Está en la página 1de 14

Av. Canaval y Moreyra 380, 6to.

Piso, San Isidro


Tel: +51(1) 987500273 www.joedayz.org

SPRING WEB SERVICES


1.5
MTOM

SPRING-WS –Session 5 1
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

CUS

CUS: Calcular operaciones matemáticas.

Descripción breve del CUS: El CUS permitirá al usuario del web service calcular
operaciones matemáticas de suma, resta, multiplicación y división, la información
enviada al servicio web podrá estar en formato XML o en un archivo de texto
comprimido en formato zip que el web service procesará y generará la información
en formato XML.

Un ejemplo de mensaje XML enviado al servidor será:

Spring WS

Como voy a usar spring para implementar mi web service lo primero que tengo que
hacer es definir el formato de los mensajes aceptados por mi web service así como
el formato de los mensajes de respuesta de mi web service. Esta definición la
realizo en un schema XSD:

SPRING-WS –Session 5 2
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

<?xml version="1.0" encoding="UTF-8"?>


<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="qualified"
targetNamespace="http://www.joedayz.pe/cus/calculadora"
xmlns:calc="http://www.joedayz.pe/cus/calculadora"
xmlns:xmime="http://www.w3.org/2005/05/xmlmime">

<!-- Este schema define el contrato de servicio que el componente calculadora


ofrecerá
a los clientes del servicio web, aquí se definen el formato de los mensajes de
envío así como de los formatos de respuesta, una vez especificado este schema
XSD
en los archivos de configuración de spring, en tiempo de ejecución spring
generará
el descriptor de despliegue del serviio web WSDL que es el que finalmente
definirá
el contrato de interoperabilidad del servicio web. -->

<!-- Formato del mensaje de envío. -->


<xs:element name="mensajeRequest">
<xs:complexType>
<!-- El calculo de las operaciones puede solicitarse mediante
XML o en un archivo zip -->
<xs:choice>
<!-- Si el envío lo realizan mediante xml -->
<xs:element name="operaciones">
<xs:complexType>
<xs:sequence>
<xs:element name="operacion"
type="calc:operacionType"
maxOccurs="unbounded" />

SPRING-WS –Session 5 3
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

</xs:sequence>
</xs:complexType>
</xs:element>
<!-- Si el envío lo realizan mediante un archivo zip. El
archivo zip comprime
un archivo de texto operaciones.txt con el
siguiente formato:
operador,operando1,operando2,operandoN
-->
<xs:element name="operacionesZip"
type="calc:operacionesZipType"/>
</xs:choice>
</xs:complexType>
</xs:element>

<!-- Formato de salida siempre es en XML -->


<xs:element name="mensajeResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="resultados">
<xs:complexType>
<xs:sequence>
<xs:element name="resultado"
maxOccurs="unbounded" type="calc:resultadoType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:simpleType name="operadorType">
<xs:restriction base="xs:string">
<xs:enumeration value="+" />
<xs:enumeration value="-" />
<xs:enumeration value="*" />
<xs:enumeration value="/" />
</xs:restriction>
</xs:simpleType>

<xs:complexType name="operacionType">
<xs:sequence>
<xs:element name="operandos">
<xs:complexType>
<xs:sequence>
<xs:element name="operando" minOccurs="2"
maxOccurs="unbounded"
type="xs:decimal" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="id" />
<xs:attribute name="operador" type="calc:operadorType" />
</xs:complexType>

<xs:complexType name="resultadoType">
<xs:simpleContent>
<xs:extension base="xs:decimal">
<xs:attribute name="id" use="required" type="xs:anySimpleType"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="operacionesZipType">
<xs:sequence>
<xs:element name="archivoZip" type="xs:base64Binary"
xmime:expectedContentTypes="application/zip"/>

SPRING-WS –Session 5 4
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

</xs:sequence>
<xs:attribute name="nombreArchivoZip" />
</xs:complexType>

</xs:schema>

El schema anterior me define los mensajes de entrada al web service y el formato


de mensaje de respuesta.

Ahora veamos el descriptor de despliegue de la aplicación web:

El descriptor anterior define el servlet calculadora-annotation-servlet.xml en forma


implicita, dicho contenedor de beans es el siguiente:

<?xml version="1.0" encoding="UTF-8"?>


<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-
oxm-1.5.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-
services/web-services-1.5.xsd">

<!-- Inicizalización del log4j -->


<bean id="log4jInitialization"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list>
<value>C:\\temporal\spring.config</value>
</list>
</property>
</bean>

<!-- Mapeo del punto al cual llegarán las solicitudes del servicio web, se define el punto final
del servicio web por defecto así como los interceptores de validación y de logging.
Detecta los métodos anotados con @PayloadRoot sobre las clases anotadas con @Endpoint. -->

