MTOM con Spring Web Services
Spring Web Services tiene soporte para poder publicar y consumir web services que adjuntan un archivo, utilizando el protocolo MTOM.
A continuación vamos a ver un ejemplo concreto de publicación e invocación a un Web Service de prueba.
Contenido
Servidor
Para el servidor, vamos a crear un modulo web. La configuración del servlet de Spring Web Services es la estándar y vamos a hacer foco en los pasos siguientes.
Publicacion del servicio
La idea para este ejemplo es crear un servicio que sea la interfaz de un repositorio de archivos pdf, por lo tanto, deberíamos poder pasarle el nombre de un archivo y éste debería retornar el mismo en caso de encontrarlo. Teniendo en mente esto, creamos nuestro contrato (contract-first):
<element name="LoadPdfRequest" type="string"/>
<element name="LoadPdfResponse" type="tns:Pdf"/>
<complexType name="Pdf">
<sequence> <element name="name" type="string"/> <element name="file" type="base64Binary" xmime:expectedContentTypes="application/pdf"/> </sequence>
</complexType>
De lo anterior, debemos prestar atención a la definición del archivo, en donde le indicamos que es del tipo "base64Binary". Una vez resuelto el contrato, debemos decirle a Spring Web Services que lo publique. Esto lo hacemos a traves de un bean especifico que a partir de un schema, crea el WSDL:
<bean id="mtom" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema"/> <property name="portTypeName" value="PdfRepository"/> <property name="locationUri" value="/ws/pdf"/>
</bean>
Procesar invocación
Debemos configurar la aplicación, para que pueda procesar pedidos del servicio que publicamos en el paso anterior.
Primero definimos un bean que busca la anotacion @Endpoint (un endpoint es el encargado de procesar el pedido):
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping" />
Luego definimos un adaptador, para que procese el pedido de forma que el endpoint lo pueda entender. Lo mismo hace con la respuesta:
<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
<constructor-arg ref="marshaller"/>
</bean>
Este adaptador necesita de un serializador. Aca es donde entra el manejo del archivo binario. Vamos a utilizar JAXB:
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.dosideas.springws.ejemplos.mtom.schema"/> <property name="mtomEnabled" value="true"/>
</bean>
El próximo paso es generar a partir del esquema, las clases de 'bindeo' de JAXB. Para esto hay varias herramientas. En nuestro caso utilizamos el NetBeans (6.8): click derecho en el proyecto -> New.. -> Xml -> JAXB Binding. Entre las opciones que figuran, por un lado debemos elegir el .xsd que definimos anteriormente y por el otro el Package Name, que debe concordar con el valor del contextPath que le pusimos en la configuración al Jaxb2Marshaller (en nuestro caso "com.dosideas.springws.ejemplos.mtom.schema").
Una vez configurado JAXB, podemos proceder a implementar el endpoint:
@Endpoint
public class PdfRepositorioEndPoint {
/** * object factory creada por JAXB para bindear clases con el schema. */ private ObjectFactory objectFactory = new ObjectFactory();
@PayloadRoot(localPart = "LoadPdfRequest", namespace = "http://www.dosideas.com/spring-ws/ejemplos/mtom") public JAXBElement<Pdf> pedidoPdf(JAXBElement<String> requestElement) throws IOException { String nombrePdf = requestElement.getValue(); Pdf response = new Pdf(); response.setName(nombrePdf); //Se puede crear un DataHandler de muchas formas. Aca una simple para el ejemplo. response.setFile(new DataHandler(getClass().getResource(nombrePdf)));
return objectFactory.createLoadPdfResponse(response); }
}
Debemos prestarle atención a la creación del DataHandler, el cual se encarga de nuestro archivo binario. Luego es manejo trivial de Spring Web Services y JAXB.
Cliente
Si bien el cliente podría ser un test del servidor, vamos a crear en este ejemplo otro modulo web, por un lado para no confundir la configuración entre cliente y servidor y por el otro para poder visualizar en el browser el pdf.
Básicamente vamos a tener un servlet, que invoca al servicio y luego escribe el pdf a la pagina.
Configurar cliente
En el ejemplo que se pueden bajar, van a encontrar dos implementaciones, una con Saaj (SOAP with Attachments API for Java) y otra con Axiom (Axis Object Model). A continuación vamos a detallar la implementación con Saaj.
Primero definimos un bean, indicando donde esta el servicio:
<bean id="client" abstract="true">
<property name="defaultUri" value="http://localhost:7001/mtom-pdf/ws/pdf"/>
</bean>
Luego definimos el cliente:
<bean id="saajClient" class="com.dosideas.mtom.ws.SaajMtomClient" parent="client">
<constructor-arg> <bean class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> </constructor-arg> <property name="marshaller" ref="marshaller"/> <property name="unmarshaller" ref="marshaller"/>
</bean>
En el caso de utilizar Saaj, vamos a necesitar indicarle un serializador, y al igual que en el servidor, vamos a utilizar JAXB:
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.dosideas.mtom.cliente.sws"/> <property name="mtomEnabled" value="true"/>
</bean>
Implementar el cliente
Para invocar servicios, Spring Web Services provee de una clase utilitaria que facilita bastante las cosas: WebServiceTemplate. En la configuración que realizamos en el paso anterior nos adelantamos a esto, indicándole a nuestro cliente donde estaba el servicio.
El WebServiceTemplate lo podemos obtener, heredando de WebServiceGatewaySupport:
public class SaajMtomClient extends WebServiceGatewaySupport {
/** * object factory creada por JAXB para bindear clases con el schema. */ private ObjectFactory objectFactory = new ObjectFactory(); /** * Construye el cliente a traves de la message factory. * @param messageFactory message factory que utiliza SAAJ. */ public SaajMtomClient(SaajSoapMessageFactory messageFactory) { super(messageFactory); }
Lo que resta es el método principal que se encargue de enviar el pedido y procesar la respuesta:
/**
* Carga un pdf en un DataHandler para luego poder leerlo. * @param path donde se encuentra el pdf. * @return un DataHandler que contiene el pdf.
- /
public DataHandler loadPdf(String path) {
//pedido JAXBElement<String> loadPdfRequest = objectFactory.createLoadPdfRequest(StringUtils.getFilename(path)); //respuesta JAXBElement<Pdf> loadPdfResponse = (JAXBElement<Pdf>) getWebServiceTemplate().marshalSendAndReceive(loadPdfRequest); Pdf pdf = loadPdfResponse.getValue(); return pdf.getFile();
} <code>
La clase DataHandler nos permite obtener un InputStream para ir leyendo el pdf.