JAX-WS Web Services Without Java EE Containers Blog

Version 2


    One justification for including JAX-WS 2.0 as part of Java SE 6.0 instead of Java EE 5 is that web service delivery with JAX-WS 2.0 does not require a servlet or EJB container. This makes HTTP more or less an equal peer of RMI as an intrinsic protocol for distributed computing on the Java platform, and further extends the reach of web services.

    This article shows how to use the JAX-WS class javax.xml.ws.Endpointto easily publish web services, and demonstrates how to configure the service runtime environment in this situation and use a customized HTTP server.

    Run Web Services in Java SE 6.0

    We will work through an example to demonstrate how to develop and run a simple web service within the Java SE 6.0 environment.

    The following shows the WSDL for this example,StockQuoteService.wsdl.

    <?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://service.stockquote.jaxws.company.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://service.stockquote.jaxws.company.com/" name="StockQuoteService"> <types> <xsd:schema> <xsd:import schemaLocation="StockQuoteService.xsd" namespace="http://service.stockquote.jaxws.company.com/"></xsd:import> </xsd:schema> </types> <message name="getQuote"> <part element="tns:getQuote" name="parameters"></part> </message> <message name="getQuoteResponse"> <part element="tns:getQuoteResponse" name="parameters"></part> </message> <portType name="StockQuote"> <operation name="getQuote"> <input message="tns:getQuote"></input> <output message="tns:getQuoteResponse"></output> </operation> </portType> <binding name="StockQuotePortBinding" type="tns:StockQuote"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"></soap:binding> <operation name="getQuote"> <soap:operation soapAction=""></soap:operation> <input> <soap:body use="literal"></soap:body> </input> <output> <soap:body use="literal"></soap:body> </output> </operation> </binding> <service name="StockQuoteService"> <port name="StockQuotePort" binding="tns:StockQuotePortBinding"> <soap:address location="http://localhost:1970/StockQuote/StockQuoteService"></soap:address> </port> </service> </definitions> 

    This WSDL is of the Document/Literal/Wrapped style, and it defines one single operation, getQuote, in the single port type StockQuote. The XML schema file referred to in the WSDL, StockQuoteService.xsd, is as follows:

    <?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:tns="http://service.stockquote.jaxws.company.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://service.stockquote.jaxws.company.com/" version="1.0"> <xs:element name="getQuote" type="tns:getQuote"></xs:element> <xs:element name="getQuoteResponse" type="tns:getQuoteResponse"></xs:element> <xs:complexType name="getQuote"> <xs:sequence> <xs:element name="arg0" type="xs:string" minOccurs="0"></xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="getQuoteResponse"> <xs:sequence> <xs:element name="return" type="xs:double"></xs:element> </xs:sequence> </xs:complexType> </xs:schema> 

    To implement the web service with JAX-WS according to such contracts, we need to implement the getQuote operation specified in the StockQuote port type. Here is the simple implementation class we are going to work with in this article, StockQuoteImpl.java:

    @WebService(name = "StockQuote", serviceName = "StockQuoteService") public class StockQuoteImpl { @WebMethod(operationName = "getQuote") public double getQuote(String ticker) { double result = 0.0; if (ticker.equals("MHP")) { result = 50.0; } else if (ticker.equals("IBM")) { result = 83.0; } return result; } } 

    Note that we annotate this class with several annotations defined in JSR 181, which JAX-WS implementations are required to support. To generate the artifacts required by the service runtime, we need theapt tool (actually the corresponding Ant task), which is part of the JDK since 5.0, to process those annotations. The target for this step, as defined in build.xml, is as follows:

    <target name="build-service" depends="setup"> <apt fork="true" debug="${debug}" verbose="${verbose}" destdir="${build.classes.home}" sourcedestdir="${build.classes.home}" sourcepath="${basedir}/src"> <classpath> <path refid="jaxws.classpath"/> <pathelement location="${basedir}/src"/> </classpath> <option key="r" value="${build.home}"/> <source dir="${basedir}/src"> <include name="**/service/*.java"/> </source> </apt> </target> 

    After compiling the service implementation class and all the generated classes, we have completed building the example web service.

    If we are going to run this web service in, say, a servlet container such as Tomcat, we need to perform the following further steps, at a minimum:

    1. Create a proprietary web service deployment descriptor (for example, sun-jaxws.xml) and the web application deployment descriptor (web.xml).
    2. Package the service implementation class, theapt-generated artifacts, and those descriptors into a .war archive.
    3. Deploy the .war archive into the servlet container.

    For an example of this, see my earlier article, " Asynchronous Web Service Invocation with JAX-WS 2.0."

    However, with Java SE 6.0, and with the help ofjavax.xml.ws.Endpoint from JAX-WS, we do not need a servlet container to run this web service. Instead, we can come up a bootstrap class (which can be the same service implementation class, if we prefer) that calls one of thepublish(...) methods in the Endpointclass. Here are the essential lines of code in the bootstrap class we will use, StockQuoteService.java:

    public class StockQuoteService { private Endpoint endpoint = null; ... public StockQuoteService() { endpoint = Endpoint.create(new StockQuoteImpl()); } ... private void publish() { ... endpoint.publish("http://localhost:1970/StockQuote/StockQuoteService"); } ... public static void main(String[] args) { StockQuoteService sqs = new StockQuoteService(); ... sqs.publish(); System.out.println("StockQuoteService is open for business at http://localhost:1970/StockQuote/StockQuoteService!"); ... } } 

    We can run this class from the command line, or execute thepublish-service target in build.xml, to get the web service up and running--no servlet container or EJB container needed.

    The web service client is the same, whether the web service is running in a container or not. Here is a simple one for illustration purposes:

    public class StockQuoteClient { public static void main(String[] args) { try { StockQuote port = new StockQuoteService().getStockQuotePort(); System.out.println("\nInvoking getQuote():"); //client code for service published with the default HTTP server String ticker1 = "MHP"; double result1 = port.getQuote(ticker1); System.out.printf("The stock price of %s is $%f.\n", ticker1, result1); ... } catch (Exception e) { e.printStackTrace(); } } } 

    To actually (compile and) invoke the web service with this client, we need to use the wsimport tool to generate the artifacts required by the client runtime. But this is a standard step when developing with JAX-WS RI. Interested readers may have a look at the target generate-client inbuild.xml to see how to accomplish this.

    The Ant task supplied in build.xml for invoking the client is run-client.

    Configure the Service Runtime

    As demonstrated in the previous section, running a simple web service in Java SE 6.0 environment is straightforward. Despite this simplicity, we still have certain control over the web service runtime. This section will show two examples.

    One is to use a customized executor to manage threads handling client requests. The Endpoint class provides a pair of methods, getExecutor() andsetExecutor(...), for this purpose.

    As an example, we can create an executor that prints out the execution time for each invocation: TimingTheadPool.java(see the example application source in the Resources section). The code is adapted from an example from Java Concurrency in Practice by Brian Goetz. To use this executor for handling requests to the simple web service we created in previous section, we only need to add the following lines to the bootstrap class, StockQuoteService.java:

    private TimingThreadPool executor = new TimingThreadPool(); ... private void publish() { 
    endpoint.setExecutor(executor); ... endpoint.publish("http://localhost:1970/StockQuote/StockQuoteService"); } 

    On my desktop, with the service runtime configured with this executor, the service-side console prints two lines whenever therun-client target is run. Here's an example:

    [java] $$$$$$$$$$$$$$$ Runnable com.sun.xml.internal.ws.transport.http.server.WSHttpHandler$HttpHandlerRunnable@160a26f starts in Thread[pool-1-thread-1,5,main]. [java] $$$$$$$$$$$$$$$ Runnable com.sun.xml.internal.ws.transport.http.server.WSHttpHandler$HttpHandlerRunnable@160a26f ends after 5618744ns. 

    The Endpoint class also provides a pair of methods for working with the javax.xml.ws.Binding interface:getBinding() and setBinding(...). With these, we can take advantage of the powerful handler framework in JAX-WS for web services in the Java SE 6.0 environment.

    As an example, we create a SOAPHandler that logs all SOAP request and response messages--EnvelopeLoggingSOAPHandler.java. To plug this handler into the service runtime for the example web service, we can add the following lines in the publish() method ofStockQuoteService.java:

    Binding binding = endpoint.getBinding(); List<Handler> handlerChain = new LinkedList<Handler>(); handlerChain.add(new EnvelopeLoggingSOAPHandler()); binding.setHandlerChain(handlerChain); 

    Republishing the service, and invoking the client, we can see the log messages in the service-side console as follows:

    [java] ------------------------------------ [java] In SOAPHandler EnvelopeLoggingSOAPHandler:handleMessage() [java] direction = inbound [java] <?xml version="1.0" ?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://service.stockquote.jaxws.company.com/"><soapenv:Body><ns1:getQuote><arg0>MHP</arg0></ns1:getQuote></soapenv:Body></soapenv:Envelope> [java] Exiting SOAPHandler EnvelopeLoggingSOAPHandler:handleMessage() [java] ------------------------------------ [java] ------------------------------------ [java] In SOAPHandler EnvelopeLoggingSOAPHandler:handleMessage() [java] direction = outbound [java] <?xml version="1.0" ?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://service.stockquote.jaxws.company.com/"><soapenv:Body><ns1:getQuoteResponse><return>50.0</return></ns1:getQuoteResponse></soapenv:Body></soapenv:Envelope> [java] Exiting SOAPHandler EnvelopeLoggingSOAPHandler:handleMessage() [java] ------------------------------------ [java] ------------------------------------ [java] In Handler EnvelopeLoggingSOAPHandler:close() [java] Exiting Handler EnvelopeLoggingSOAPHandler:close() [java] ------------------------------------ 

    EnvelopingLoggingSOAPHandler.java is taken from the example application of an article I wrote recently, and that article provides a comprehensive discussion of the handler framework in JAX-WS.

    With the Binding interface available from theEndpoint class, we can also configure other aspects of the service runtime; for example, enabling/disabling MTOM forSOAPBinding, etc.

    Take Control of the Underlying HTTP Server

    The class javax.xml.ws.Endpoint is defined in the JAX-WS 2.0 specification, and therefore all implementations should support it.

    In this section, we will demonstrate how we can further take control of the underlying HTTP server when working with theEndpoint class in JDK 6.0, which uses a lightweight HTTP server supplied by Sun. The API for working this server is defined in the package com.sun.net.httpserver.

    When publishing web services with the Endpointclass, we can actually provide a customized HTTPServerinstance to support the HTTP communications between clients and the service. The method in Endpoint that makes this possible is:

    abstract void publish(Object serverContext) 

    What is not obvious from this method signature is that the input parameter serverContext can be acom.sun.net.httpserver.HTTPContext. With this knowledge, it is easy to see, by looking at the APIs incom.sun.net.httpserver, that we can control several very useful aspects of the HTTP communications for web services published by the Endpoint class, such as applying filters and using a customized executor managing threads handling HTTP requests, or using a customized HTTP handler, etc.

    As an example, we will demonstrate how to enable HTTP basic authentication for web services running in Java SE 6.0.

    For this purpose, we will first create a HTTPServerinstance (see the server() method inStockQuoteService.java):

    private HttpServer server = null; ... server = HttpServer.create(new InetSocketAddress(1970), 5); server.start(); 

    And, then from this server instance, we can create theHTTPContext we will use, by providing the context path (to which request URLs will be translated):

    context = server.createContext("/StockQuote/StockQuoteService"); 

    To enable basic authentication for this HTTP context, we need an authenticator. An easy way to achieve this is to extend the support class com.sun.net.httpserver.BasicAuthenticator. The simple authenticator we will create isTestBasicAuthenticator.java. It only authenticates the user yyang with the password #$$1x5Y7z. (This specific username/password pair is just an example and is of no significance by itself!)

    A single line of code provides basic authentication for the previously created HTTP context with aTestBasicAuthenticator instance:

    context.setAuthenticator(new TestBasicAuthenticator("test")); 

    As the last step, we need to configure the Endpointto use our own HTTP server instance and publish the example web service to the previously created HTTP context.


    To run this web service, we can invoke thestart-server target in build.xml.

    Running the original client, we will see a message in the client side console indicating that the request is unauthorized:

    [java] Invoking getQuote(): [java] 
    javax.xml.ws.WebServiceException: request requires HTTP authentication: Unauthorized [java] at com.sun.xml.internal.ws.util.SOAPConnectionUtil.getSOAPMessage(SOAPConnectionUtil.java:83) [java] at com.sun.xml.internal.ws.encoding.soap.client.SOAPXMLDecoder.toSOAPMessage(SOAPXMLDecoder.java:102) [java] at com.sun.xml.internal.ws.protocol.soap.client.SOAPMessageDispatcher.receive(SOAPMessageDispatcher.java:440) ... 

    To make the call succeed, we need to modify the client a little bit so that it can send the user/name password pair. This code snippet follows the standard approach in JAX-WS for sending client credentials:

    BindingProvider bp = (BindingProvider)port; Map<String,Object> map = bp.getRequestContext(); map.put(BindingProvider.USERNAME_PROPERTY, "yyang"); map.put(BindingProvider.PASSWORD_PROPERTY,"#$$1x5Y7z"); //map.put(BindingProvider.PASSWORD_PROPERTY,"password"); String ticker2 = "IBM"; double result2 = port.getQuote(ticker2); System.out.printf("The stock price of %s is $%f.\n", ticker2, result2); 

    We can also use a different username/password pair other thanyyang/#$$1x5Y7z to see how an authentication failure behaves.


    In use cases that do not require the full power of a commercial HTTP server and servlet or EJB container, the Endpointclass provides a convenient mechanism for components in software or systems in tightly controlled environment to easily communicate through web services. A second use of this mechanism is to prototype and to develop production web services to be finally deployed in Java EE containers. We can also use it to mock or simulate web services to decouple client development and test from service delivery. In summary, I believe this tool can be very helpful for organizations adopting SOA, not only simplifying some production system configuration, but also facilitating the service delivery lifecycle.