SPRING-WS –Session 5 5
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

<bean id="annotationMapping"

class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
<property name="interceptors">
<list>
<ref local="validatingInterceptor"/>
<ref local="loggingInterceptor"/>
</list>
</property>
<property name="order" value="1"/>
</bean>

<!-- Resolvedor de excepciones que genera el servicio web implementado mediante anotaciones
@SoapFault -->
<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver">
<property name="order" value="1"/>
</bean>

<!-- Interceptor de validación de mensajes, valida que los mensajes de entrada y salida del
servicio web
se correspondan con el XSD -->
<bean id="validatingInterceptor"

class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="xsdSchema" ref="schema"/>
<property name="validateRequest" value="true"/>
<property name="validateResponse" value="true"/>
</bean>

<!-- Interceptor para procesar mensajes de log -->


<bean id="loggingInterceptor"
class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>

<!-- Este bean define al descriptor del servicio web(WSDL) que es generado en tiempo de
ejecución
El descriptor de despliegue del servicio web(WSDL) puede ser recibido ingresando a
http://server:port/contexto/servlet/locationUri/nombreBean.wsdl, para mi caso es:
http://192.168.1.33:7001/springws/serviciosWeb/calculadoraDescriptor.wsdl
En este caso el nombre del bean se corresponde con el nombre del archivo -->
<bean id="calculadoraDescriptor"
class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema"/>
<property name="portTypeName" value="Calculadora"/>
<property name="locationUri" value="/serviciosWeb"/>
</bean>

<!-- Este bean define el schema XSD que soportará el servicio web -->
<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="/WEB-INF/calculadora.xsd"/>
</bean>

</beans>

A continuación veamos las clases java que permitirán crear el endpoint:

Operación.java

package pe.joedayz.cus.domain;

import java.math.BigDecimal;

SPRING-WS –Session 5 6
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

import java.util.List;

