Wednesday, June 17, 2009

Spring Web Services Framework - Details

Before going into implementation details, lets cover some spring web services concepts.

WebServiceMessage
A webServiceMessage represents the platform independent XML message. Related interface (org.springframework.ws.WebServiceMessage) contains methods for getting the payload of the request. When developing web services using spring web services framework, you generally deal with the payload of the message. If you need to access the SOAP specific part of the message, then all you need is to cast the web service message to the SoapMessage.

SoapMessage
org.springframework.ws.soap.SoapMessage class extends the org.springframework.ws.WebServiceMessage interface for accessing the SOAP headers, attachments etc.

Message Factories
To create empty web service messages, it is required to configure a message factory. These classes also help us with generating web service messages from different input streams, such as from java.io.File. Message factories must implement the WebServiceMessageFactory interface. There are two concreate implementation of this interface.
  • org.springframework.ws.soap.saaj.SaajSoapMessageFactory
  • org.springframework.ws.soap.axiom.AxiomSoapMessageFactory
Former uses SAAJ(SOAP Attachements API for JAVA). This standart is a part of JEE 1.4 so it is most supportable API for most application servers. But there is one handicap using SAAJ. It uses DOM(Document Object Model) API and SOAP messages are kept in memory. For applications sending and receiving large web service messages, as you guess, there will be some performance&memory problems:)
Latter uses AXis 2 Object Model to create web service messages. AXIOM is based on STAX(Streaming API for XML) API and hence can be more efficient for larger xml messages.

MessageContext
Client request some information and server responses the requested information. In spring web services, this conversation is kept in MessageContext. Both web service request and response can be accessable from concreate implementation of MessageContext interface.

Transport Context
Sometimes, you need to access to the connection layer. In spring ws, you can get current connection via org.springframework.ws.transport.context.TransportContextHolder

public WebServiceConnection getWebServiceConnection(){
TransportContext transportContext = TransportContextHolder.getTransportContext();
return transportContext.getConnection();
}

Now we can get the remote address of the client after casting the webServiceConnection to the appropriate transport class.

public String getClientIP(){
HttpServletConnection httpServletConnection = (HttpServletConnection) getWebServiceConnection();
return httpServletConnection.getHttpServletRequest().getRemoteAddr();
}

Transport Types
Why spring ws cares xml payload instead of request URL? Answer is quite simple. Because it supports multiple transport mechanisms. If it supports only HTTP Transport, then dealing with request URL is enough. So lets look at all supportable transports in brief.
  1. HTTP Transport
  2. JMS Transport
  3. E-Mail Transport
  4. Embedded HTTP Server Transport
Widely used one is the HTTP Transport. Requests are taken by servlets (dispatcher servlets). In your web.xml file, add a servlet definition like below.


<web-app>

<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>
org.springframework.ws.transport.http.MessageDispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

</web-app>


Spring web services doesn't let us deep into SOAP or WSDL details. We only deal with data contract (Xml Schema Definition). Spring ws has automatic wsdl publishing feature. When application context starts up, it look for wsdl bean definitions. Found bean definitions are exposed as wsdl with their bean names. For example in our spring-ws.servlet.xml file contains such a bean definition:


<bean id="orders"
class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
<constructor-arg value="/WEB-INF/wsdl/Orders.wsdl"/>
</bean>


Orders.wsdl can be accessed via URL:


http://localhost:8080/spring-ws/orders.wsdl


This code is taken from reference documentation and expose existing wsdl file to the clients.However, in our sample application, we are going to publish our wsdl using only data contract file.

Endpoints
Webservice messages are delivered to these points. They supply the related data for business layer by extracting the information from web service messages. Endpoints implement the PayloadEndpoint interface.
But sometimes, if you need to access message context in your endpoints, then your class should implement the MessageEndpoint interface. By accessing to message context, it is possible to access entire message including the SOAP header which means we can get SOAP header and attachments.

There are different abstract endpoint classes that we can implement based on whether we will use OXM(Object XML Mapping) or not.
  1. AbstractDomPayloadEndpoint - uses W3C DOM API
  2. AbstractJDomPayloadEndpoint - uses JDOM API
  3. AbstractXomPayloadEndpoint - uses XOM API
  4. and other XML API's Payload Endpoint Interfaces
If you are using OXM, then your endpoint classes implement the AbstractMarshallingPayloadEndpoint interaface.

All these interfaces has a invokeInternal method. Method result and arguements change according to related API. If it is AbstractMarshallingPayloadEndpoint, then method result and method arguement, as you guess, java.lang.Object (Marshalled/Unmarshalled XML Request/Response).

After J2EE 1.5, annotations are part of our development life. Spring ws also supports annotation based endpoint configuration. To do this, we mark our classes with @Endpoint annotation. if we extend our endpoints from abstract classes (using inheritance), invokeInternal method will be invoked when request arrives. But when we configure our endpoints using @Endpoint annotation, it is possible to handle more than one request. At this point, we use @PayloadRoot method annotation. PayloadRoot annotation maps requests to the method by looking at the root element of the payload. For instance, an orderRequest arrives to the endpoint with the following structure:


<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<orderRequest xmlns="http://samples" id="42"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>


And our end point is configured as below:

package samples;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;

@Endpoint
public class AnnotationOrderEndpoint {
private final OrderService orderService;

public AnnotationOrderEndpoint(OrderService orderService) {
this.orderService = orderService;
}

@PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
public Order getOrder(OrderRequest orderRequest) {
return orderService.getOrder(orderRequest.getId());
}

@PayloadRoot(localPart = "order", namespace = "http://samples")
public void order(Order order) {
orderService.createOrder(order);
}

}


Endpoint looks the namespace and localpart of the payload(in this example orderRequest element - root element of the SOAP Body) and delegates message to our getOrder method.

Endpoint Mappings
Taken requests are mapped to the endpoints in three way:

PayloadRootQNameEndpointMapping
This type of mapping is done by looking at the qualified name of the root element of the payload.

In above orderRequest, root element of the payload (SOAP Body) is orderRequest element.


<?xml version="1.0" encoding="UTF-8"?>
<orderRequest xmlns="http://samples" id="42"/>


Endpoint mapping configuration is done by adding related bean to our springws-servlet.xml.



<bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="mappings">
<props>
<prop key="{http://samples}orderRequest">getOrderEndpoint</prop>
<prop key="{http://samples}order">createOrderEndpoint</prop>
</props>
</property>
</bean>

<bean id="getOrderEndpoint" class="samples.GetOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>

<bean id="createOrderEndpoint" class="samples.CreateOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>
<beans>



SoapActionEndpointMapping
Requests are mapped according to SOAPAction HTTP header. Every client sends this information to the server.

For mapping configuration, add below bean to your spring ws configuration file:


<beans>
<bean id="endpointMapping" class="org.springframework.ws.soap.server.endpoint.mapping.SoapActionEndpointMapping">
<property name="mappings">
<props>
<prop key="http://samples/RequestOrder">
getOrderEndpoint
</prop>
<prop key="http://samples/CreateOrder">
createOrderEndpoint
</prop>
</props>
</property>
</bean>

<bean id="getOrderEndpoint" class="samples.GetOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>

<bean id="createOrderEndpoint" class="samples.CreateOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>
</beans>

MethodEndpointMapping
Besides these mapping types, as i mentioned before, mapping can also be done using java 1.5 annotations (@PayloadRoot and @SoapAction)

After looking the details, lets develop our server and related clients.

1 comment:

Unknown said...

Good entry. I have a question. Why my WSDL creating with SpringWS does not have the:



and other things for SOAP headers elements