XML/SOAP services work on the XML/SOAP Protocol level (see WSO2 SOA Enablement Server Message Processing Overview for a description of levels). This is a lower level than the one on which Java services reside. It provides direct access to the SOAP messages that represent Web service requests. In WSO2 SOA Enablement Server, SOAP messages on the XML/SOAP level follow Simple Object Access Protocol (SOAP) 1.1 or 1.2 and SOAP with Attachments specifications and are accessible by using SOAP with Attachments API for Java (SAAJ) 1.1.
There are two varieties of XML services, consuming either request-response or one-way messages, as per the Java API for XML Messaging (JAXM) 1.1 specification. The service instance implements a different interface for each:
Type of message consumed | JAXM interface implemented by service instance |
---|---|
Request-response | javax.xml.messaging.ReqRespListener |
One-way | javax.xml.messaging.OnewayListener |
Upon receiving an incoming request, the only method of either interface, onMessage(SOAPMessage), is called: the parameter represents the incoming SOAP message. For one-way messages, the method has no return value. For request-response messages, the return value represents the SOAP response message.
The following sections explain how to choose which type of service to implement.
The OnewayListener interface is implemented by one-way messaging services. The receiver of a one-way message is sent the message in one operation and returns the response in another operation.
There is only one method, onMessage(SOAPMessage), on OnewayListener. It describes how to process a one-way request. When the method is called, it passes the SOAPMessage object to the OnewayListener implementation.
Unlike the case for a request-response message, the recipient of a one-way request does not have to send an immediate response. The time interval between the request and the response can be very long.
Example 29 shows how to perform simple SOAP messaging through an XML/SOAP service that implements OnewayListener. When the service gets a SOAP message, it performs the following steps:
It obtains the target from the SOAP header.
It creates a new SOAP message with the rest of the header.
It puts a new string message into the body of the outgoing message.
It sends the message to the target.
Example 29. One-Way XML/SOAP Service
// Copyright WSO2 Inc. All rights reserved. // Use is subject to license terms. package example.basics.webservices; import org.idoox.util.RuntimeWrappedException; import org.systinet.wasp.messaging.WaspProviderConnection; import java.util.Iterator; import javax.xml.messaging.JAXMException; import javax.xml.messaging.OnewayListener; import javax.xml.messaging.ProviderConnectionFactory; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPConstants; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; public class MessagingService implements OnewayListener { private WaspProviderConnection provider; //constructor public MessagingService() { try { this.provider = (WaspProviderConnection) ProviderConnectionFactory.newInstance().createConnection(); } catch (JAXMException e) { throw new RuntimeWrappedException(e); } } public void onMessage(SOAPMessage msg) { try { // gets the envelope SOAPEnvelope envelope = msg.getSOAPPart() .getEnvelope(); SOAPBody body = envelope.getBody(); Iterator iter = body.getChildElements(envelope.createName("message")); SOAPElement message = (SOAPElement) iter.next(); // the first header is the next target SOAPHeader headers = envelope.getHeader(); // the message is not processed further if there is no header if (headers != null) { // get the first header and remove it - it is the next target iter = headers.examineHeaderElements( SOAPConstants.URI_SOAP_ACTOR_NEXT); SOAPHeaderElement target = (SOAPHeaderElement) iter.next(); target.detachNode(); // remove old body/message and create new one message.detachNode(); message = body.addBodyElement(envelope.createName("message")); int lastSlash = target.getValue() .lastIndexOf('/'); String dwarf = target.getValue() .substring(lastSlash + 1); message.addTextNode("Hello " + dwarf + " !!!"); // send the message this.provider.send( msg, target.getValue()); } } catch (Exception e) { e.printStackTrace(); } } }
The ReqResponseListener interface is implemented by request-response messaging services. In request-response messaging, sending a request and receiving the response are both done in a single operation.
As with OnewayListener, the only method on the interface is onMessage(SOAPMessage). It is used to describe how to process a request. In contrast to the OnewayListener, the onMessage method has a return value, representing the response. When the method is called, it passes the SOAPMessage object to the interface implementation and returns a response.
Example 30 is a simple HelloService that consumes an incoming request SOAP message and creates a simple response SOAP message.
Example 30. Request-Response XML/SOAP Service
// Copyright WSO2 Inc. All rights reserved. // Use is subject to license terms. package example.basics.webservices; import org.idoox.util.RuntimeWrappedException; import java.util.Iterator; import javax.xml.messaging.ReqRespListener; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPBodyElement; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPMessage; public class XMLHelloService implements ReqRespListener { MessageFactory factory; //constructor public XMLHelloService() { try { this.factory = MessageFactory.newInstance(); } catch (SOAPException e) { throw new RuntimeWrappedException(e); } } public SOAPMessage onMessage(SOAPMessage message) { try { // read request SOAPEnvelope envelope = message.getSOAPPart() .getEnvelope(); SOAPBody body = envelope.getBody(); Iterator operations = body.getChildElements( envelope.createName("string_Request", null, "http://wso2.com/xsd/SchemaTypes/")); String requestString = ((SOAPElement) operations.next()).getValue(); // create response SOAPMessage respMessage = factory.createMessage(); SOAPEnvelope respEnvelope = respMessage.getSOAPPart() .getEnvelope(); SOAPBody respBody = respEnvelope.getBody(); SOAPBodyElement bodyElement = respBody.addBodyElement( respEnvelope.createName("string_Response", null, "http://wso2.com/xsd/SchemaTypes/")); bodyElement.addTextNode("Hello, " + requestString + "!"); return respMessage; } catch (Exception e) { try { // create fault SOAPMessage faultMessage = factory.createMessage(); SOAPBody body = faultMessage.getSOAPPart() .getEnvelope() .getBody(); SOAPFault fault = body.addFault(); fault.setFaultCode("Server"); fault.setFaultString("SOAP Message could not be processed"); return faultMessage; } catch (SOAPException ee) { throw new RuntimeWrappedException(e); } } } }
Instead of using the tree-like SAAJ API, you can access and process SOAP messages in a stream-based manner using Tokenizer (see Javadoc for org.idoox.xml.Tokenizer). The tokenizer represents an XML document as a sequence of XML tokens (elements).
Example 31 is a stream-based HelloService using Tokenizer. The service performs the following steps:
It gets an instance of Tokenizer from the incoming SOAP message. (This is done by calling the methods getSOAPPart(), getContent() and then getTokenizer(), in that order.
It creates a wrapper of this tokenizer. Inside this wrapper, it modifies the tokens of the incoming SOAP message to create the outgoing message.
It sets this tokenizer as the source for the outgoing SOAP message.
Example 31. Stream-Based Service Using Tokenizer
// Copyright WSO2 Inc. All rights reserved. // Use is subject to license terms. package example.basics.webservices; import org.idoox.util.RuntimeWrappedException; import org.idoox.xml.Token; import org.idoox.xml.Tokenizer; import org.idoox.xml.TokenizerException; import org.idoox.xml.TokenizerSource; import org.idoox.xml.TokenizerWrapper; import java.io.IOException; import javax.xml.messaging.ReqRespListener; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; public class XMLTokenizerService implements ReqRespListener { MessageFactory factory; // constructor for service public XMLTokenizerService() { try { this.factory = MessageFactory.newInstance(); } catch (SOAPException e) { throw new RuntimeWrappedException(e); } } public SOAPMessage onMessage(SOAPMessage message) { try { // gets SOAP part from incoming message SOAPPart part = message.getSOAPPart(); // gets tokenizer from this part TokenizerSource source = (TokenizerSource) part.getContent(); Tokenizer tokenizer = source.getTokenizer(); // creates instance of Tokenizer that is a wrapper of the tokenizer // from the incoming message XMLTokenizerService.MyTokenizer myTokenizer = new XMLTokenizerService.MyTokenizer(tokenizer); // creates outgoing message SOAPMessage outMessage = factory.createMessage(); // gets SOAP part for outgoing message SOAPPart outPart = outMessage.getSOAPPart(); // gets the tokenizer source TokenizerSource outSource = (TokenizerSource) outPart.getContent(); // sets new tokenizer outSource.setTokenizer(myTokenizer); outPart.setContent(outSource); return outMessage; } catch (Exception e) { throw new RuntimeWrappedException(e); } } /** * Logging tokenizer. Wraps original tokenizer * and logs tokens. */ class MyTokenizer extends TokenizerWrapper { // current token private Token token = new Token(); // constructor public MyTokenizer(Tokenizer tokenizer) { super(tokenizer); } // modifies tokens for outgoing message public byte next() throws TokenizerException, IOException { Tokenizer tokenizer = getTokenizer(); byte next = tokenizer.next(); switch (next) { case START_TOKEN: tokenizer.readToken(token); if (token.getLocalName().equals("string_Request")) { token.localName = "string_Response"; setCurrentToken(token); } break; case END_TOKEN: tokenizer.readToken(token); if (token.getLocalName() .equals("string_Request")) { token.localName = "string_Response"; setCurrentToken(token); } break; case CONTENT: String content = tokenizer.readContent(); if (content.equals("World")) { setCurrentContent("Hello, World !"); } break; } return next; } } }
Sometimes a service needs to send or receive application-specific binary data to its peer. Such data is represented at the XML/SOAP level as attachments, following the SOAP with Attachments specification. A SOAP message can contain any number of these attachments in addition to the SOAP part.
In the SAAJ API the SOAPMessage object provides methods for creating AttachmentPart objects and for adding them to a SOAPMessage object.
Example 32 is a simple service that accesses attachments from the SOAP message.
Example 32. Service Accessing Binary Attachments From SOAP
// Copyright WSO2 Inc. All rights reserved. // Use is subject to license terms. package example.basics.webservices; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Iterator; import javax.xml.messaging.OnewayListener; import javax.xml.soap.AttachmentPart; import javax.xml.soap.SOAPMessage; public class XMLAttachmentService implements OnewayListener { public void onMessage(SOAPMessage message) { try { // gets iterator over attachments Iterator iterator = message.getAttachments(); if (iterator.hasNext()) { // gets the first attachment AttachmentPart attachmentPart = (AttachmentPart) iterator.next(); // gets the content InputStream stream = (InputStream) attachmentPart.getContent(); // writes to the file FileOutputStream toStream = new FileOutputStream("image.jpg"); byte[] buffer = new byte[1024]; int length; while ((length = stream.read(buffer)) > 0) { toStream.write(buffer, 0, length); } toStream.close(); } } catch (Exception e) { e.printStackTrace(); } } }
Alternatively, attachments can be accessed by using the DataHandler object that is a part of the JavaBeans Activation Framework (JAF). This is demonstrated in the code fragment in Example 33:
Example 33. Handling Attachments Through JAF
Iterator iterator = message.getAttachments(); if (iterator.hasNext()) { // gets the first attachment AttachmentPart attachmentPart = (AttachmentPart) iterator.next(); // gets the content DataHandler handler = attachmentPart.getDataHandler(); Object data = handler.getContent(); if ("image/jpeg".equals(handler.getContentType()) && data instanceofjava.awt.Image) { java.awt.Image image = (java.awt.Image)data; // ..... }
WSO2 SOA Enablement Server supports both MIME and DIME encapsulation types. The XML service can use the default encapsulation chosen during WSO2 SOA Enablement Server installation as its default Multi-part encapsulation or it can be set in the SOAP message. Both options are transparent to the user.
The code fragment in Example 34 illustrates how to set DIME encapsulation in the SOAP message:
Example 34. Setting DIME Encapsulation in the SOAP Message
MessageFactory factory = MessageFactory.newInstance(); WaspSOAPMessage message = (WaspSOAPMessage)factory.createMessage(); message.setAttachmentType(org.idoox.transport.Message.CT_APPLICATION_DIME);
See also Processing Attachments in XML/SOAP Level Processing, Message Processing.
If a service cannot correctly process an incoming SOAP message for any reason, the client gets back a response containing a SOAP Fault with information about the error. A SOAP message can carry one SOAP Fault element, which must be placed in the body of the message. In the SAAJ API, a SOAP fault is represented by the SOAPFault object.
The following code fragment in Example 35 shows how a SOAPFault object is added to a SOAPMessage:
Example 35. Adding a SOAPFault Object to a SOAP Message
SOAPBody body = message.getSOAPPart().getEnvelope().getBody(); SOAPFault fault = body.addFault(); fault.setFaultCode("MustUnderstand"); fault.setFaultString("SOAP Must Understand Error");
For more information on the SOAP Fault element and code, see SOAP Faults.