public class Operacion {

private String operador;


private List<BigDecimal> operandos;

public Operacion(){
}

public Operacion(String operador,List<BigDecimal> operandos){


this.operador = operador;
this.operandos = operandos;
}

public String getOperador() {


return operador;
}

public void setOperador(String operador) {


this.operador = operador;
}

public List<BigDecimal> getOperandos() {


return operandos;
}

public void setOperandos(List<BigDecimal> operandos) {


this.operandos = operandos;
}

OperandoInvalidoException.java

UnzipException.java

SPRING-WS –Session 5 7
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

CalculadoraService.java

CalculadoraServiceImpl.java

package pe.joedayz.cus.service.impl;

import java.math.BigDecimal;
import java.util.List;

import pe.joedayz.cus.service.CalculadoraService;

public class CalculadoraServiceImpl implements CalculadoraService {

public BigDecimal dividir(List<BigDecimal> parametros) {


BigDecimal operador1 = parametros.get(0);
BigDecimal operador2 = parametros.get(1);
return operador1.divide(operador2);
}

public BigDecimal multiplicar(List<BigDecimal> parametros) {


BigDecimal resultado = BigDecimal.ONE;
for(BigDecimal operando:parametros){
resultado = resultado.multiply(operando);
}
return resultado;
}

SPRING-WS –Session 5 8
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

public BigDecimal restar(List<BigDecimal> parametros) {


BigDecimal operador1 = parametros.get(0);
BigDecimal operador2 = parametros.get(1);
return operador1.subtract(operador2);
}

public BigDecimal sumar(List<BigDecimal> parametros) {


BigDecimal resultado = BigDecimal.ZERO;
for(BigDecimal operando:parametros){
resultado = resultado.add(operando);
}
return resultado;
}

public BigDecimal calcular(List<BigDecimal> parametros,String operacion) {


if (operacion.equals("+"))
return sumar(parametros);
if (operacion.equals("-"))
return restar(parametros);
if (operacion.equals("*"))
return multiplicar(parametros);
if (operacion.equals("/")) {
return dividir(parametros);
}
return null;
}
}

Como siempre para que el desarrollo sea más eficiente, generar fuentes con JAXB,
usamos MAVEN, el descriptor del proyecto es el siguiente:

SPRING-WS –Session 5 9
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

CalculadoraEndpointWithAnnotation.java

package pe.joedayz.cus.ws;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipInputStream;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.datatype.DatatypeConfigurationException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;

import pe.joedayz.cus.domain.Operacion;
import pe.joedayz.cus.exception.OperandoInvalidoException;
import pe.joedayz.cus.exception.UnzipException;
import pe.joedayz.cus.schema.MensajeRequest;
import pe.joedayz.cus.schema.MensajeResponse;
import pe.joedayz.cus.schema.MensajeResponse.Resultados;
import pe.joedayz.cus.schema.ObjectFactory;
import pe.joedayz.cus.schema.OperacionType;
import pe.joedayz.cus.schema.OperacionesZipType;
import pe.joedayz.cus.schema.ResultadoType;
import pe.joedayz.cus.service.CalculadoraService;

@Endpoint
public class CalculadoraEndpointWithAnnotation {

private final Log logger = LogFactory.getLog(CalculadoraEndpointWithAnnotation.class);


public static final String NAMESPACE_URI = "http://www.joedayz.pe/cus/calculadora";
public static final String REQUEST_LOCAL_NAME = "mensajeRequest";
public static final String RESPONSE_LOCAL_NAME = "mensajeResponse";
private ObjectFactory objectFactory = new ObjectFactory();

private CalculadoraService calculadoraService;

/**
* Antes de la ejecución de este método la declaración:
* <sws:marshalling-endpoints marshaller="formador" unmarshaller="formador"/>
* se a encargado de crear el arbol de objetos desde un archivo xml, hay
* que recordar que el servicio web recibe XML y que es spring integrado con
* JAXB quién realiza el proceso de unmarshalling y marshalling para crear el
* arbol de objetos(MensajeRequest) y el formato de respuesta respectivamente.
* */
@PayloadRoot(localPart = REQUEST_LOCAL_NAME, namespace = NAMESPACE_URI)
public MensajeResponse calcular(MensajeRequest request)
throws DatatypeConfigurationException,OperandoInvalidoException,UnzipException {

SPRING-WS –Session 5 10
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

logger.info("Mensaje recibido.");

//Empiezo a crear el objeto de respuesta


MensajeResponse response = objectFactory.createMensajeResponse();
Resultados resultados = objectFactory.createMensajeResponseResultados();
response.setResultados(resultados);

//Si el cliente del servicio web envió las operaciones en formato XML
if(request.getOperaciones()!=null){
List<OperacionType> operaciones =
request.getOperaciones().getOperacion();
for(OperacionType operacion:operaciones){
if( operacion.getOperador().equals("/") &&

operacion.getOperandos().getOperando().get(1).equals(BigDecimal.ZERO))
throw new OperandoInvalidoException("No se puede dividir
por cero.");

BigDecimal calculo =
calculadoraService.calcular(operacion.getOperandos().getOperando(), operacion.getOperador());
ResultadoType resultado = objectFactory.createResultadoType();
resultado.setId(operacion.getId());//Copio los identificadores.
resultado.setValue(calculo);
resultados.getResultado().add(resultado);
logger.info("Respuesta calculada en el
server:"+calculo.toPlainString());
}
}
else{
//Si no me enviaron las operaciones en formato XML, entonces me las
//enviaron en un archivo comprimido en formato zip:
OperacionesZipType operacionesZip = request.getOperacionesZip();
String archivoAsString = unzipOperaciones(operacionesZip.getArchivoZip());
List<Operacion> operacionesDomain = crearOperaciones(archivoAsString);
int idSecuencial = 1;
for(Operacion operacion:operacionesDomain){
BigDecimal calculo =
calculadoraService.calcular(operacion.getOperandos(), operacion.getOperador());
ResultadoType resultado = objectFactory.createResultadoType();
resultado.setId(String.valueOf(idSecuencial++));
resultado.setValue(calculo);
resultados.getResultado().add(resultado);
logger.info("Respuesta calculada en el
server:"+calculo.toPlainString());
}
}
return response;
}

/**
* Descomprime el archivo zip en una cadena
* */
private String unzipOperaciones(DataHandler handlerZip)throws UnzipException{
DataSource dataSource = null;
ZipInputStream zis = null;
String archivoAsString = null;
try{
dataSource = handlerZip.getDataSource();
zis = new ZipInputStream(dataSource.getInputStream());
if(zis.getNextEntry() != null){

SPRING-WS –Session 5 11
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

int bytesLeidos = 0;
int BUFFER_SIZE = 1024*10;
byte buffer[] = new byte[BUFFER_SIZE];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while( (bytesLeidos = zis.read(buffer,0,BUFFER_SIZE)) != -1 ){
bos.write( buffer, 0, bytesLeidos);
}
archivoAsString = new String(bos.toByteArray());
}
zis.close();
}catch(Exception e){
logger.info("Error al leer el zip.",e);
throw new UnzipException("Error al leer el zip.");
}finally{
}
return archivoAsString;
}

/**
* Crea una lista de operaciones a partir de un cadena de texto.
* */
private List<Operacion> crearOperaciones(String archivoAsString){
Operacion operacion = null;
List<Operacion> operaciones = new ArrayList<Operacion>();
String[] lineas = archivoAsString.split("\n");
for(int i=0;i<lineas.length;i++){
logger.info("Linea a analizar:"+lineas[i]);
String[] operacionToken = lineas[i].split(",");
List<BigDecimal> operandos = new ArrayList<BigDecimal>();
for(int j=1;j<operacionToken.length;j++){
logger.info("Operador a procesar:"+operacionToken[j]);
operandos.add(new BigDecimal(operacionToken[j].trim()));
}
operacion = new Operacion(operacionToken[0],operandos);
operaciones.add(operacion);
}
return operaciones;
}

public CalculadoraService getCalculadoraService() {


return calculadoraService;
}

public void setCalculadoraService(CalculadoraService calculadoraService) {


this.calculadoraService = calculadoraService;
}
}

Finalmente el calculadora-annotation-servlet.xml será

<?xml version="1.0" encoding="UTF-8"?>


<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:sws="http://www.springframework.org/schema/web-services"

SPRING-WS –Session 5 12
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-
oxm-1.5.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-
services/web-services-1.5.xsd">

<!-- Inicizalización del log4j -->


<bean id="log4jInitialization"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list>
<value>C:\\temporal\spring.config</value>
</list>
</property>
</bean>

<!-- Mapeo del punto al cual llegarán las solicitudes del servicio web, se define el punto final
del servicio web por defecto así como los interceptores de validación y de logging.
Detecta los métodos anotados con @PayloadRoot sobre las clases anotadas con @Endpoint. -->
<bean id="annotationMapping"

class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
<property name="interceptors">
<list>
<ref local="validatingInterceptor"/>
<ref local="loggingInterceptor"/>
</list>
</property>
<property name="order" value="1"/>
</bean>

<!-- Resolvedor de excepciones que genera el servicio web implementado mediante anotaciones
@SoapFault -->
<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver">
<property name="order" value="1"/>
</bean>

<!-- Interceptor de validación de mensajes, valida que los mensajes de entrada y salida del
servicio web
se correspondan con el XSD -->
<bean id="validatingInterceptor"

class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="xsdSchema" ref="schema"/>
<property name="validateRequest" value="true"/>
<property name="validateResponse" value="true"/>
</bean>

<!-- Interceptor para procesar mensajes de log -->


<bean id="loggingInterceptor"
class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>

<!-- El punto al cual todas las solicitudes que ingresen por medio del servicio web llegarán y
procesarán -->
<bean id="calculadoraEndpoint" class="pe.joedayz.cus.ws.CalculadoraEndpointWithAnnotation">
<property name="calculadoraService" ref="calculadoraService"/>
</bean>

<!-- Definiciones a tener en cuenta:


Marshalling: Proceso por el cual se crea instancias xml a partir de objetos java.

SPRING-WS –Session 5 13
Av. Canaval y Moreyra 380, 6to.Piso, San Isidro
Tel: +51(1) 987500273 www.joedayz.org

Ejemplo: instancia persona -> persona.xml


Unmarshalling: Proceso por el cual se crea un arbol de objetos a partir de una instancia xml
Ejemplo: persona.xml -> instancia persona(el objeto, no la clase).
La declaración xml siguiente indica que se creará un objeto que realizará los procesos
de marshalling(formación de xml a partir de instancias java) y
de unmarshalling(formación de java a partir de instancias xml), como parámetros
tiene su identificador y el contexto(contextPath) desde el cuál analizará las clases
para realizar los procesos de marshall y unmarshall
-->
<oxm:jaxb2-marshaller id="formador" contextPath="pe.joedayz.cus.schema" />

<!-- Indica que un unmarshaller debe ser usado para convertir un mensaje xml enviado al servicio
web a un árbol de objetos y una vez convertido setearlo en los parámetros de un método
tambien es usado para retornar valores marshalled(de objetos a java) en los mensajes de
respuesta -->
<sws:marshalling-endpoints marshaller="formador" unmarshaller="formador"/>

<!-- Este bean define al descriptor del servicio web(WSDL) que es generado en tiempo de
ejecución
El descriptor de despliegue del servicio web(WSDL) puede ser recibido ingresando a
http://server:port/contexto/servlet/locationUri/nombreBean.wsdl, para mi caso es:
http://localhost:28080/cus/serviciosWeb/calculadoraDescriptor.wsdl
En este caso el nombre del bean se corresponde con el nombre del archivo -->
<bean id="calculadoraDescriptor"
class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema"/>
<property name="portTypeName" value="Calculadora"/>
<property name="locationUri" value="/serviciosWeb"/>
</bean>

<!-- Este bean define el schema XSD que soportará el servicio web -->
<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="/WEB-INF/calculadora.xsd"/>
</bean>

<!-- Nuestro bean de servicios -->


<bean id="calculadoraService" class="pe.joedayz.cus.service.impl.CalculadoraServiceImpl"/>
</beans>

SPRING-WS –Session 5 14

También podría gustarte