In this tutorial, we make no assumption about your IDE and servlet container preferences. The library has been designed to existing specifications such as the Servlet API, and does not make use of vendor-specific extensions.
HttpSoapServiceServlet is able to publish and handle SOAP web services requests. In most cases, you will only need to deploy one instance of this servlet for all of your web services. For every request it receives, the servlet will dispatch it to an endpoint handler which will handle the request.
It is possible to just set-up endpoint handlers to run your web services. However, you will probably be interested in providing a WSDL document. Also, it is possible to define SOAP header handlers at the servlet level. This is useful if you have cross-service data in the headers like authentification tokens. Endpoint handlers can also handle them when a more specific behavior is required.
Finally, the servlet configuration is made through a BSF-compliant scripting language. Indeed, we thought that XML configuration files can be silly for assembling classes together, and our API is quite simple. You can use your favorite scripting language, but a Java syntax is possible if you use Beanshell. Alternatively, if you don’t want to set-up the servlet through a scripting language of your choice, you can still subclass HttpSoapServiceServlet and put the assembly logic inside.
Endpoint handlers are pretty simple, all you need is to implement the IServiceEndpointHandler interface. The handleRequestResponse() and handleSollicitResponse() methods handle requests, while the isSoapHeaderHandled() method indicates wether a SOAP header element identified by a QName can be handled or not. This is useful when processing SOAP mandatory headers, as HttpSoapServiceServlet will generate a SOAP fault if no global header handler exists for a mandatory header, unless the target endpoint handler declares beeing able to process it.
Each HttpSoapServiceServlet instance will be mapped to its own path by the servlet container (for example to handle /dtc/* requests). The dispatching to specific endpoint handlers is done by using the path info which is appended to this base path. For example, if we associate a handler to the path info /iya, all requests to /dtc/iya will be routed to this handler. Here is how it can be performed:
// (...) servlet.addPathInfoMapping("/iya", endpointHandler); // (...)
Defining a servlet-global SOAP header handler is simple.
servlet.addHeaderHandlerMapping( QName.get("h:plop", "urn:plop:da:plop"), new ISoapHeaderHandler() { public SoapFault handleHeader(Element header, SoapMessage soapMessage) { // Do your thing... } } );
In this example, we define a handler for headers whose XML element name is plop in the urn:plop:da:plop XML namespace. The handleHeader() method should return:
null if no error has occured while processing the header.
If a endpoint handler is accessible from the /dtc/iya path, then a WSDL document can be accessed from /dtc/iya/wsdl if a WSDL provider has been defined.
The IWsdlProvider interface has to be implemented by WSDL providers. It consists of a single writeWsdl() method that will output the WSDL document to a provided writer.
We provided the StaticResourceWsdlProvider class which will simply output the content of a WSDL document as a Java application resource. The content of the resource is cached for subsequent WSDL requests. In the future, we intend to provide other implementation of WSDL providers. In particular, we are interested in a WSDL generator from a simpler XML-based service interface description.
The following example shows how to add a WSDL provider using StaticResourceWsdlProvider. The constructor takes the resource path as a parameter.
servlet.addWsdlProviderMapping("/iya", new StaticResourceWsdlProvider("iya.wsdl"));
We have also developped a simple XML-based language to help generating WSDL documents. The provider class is WsdlDtcProvider. Given the following WSDL-DTC document:
<?xml version="1.0" encoding="UTF8"?> <specification xmlns:wsdl-dtc="http://www.izforge.com/soap-dtc/wsdl-dtc" xmlns:echo="urn:testing:echo" xmlns="http://www.izforge.com/soap-dtc/wsdl-dtc" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <!-- The endpoint data --> <endpoint> <name>Echo</name> <namespace>urn:testing:echo</namespace> </endpoint> <!-- The XML Schema definition --> <xsd:schema targetNamespace="urn:testing:echo"> <xsd:element name="onestring"> <xsd:complexType> <xsd:sequence> <xsd:element name="str" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="twostrings"> <xsd:complexType> <xsd:sequence> <xsd:element name="str1" type="xsd:string"/> <xsd:element name="str2" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> <!-- The sole operation: * send two strings in the input message * get them back in the output message, plus: - a concatenation both in the header - a concatenation in the header --> <operation name="Echo" soapAction="echo"> <input> <header></header> <body> <part name="twostrings" element="echo:twostrings" /> </body> </input> <output> <header> <part name="onestring" element="echo:onestrings" /> </header> <body> <part name="onestring" element="echo:onestrings" /> <part name="twostrings" element="echo:twostrings" /> </body> </output> </operation> </specification>
it generates the following output:
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="urn:testing:echo" xmlns:wsoap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl-dtc="http://www.izforge.com/soap-dtc/wsdl-dtc" xmlns:echo="urn:testing:echo" targetNamespace="urn:testing:echo"> <wsdl:types> <xsd:schema targetNamespace="urn:testing:echo"> <xsd:element name="onestring"> <xsd:complexType> <xsd:sequence> <xsd:element name="str" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="twostrings"> <xsd:complexType> <xsd:sequence> <xsd:element name="str1" type="xsd:string"/> <xsd:element name="str2" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </wsdl:types> <wsdl:message name="EchoIn"> <wsdl:part name="twostrings" element="echo:twostrings"/> </wsdl:message> <wsdl:message name="EchoOut"> <wsdl:part name="onestring" element="echo:onestrings"/> <wsdl:part name="twostrings" element="echo:twostrings"/> </wsdl:message> <wsdl:portType name="EchoPortType"> <wsdl:operation name="Echo"> <wsdl:input message="tns:EchoIn"/> <wsdl:output message="tns:EchoOut"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="EchoSoap12Binding" type="tns:EchoPortType"> <wsoap12:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/> <wsdl:operation name="Echo"> <wsoap12:operation soapAction="echo" soapActionRequired="true" style="document"/> <wsdl:input> <wsoap12:body use="literal"/> </wsdl:input> <wsdl:output> <wsoap12:header part="onestring" use="literal"/> <wsoap12:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="Echo"> <wsdl:port name="EchoSoap12" binding="tns:EchoSoap12Binding"> <wsoap12:address location="http://localhost:8080/test/endpoint"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
Set-up your servlets container to deploy HttpSoapServiceServlet as any other servlet. To use the BSF-based scripting set-up, you will need to define the bsf.setup.resource servlet init parameter, and the value needs to be the path to a Java resource. This resource has to be a properties file that contains the parameters to set-up BSF, and the script to run at configuration time.
In this example, we use the Beanshell scripting language, which is familiar to any Java developer since it accepts classic Java constructs. The instructions will be similar if you use Groovy, JRuby, Jython, ... Let’s create bsf-setup.properties:
language = beanshell engine = bsh.util.BeanShellBSFEngine extensions = bsh,java resource = bsf-setup.bsh
The parameters meanings are pretty obvious:
language is the BSF name of the scripting languageengine is the BSF engine classextensions is the list of valid file extensions for the languageresource is the resource path to the script.Our script does the following:
import com.izforge.soapdtc.SoapMessage; import com.izforge.soapdtc.servlet.IServiceEndpointHandler; import org.dom4j.QName; daService = new IServiceEndpointHandler() { public SoapMessage handleRequestResponse(SoapMessage inMessage, String soapAction) { return inMessage; } public SoapMessage handleSollicitResponse(String soapAction) { return null; } public boolean isSoapHeaderHandled(QName headerQName) { return false; } }; servlet.addPathInfoMapping("/da-service", daService);
We have instanciated an anonymous endpoint handler, but you can of course instanciate any class you want as BSF makes it transparent for hosted scripting languages. Also note that in any language, we define an object named servlet which is the instance of the servlet to set-up.
If you don’t want to use the scripting-based configuration feature, you can simple override HttpSoapServiceServlet in a new subclass that you will deploy to your application server.
HttpSoapServiceServletinit() methodinit(), use the configuration methods as you would do from a scripting language (ex: addPathInfoMapping(...) to register an endpoint handler)
For the most basic services, the project can consist of just one subclass of HttpSoapServiceServlet that implements the IServiceEndpointHandler interface...