Version 1
    Since 3.0.1 (Native)

    Scenario

    This sample targets those business scenarios in which web services expose functionalities requiring long running processes. Service consumers send the message requests and don't want to actively wait for the response or use polling mechanism to find out whether their response is ready.

     

    The org.jboss.test.ws.jaxws.samples.dar sample is about a Dial-a-ride optimization service. The Dial-a-Ride (DaR) is an emerging transport system, in which a fleet of vehicles, without fixed routes and schedules, carries people from the desired pickup point to the desired delivery point, during a pre-specified time interval. Passengers ring the service provider office the day before they want to travel and book their ride. An optimization system works out the route to be as efficient as possible, so that for instance people with similar travel requests are collected by the same vehicle. Since the problem can be modeled as an NP-hard routing and scheduling problem and exact approaches are insufficient for real-life problems, our software company decides to provide a service that solves the DaR problem for transportation companies' requests through complex custom heuristic algorithms. And of course, this takes some time.

     

    The service provider

    We start defining and implementing the DaR service provider (bottom-up approach):

    @Stateless
    @WebService(name = "DarEndpoint",
                targetNamespace = "http://org.jboss.ws/samples/dar",
                serviceName = "DarService")
    @SOAPBinding(style = SOAPBinding.Style.RPC,
                 use = SOAPBinding.Use.LITERAL)
    @SecurityDomain("JBossWS")
    @WebContext(contextRoot="/dar",
                urlPattern="/*",
                authMethod="BASIC",
                transportGuarantee="NONE",
                secureWSDLAccess=false)
    public class DarEndpoint
    {
       @WebMethod(operationName = "process", action = "http://org.jboss.test.ws.jaxws.samples.dar/action/processIn")
       public DarResponse process(DarRequest request)
       {
          DarProcessor processor = new DarProcessor();
          return processor.process(request);
       }
    }
    

    To keep this simple we use a SLSB endpoint and the rpc/literal style/use, of course you can choose POJO endpoint and the better document style as well. A BASIC authorization method is used to achieve request accountability; for ease we use the default JBossWS security domain, of course you would need to authenticate transport companies through your own login module as well as keep track of the requests for billing reasons. The DarProcessor performs the actual DaR problem solution and optimization. DarRequest and DarResponse, along with the referenced classes, define the request and response model, with the pickup and delivery point on the map, the departure/arrival times, etc. Every further detail (for instance about quality of service) is not considered to keep the sample simple.

    You can build the sample, take a look at the simple service endpoint in jaxws-samples-dar.jar and deploy it.

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar.jar
         0 Fri Feb 01 17:55:20 CET 2008 META-INF/
       106 Fri Feb 01 17:55:18 CET 2008 META-INF/MANIFEST.MF
         0 Thu Jan 10 21:41:02 CET 2008 org/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/
       789 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Bus.class
      1643 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarEndpoint.class
      4231 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarProcessor.class
      1585 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarRequest.class
      1340 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarResponse.class
      1085 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Route.class
      1314 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/ServiceRequest.class
       834 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Stop.class
    

    Assume your bind address is localhost.localdomain:8080; the JBossWS stack will deploy the service and publish the wsdl contract at http://localhost.localdomain:8080/dar?wsdl.

     

    JAX-WS Asynchronous API

    The transportation company client

    Once the first implementation of the service provider is ready, we generate a simple client to test it. This can be easily achieved with the wsconsume script:

    wsconsume.sh -k -p org.jboss.test.ws.jaxws.samples.dar.generated http://localhost.localdomain:8080/dar?wsdl

    This outputs the endpoint interface that our client has to use, as well as all the classes for requests/responses. Since we need to perform asynchronous invocations using the JAX-WS async API, we a need method with the same name of the existing one followed by Async and returning a javax.xml.ws.Response:

    @WebService(name = "DarEndpoint", targetNamespace = "http://org.jboss.ws/samples/dar")
    @SOAPBinding(style = SOAPBinding.Style.RPC)
    public interface DarEndpoint {
    
       @WebMethod(operationName = "process")
       public Response<DarResponse> processAsync(@WebParam(name = "arg0", partName = "arg0") DarRequest arg0);
       
       @WebMethod(action = "http://org.jboss.test.ws.jaxws.samples.dar/action/processIn")
       @WebResult(partName = "return")
       public DarResponse process(@WebParam(name = "arg0", partName = "arg0") DarRequest arg0);
    }
    

    Then we implement the client, invoking both the method to see the different behaviors:

    public class Client
    {
       protected DarEndpoint endpoint;
       
       public Client(URL url)
       {
          DarService service = new DarService(url, new QName("http://org.jboss.ws/samples/dar", "DarService"));
          endpoint = service.getDarEndpointPort();
          ClientHelper.setUsernamePassword((BindingProvider)endpoint, "kermit", "thefrog");
       }
       
       public void run(boolean asynch) throws Exception
       {
          DarRequest request = ClientHelper.getRequest();
          System.out.println(new Date() + " Sending request...");
          DarResponse darResponse;
          if (asynch)
          {
             Response<DarResponse> response = endpoint.processAsync(request);
             System.out.println("Doing something interesting in the mean time... ;-) ");
             darResponse = response.get();
          }
          else
          {
             darResponse = endpoint.process(request);
          }
          System.out.println(new Date() + " Response received: "+darResponse);
          ClientHelper.printResponse(darResponse);
       }
       
       public static void main(String[] args)
       {
          try
          {
             if (args.length == 1)
             {
                Client client = new Client(new URL(args[0]));
                System.out.println("* Synchronous invocation: ");
                client.run(false);
                System.out.println("\n* Asynchronous invocation: ");
                client.run(true);
             }
             else
             {
                System.out.println("Client usage:");
                System.out.println("wsrunclient.sh -classpath client.jar org.jboss.test.ws.jaxws.samples.dar.Client http://host:port/dar?wsdl");
             }
          }
          catch (Exception e)
          {
             e.printStackTrace();
          }
       }
    }
    

    The ClientHelper contains utility methods for generating sample requests and outputting responses to the log. Here is how the client package looks like:

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-client.jar 
         0 Fri Feb 01 17:55:20 CET 2008 META-INF/
       106 Fri Feb 01 17:55:18 CET 2008 META-INF/MANIFEST.MF
         0 Thu Jan 10 21:41:02 CET 2008 org/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/
      3493 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Client.class
      4407 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/ClientHelper.class
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/
      1017 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/Bus.class
      1447 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarEndpoint.class
      1619 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarRequest.class
      1389 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarResponse.class
      1597 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarService.class
      1681 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/ObjectFactory.class
      1267 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/Route.class
      1589 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/ServiceRequest.class
      1210 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/Stop.class
       282 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/package-info.class

    Running the client

    Now you can run the client and see whether we got the expected behavior:

    wsrunclient.sh -classpath jaxws-samples-dar-client.jar org.jboss.test.ws.jaxws.samples.dar.Client http://localhost.localdomain:8080/dar?wsdl
    

    As you can see from the output below, both the standard and asynchronous invocation took some time (5 sec):

    Synchronous invocation: 
    Mon Feb 04 15:10:44 CET 2008 Sending request...
    Mon Feb 04 15:10:50 CET 2008 Response received: org.jboss.test.ws.jaxws.samples.dar.generated.DarResponse@1389b3f
    Bus1: 778 118 734 341 
    Bus0: 298 142 788 876 
    
    Asynchronous invocation: 
    Mon Feb 04 15:10:50 CET 2008 Sending request...
    Doing something interesting in the mean time... ;-) 
    Mon Feb 04 15:10:55 CET 2008 Response received: org.jboss.test.ws.jaxws.samples.dar.generated.DarResponse@1265109
    Bus1: 169 863 
    Bus0: 951 28 244 903 226 159 
    

    The use of the async invocation does not block the client and allows it to perform something else before waiting for the result from the server. We could have also used an AsyncHandler as showed in the asynchronous simple test case.

     

    WS-Addressing

    Even if the client can do something else while the server handles the request, it would be better if the response could be received by another instance, leaving the client completely free go on with its business. WS-Addressing provides means of specifying which address the response has to be sent to. This is performed through the replyTo property.

    Addressing service provider

    For this reason we create a new AddressingEndpoint:

    @Stateless
    @WebService(name = "DarEndpoint",
                targetNamespace = "http://org.jboss.ws/samples/dar",
                serviceName = "DarService")
    @SOAPBinding(style = SOAPBinding.Style.RPC,
                 use = SOAPBinding.Use.LITERAL)
    @SecurityDomain("JBossWS")
    @WebContext(contextRoot="/dar",
                urlPattern="/*",
                authMethod="BASIC",
                transportGuarantee="NONE",
                secureWSDLAccess=false)
    @EndpointConfig(configName = "Standard WSAddressing Endpoint")
    public class DarAddressingEndpoint
    {
       @Resource
       private WebServiceContext ctx;
       private static Logger log = Logger.getLogger(DarAddressingEndpoint.class);
       
       @WebMethod(operationName = "process", action = "http://org.jboss.test.ws.jaxws.samples.dar/action/processIn")
       @Action(input = "http://org.jboss.test.ws.jaxws.samples.dar/action/processIn", output = "http://org.jboss.test.ws.jaxws.samples.dar/action/processOut")
       public DarResponse process(DarRequest request)
       {
          DarProcessor processor = new DarProcessor();
          AddressingProperties props = (AddressingProperties)ctx.getMessageContext().get(JAXWSAConstants.SERVER_ADDRESSING_PROPERTIES_INBOUND);
          if (props!=null && props.getReplyTo()!=null)
          {
             System.out.println(props.getReplyTo().getAddress().getURI());
          }
          return processor.process(request);
       }
    }
    

    As you can see, we used the proper endpoint config and specified the input and output action names. The WS-Addressing implementation write/read the replyTo property to/from the SOAP header, thus the WebServiceContext allows us to retrieve the replyTo address to be used for the current invocation.

     

    Addressing client

    In order to provide the right replyTo address, the client side too needs to be changed:

    public class AddressingClient
    {
       protected DarEndpoint endpoint;
       
       private static AddressingBuilder BUILDER;
       private static final String WSA_TO = "http://org.jboss.test.ws.jaxws.samples.dar/server";
       private static final String WSA_ACTION = "http://org.jboss.test.ws.jaxws.samples.dar/action/processIn";
       private static final String WSA_ACTION_ONEWAY = "http://org.jboss.test.ws.jaxws.samples.dar/action/onewayProcessIn";
    
       static
       {
          BUILDER = AddressingBuilder.getAddressingBuilder();
       }
       
       public AddressingClient(URL url)
       {
          DarService service = new DarService(url, new QName("http://org.jboss.ws/samples/dar", "DarService"));
          endpoint = service.getDarEndpointPort();
          ((StubExt)endpoint).setConfigName("Standard WSAddressing Client");
          ClientHelper.setUsernamePassword((BindingProvider)endpoint, "kermit", "thefrog");
       }
       
       public void run(boolean asynch) throws Exception
       {
          configureAddressingProperties((BindingProvider)endpoint, WSA_ACTION, WSA_TO, "http://localhost:8080/dar-client/replyTo");
          DarRequest request = ClientHelper.getRequest();
          System.out.println(new Date() + " Sending request...");
          if (asynch)
          {
             Response<DarResponse> resp = endpoint.processAsync(request);
             System.out.println("Doing something interesting in the mean time... ;-) ");
             resp.get();
          }
          else
          {
             endpoint.process(request);
          }
          //don't care about the response: it is null since the real one went to the replyTo address
          System.out.println(new Date() + " Done.");
       }
       
       private static void configureAddressingProperties(BindingProvider bp, String wsaAction, String wsaTo, String replyTo) throws URISyntaxException
       {
          AddressingProperties requestProps = AddressingClientUtil.createDefaultProps(wsaAction, wsaTo);
          AttributedURI messageId = AddressingClientUtil.createMessageID();
          System.out.println("Sent message ID: " + messageId.getURI());
          requestProps.setMessageID(messageId);
          requestProps.setReplyTo(BUILDER.newEndpointReference(new URI(replyTo)));
          bp.getRequestContext().put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES_OUTBOUND, requestProps);
       }
       
       public static void main(String[] args)
       {
          try
          {
             if (args.length == 1)
             {
                AddressingClient client = new AddressingClient(new URL(args[0]));
                System.out.println("* Synchronous invocation: ");
                client.run(false);
                System.out.println("\n* Asynchronous invocation: ");
                client.run(true);
             }
             else
             {
                System.out.println("AddressingClient usage:");
                System.out.println("wsrunclient.sh -classpath AddressingClient.jar org.jboss.test.ws.jaxws.samples.dar.AddressingClient http://host:port/dar?wsdl");
             }
          }
          catch (Exception e)
          {
             e.printStackTrace();
          }
       }
    }
    

    You can see in the configureAddressingProperties(...) method how the WS-Addressing properties are configured. The Standard WSAddressing Client client config needs also to be specified so that the WS-Addressing stack extension is used.

     

    The reply sink

    Thanks to the WS-Addressing implementation, the server will automatically send the response to the specified address. Of course we need something to receive it; for ease here we have a simple servlet dumping everything to the logs:

    public class ReplyToServlet extends HttpServlet {
    
       protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
          dump(httpServletRequest, httpServletResponse);
       }
    
       protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
          dump(httpServletRequest, httpServletResponse);
       }
    
       private void dump(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
          System.out.println("ReplyTo sink:");
    
          try {
             BufferedReader reader = httpServletRequest.getReader();
             String inputLine;
    
             while ((inputLine = reader.readLine()) != null) {
                System.out.println(inputLine);
             }
             reader.close();
          } catch (IOException e) {
             e.printStackTrace();
          }
          
          httpServletResponse.setStatus(200);
       }
    }

     

    Oneway invocations

    Having splitted the client is actually not enough. The reason is that even if the response is not received by the AddressingClient, with the current JBossWS stack implementation, the underlying connection between the ws consumer and provider is nevertheless closed once the latter has sent the response to the ReplyToServlet and this is a resource waste. Moreover, on the server side, since a new thread is spanned for each invocation, the long running process could cause the system to reach the max number of thread/http connections allowed at the same time.

    For this reason, we split the service too. A oneway invocation is performed, then on the server side the collected requests are stored into a queue. A message driven bean consumes the queue, performs the long running optimization and finally replies to the address the client specified. The following paragraphs go deeper into the details.

     

    Response service implementation

    To get what has just been summarized, the server has to send messages whose schema has to be somewhere declared: we'll use a top-down development strategy. Here is the hand-coded reply service wsdl contract:

    <definitions name="DarReplyService" targetNamespace="http://org.jboss.ws/samples/dar" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://org.jboss.ws/samples/dar" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <types>
      <xs:schema targetNamespace="http://org.jboss.ws/samples/dar" version="1.0" xmlns:tns="http://org.jboss.ws/samples/dar" xmlns:xs="http://www.w3.org/2001/XMLSchema">
       <xs:complexType name="serviceRequest">
        <xs:sequence>
         <xs:element minOccurs="0" name="from" type="tns:stop"/>
         <xs:element minOccurs="0" name="id" type="xs:string"/>
         <xs:element name="people" type="xs:int"/>
         <xs:element minOccurs="0" name="to" type="tns:stop"/>
        </xs:sequence>
       </xs:complexType>
       <xs:complexType name="stop">
        <xs:sequence>
         <xs:element minOccurs="0" name="node" type="xs:int"/>
         <xs:element minOccurs="0" name="time" type="xs:dateTime"/>
        </xs:sequence>
       </xs:complexType>
       <xs:complexType name="darResponse">
        <xs:sequence>
         <xs:element maxOccurs="unbounded" minOccurs="0" name="routes" nillable="true" type="tns:route"/>
         <xs:element maxOccurs="unbounded" minOccurs="0" name="unservedRequests" nillable="true" type="tns:serviceRequest"/>
        </xs:sequence>
       </xs:complexType>
       <xs:complexType name="route">
        <xs:sequence>
         <xs:element minOccurs="0" name="busId" type="xs:string"/>
         <xs:element maxOccurs="unbounded" minOccurs="0" name="stops" nillable="true" type="tns:stop"/>
        </xs:sequence>
       </xs:complexType>
      </xs:schema>
     </types>
     <message name="DarReplyEndpoint_receive">
      <part name="arg0" type="tns:darResponse"/>
     </message>
     <portType name="DarReplyEndpoint">
      <operation name="receive">
       <input message="tns:DarReplyEndpoint_receive"/>
      </operation>
     </portType>
     <binding name="DarReplyEndpointBinding" type="tns:DarReplyEndpoint">
      <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
      <operation name="receive">
       <soap:operation soapAction="http://org.jboss.test.ws.jaxws.samples.dar/action/receiveIn"/>
       <input>
        <soap:body namespace="http://org.jboss.ws/samples/dar" use="literal"/>
       </input>
      </operation>
     </binding>
     <service name="DarReplyService">
      <port binding="tns:DarReplyEndpointBinding" name="DarReplyEndpointPort">
       <soap:address location="http://localhost.localdomain:8080/dar-client/replyService"/>
      </port>
     </service>
    </definitions>
    

    The optimization system acts as a client here: it owns the schema and gets the interface through the wsconsume script:

    wsconsume.sh -k -p org.jboss.test.ws.jaxws.samples.dar.generated.reply reply.wsdl
    

    The transportation companies play the server role instead, thus they implement the generated endpoint interface:

    @WebService(name = "DarReplyEndpoint", targetNamespace = "http://org.jboss.ws/samples/dar")
    @SOAPBinding(style = SOAPBinding.Style.RPC)
    public interface DarReplyEndpoint {
    
    
        /**
         * 
         * @param arg0
         */
        @WebMethod(action = "http://org.jboss.test.ws.jaxws.samples.dar/action/receiveIn")
        @Oneway
        public void receive(
            @WebParam(name = "arg0", partName = "arg0")
            DarResponse arg0);
    
    }
    

    Here is the endpoint implementation: as for the request service, the addressing configuration is specified as well as the action for the oneway operation.

    @WebService(name = "DarReplyEndpoint",
                targetNamespace = "http://org.jboss.ws/samples/dar",
                endpointInterface = "org.jboss.test.ws.jaxws.samples.dar.generated.reply.DarReplyEndpoint",
                wsdlLocation = "/WEB-INF/wsdl/reply.wsdl",
                serviceName = "DarReplyService")
    @SOAPBinding(style = Style.RPC,
                 use = Use.LITERAL)
    @EndpointConfig(configName = "Standard WSAddressing Endpoint")
    public class DarReplyEndpointImpl implements DarReplyEndpoint
    {
       @Resource
       private WebServiceContext ctx;
       private static Logger log = Logger.getLogger(DarReplyEndpointImpl.class);
       
       @WebMethod(action = "http://org.jboss.test.ws.jaxws.samples.dar/action/receiveIn")
       @Oneway
       @Action(input = "http://org.jboss.test.ws.jaxws.samples.dar/action/receiveIn")
       public void receive(DarResponse arg0)
       {
          AddressingProperties props = (AddressingProperties)ctx.getMessageContext().get(JAXWSAConstants.SERVER_ADDRESSING_PROPERTIES_INBOUND);
          log.info("Result received; relationship message id: " + props.getRelatesTo()[0].getID());
          List<Route> routes = arg0.getRoutes();
          for (Route route : routes)
          {
             log.info(route.getBusId() + ": ");
             StringBuilder sb = new StringBuilder();
             for (Stop stop : route.getStops())
             {
                sb.append(stop.getNode() + " ");
             }
             log.info(sb.toString());
          }
       }
    }

    Request service improvements

    Finally the request service endpoint needs a new oneway operation method:

       @WebMethod(operationName = "onewayProcess", action = "http://org.jboss.test.ws.jaxws.samples.dar/action/onewayProcessIn")
       @Action(input = "http://org.jboss.test.ws.jaxws.samples.dar/action/onewayProcessIn")
       @Oneway
       public void onewayProcess(DarRequest request)
       {
          QueueSession queueSession =null;
          QueueSender sender = null;
          try {
             InitialContext context = new InitialContext();
             QueueConnectionFactory connectionFactory = (QueueConnectionFactory)context.lookup("ConnectionFactory");
             QueueConnection con = connectionFactory.createQueueConnection();
             queueSession = con.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
             Queue queue = (Queue)context.lookup("queue/DarQueue");
             sender = queueSession.createSender(queue);
             AsyncProcessRequest asyncRequest = new AsyncProcessRequest();
             asyncRequest.setRequest(request);
             AddressingProperties props = (AddressingProperties)ctx.getMessageContext().get(JAXWSAConstants.SERVER_ADDRESSING_PROPERTIES_INBOUND);
             asyncRequest.setReplyTo(props.getReplyTo().getAddress().getURI());
             asyncRequest.setMessageId(props.getMessageID().getURI());
             ObjectMessage message = queueSession.createObjectMessage(asyncRequest);
             sender.send(message);
             log.info("AsyncProcessRequest sent...");
          } catch (Exception e) {
             throw new WebServiceException(e);
          } finally {
             try
             {
                sender.close();
             }
             catch(Exception e1) {}
             try
             {
                queueSession.close();
             }
             catch(Exception e1) {}
          }
          
       }
    

    As you can see this does a lookup of the local queue/DarQueue and sends a custom AsyncProcessRequest pojo. This way the invocation is served and the endpoint thread is ready to receive another request.

    The afore-mentioned queue is consumed by the following Message Driven bean:

    @MessageDriven( name="DarListener", activationConfig= {
          @ActivationConfigProperty(propertyName="destinationType",propertyValue="javax.jms.Queue"),
          @ActivationConfigProperty(propertyName="destination",propertyValue="queue/DarQueue")}
    )
    public class DarMessageBean implements MessageListener
    {
       private static Logger log = Logger.getLogger(DarMessageBean.class);
       private static final String WSA_ACTION = "http://org.jboss.test.ws.jaxws.samples.dar/action/receiveIn";
       
       public void onMessage(Message arg0)
       {
          try {
             ObjectMessage message = (ObjectMessage)arg0;
             AsyncProcessRequest asyncRequest = (AsyncProcessRequest)message.getObject();
             DarProcessor processor = new DarProcessor();
             DarResponse response = processor.process(asyncRequest.getRequest());
             
             //convert the response and send it to the client reply service
             org.jboss.test.ws.jaxws.samples.dar.generated.reply.DarResponse darResponse = ReplyConverter.convertResponse(response);
             String replyTo = asyncRequest.getReplyTo().toURL().toString();
             log.info("Response will be sent to: " + replyTo);
             QName serviceName = new QName("http://org.jboss.ws/samples/dar", "DarReplyService");
             Service service = Service.create(new URL(replyTo + "?wsdl"), serviceName);
             DarReplyEndpoint endpoint = (DarReplyEndpoint)service.getPort(DarReplyEndpoint.class);
             
             //setup addressing configuration and properties
             ((StubExt)endpoint).setConfigName("Standard WSAddressing Client");
             ((BindingProvider)endpoint).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, replyTo);
             AddressingProperties requestProps = AddressingClientUtil.createOneWayProps(WSA_ACTION, replyTo);
             requestProps.setMessageID(AddressingClientUtil.createMessageID());
             Relationship[] relationships = new Relationship[1];
             relationships[0] = new RelationshipImpl(asyncRequest.getMessageId());
             requestProps.setRelatesTo(relationships);
             ((BindingProvider)endpoint).getRequestContext().put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES_OUTBOUND, requestProps);
             
             endpoint.receive(darResponse);
             log.info("Response sent.");
          } catch (Exception e) {
             e.printStackTrace();
          }
       }
    }
    

    This basically delegates to the DarProcessor then invokes the transportation company endpoint. Please note that:

    • the Standard WSAddressing Client configuration is used so that reply message ID can be associated to the request message ID; this allows the transportation company to to link requests and responses;
    • the endpoint address is of course set at runtime according to the replyTo addressing property found in the request message;
    • a stupid ReplyConverter is used since the classes generated from the hand-coded wsdl contract differ from the server model ones.

     

    Addressing client changes

    On the transportation company side, we have a new AddressingClient's method for the oneway request service invocation:

    public void runOneway() throws Exception
       {
          configureAddressingProperties((BindingProvider)endpoint, WSA_ACTION_ONEWAY, WSA_TO, "http://localhost:8080/dar-client/replyService");
          DarRequest request = ClientHelper.getRequest();
          System.out.println(new Date() + " Sending request...");
          endpoint.onewayProcess(request);
          System.out.println(new Date() + " Done.");
       }
    
    public static void main(String[] args)
       {
          try
          {
             if (args.length == 1)
             {
                AddressingClient client = new AddressingClient(new URL(args[0]));
                System.out.println("* Synchronous invocation: ");
                client.run(false);
                System.out.println("\n* Asynchronous invocation: ");
                client.run(true);
                System.out.println("\n* Oneway invocation: ");
                client.runOneway();
             }
             else
             {
                System.out.println("AddressingClient usage:");
                System.out.println("wsrunclient.sh -classpath AddressingClient.jar org.jboss.test.ws.jaxws.samples.dar.AddressingClient http://host:port/dar?wsdl");
             }
          }
          catch (Exception e)
          {
             e.printStackTrace();
          }
       }

    Running the sample

    Before trying the new addressing version of this sample, please take a look at the three archives you'll use. The jaxws-samples-dar-addressing.jar contains the optimization system:

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-addressing.jar
         0 Fri Feb 01 17:55:20 CET 2008 META-INF/
       106 Fri Feb 01 17:55:18 CET 2008 META-INF/MANIFEST.MF
         0 Thu Jan 10 21:41:02 CET 2008 org/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/
      1175 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/AsyncProcessRequest.class
       789 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Bus.class
      5258 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarAddressingEndpoint.class
      4751 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarMessageBean.class
      4231 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarProcessor.class
      1585 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarRequest.class
      1340 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarResponse.class
      3747 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/ReplyConverter.class
      1085 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Route.class
      1314 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/ServiceRequest.class
       834 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Stop.class
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/
       847 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/DarReplyEndpoint.class
      1680 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/DarReplyService.class
      1425 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/DarResponse.class
      1336 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/ObjectFactory.class
      1291 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/Route.class
      1619 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/ServiceRequest.class
      1222 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/Stop.class
       288 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/reply/package-info.class
    

    The jaxws-samples-dat-addressing-client.jar is the first (request) part of the transportation companies software:

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-addressing-client.jar
         0 Fri Feb 01 17:55:20 CET 2008 META-INF/
       106 Fri Feb 01 17:55:18 CET 2008 META-INF/MANIFEST.MF
         0 Thu Jan 10 21:41:02 CET 2008 org/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/
      5859 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/AddressingClient.class
      4407 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/ClientHelper.class
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/
      1017 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/Bus.class
      1447 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarEndpoint.class
      1619 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarRequest.class
      1389 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarResponse.class
      1597 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/DarService.class
      1681 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/ObjectFactory.class
      1267 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/Route.class
      1589 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/ServiceRequest.class
      1210 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/Stop.class
       282 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/generated/package-info.class
    

    Finally, the jaxws-samples-dar-addressing-client.war is the second (response) part of the transportation company, including both the sink servlet and the DarReplyEndpointImpl for response receipt:

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-addressing-client.war 
         0 Fri Feb 01 17:55:20 CET 2008 META-INF/
       106 Fri Feb 01 17:55:18 CET 2008 META-INF/MANIFEST.MF
         0 Fri Feb 01 17:55:20 CET 2008 WEB-INF/
       941 Thu Jan 31 16:07:30 CET 2008 WEB-INF/web.xml
         0 Fri Feb 01 17:55:20 CET 2008 WEB-INF/classes/
         0 Thu Jan 10 21:41:02 CET 2008 WEB-INF/classes/org/
         0 Thu Jan 10 21:07:24 CET 2008 WEB-INF/classes/org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 WEB-INF/classes/org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 WEB-INF/classes/org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/
      3850 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/DarReplyEndpointImpl.class
      1612 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/ReplyToServlet.class
         0 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/
         0 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/
       847 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/DarReplyEndpoint.class
      1680 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/DarReplyService.class
      1425 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/DarResponse.class
      1336 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/ObjectFactory.class
      1291 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/Route.class
      1619 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/ServiceRequest.class
      1222 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/Stop.class
       288 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/reply/package-info.class
       228 Wed Jan 30 00:44:14 CET 2008 WEB-INF/jboss-web.xml
         0 Thu Jan 31 16:07:30 CET 2008 WEB-INF/wsdl/
      2457 Thu Jan 31 16:07:30 CET 2008 WEB-INF/wsdl/reply.wsdl
    

    You have to deploy both the jaxws-samples-dar-addressing.jar and the jaxws-samples-dar-addressing-client.war; for ease we run the transportation company receipt system on the same host of the optimization system, of course you can play with the addresses and simulate a real world architecture using two hosts. Then we run the request service client...

    wsrunclient.sh -classpath jaxws-samples-dar-addressing-client.jar org.jboss.test.ws.jaxws.samples.dar.AddressingClient http://localhost.localdomain:8080/dar?wsdl
    

    ...and verify the obtained behavior checking the log on the client side:

    * Synchronous invocation: 
    Sent message ID: urn:uuid:321dee7b-953c-4405-9b06-dbc6743d36e6
    Tue Feb 05 11:31:37 CET 2008 Sending request...
    [Fatal Error] :-1:-1: Premature end of file.
    Tue Feb 05 11:31:42 CET 2008 Done.
    
    * Asynchronous invocation: 
    Sent message ID: urn:uuid:b9076402-f52-4aca-9e5b-b6e80cf1c3e2
    Tue Feb 05 11:31:42 CET 2008 Sending request...
    Doing something interesting in the mean time... ;-) 
    [Fatal Error] :-1:-1: Premature end of file.
    Tue Feb 05 11:31:47 CET 2008 Done.
    
    * Oneway invocation: 
    Sent message ID: urn:uuid:cc5d90d1-d95-450d-a0cc-3a025f49d6c3
    Tue Feb 05 11:31:47 CET 2008 Sending request...
    Tue Feb 05 11:31:47 CET 2008 Done.
    

    ... and on the server side:

    11:31:37,218 INFO  [STDOUT] http://localhost:8080/dar-client/replyTo
    11:31:37,218 INFO  [DarProcessor] Processing DAR request... org.jboss.test.ws.jaxws.samples.dar.DarRequest@1c2cbee
    11:31:37,218 INFO  [DarProcessor] 1 person(s) from 582 to 662
    11:31:37,218 INFO  [DarProcessor] 2 person(s) from 260 to 962
    11:31:37,218 INFO  [DarProcessor] 1 person(s) from 10 to 959
    11:31:37,218 INFO  [DarProcessor] 3 person(s) from 138 to 722
    11:31:42,219 INFO  [DarProcessor] Done org.jboss.test.ws.jaxws.samples.dar.DarRequest@1c2cbee
    11:31:42,311 INFO  [STDOUT] ReplyTo sink:
    11:31:42,314 INFO  [STDOUT] <env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'><env:Header xmlns:wsa='http://www.w3.org/2005/08/addressing'><wsa:To>http://localhost:8080/dar-client/replyTo</wsa:To><wsa:Action>http://org.jboss.ws/samples/dar/DarEndpointPort/OUT</wsa:Action><wsa:RelatesTo>urn:uuid:321dee7b-953c-4405-9b06-dbc6743d36e6</wsa:RelatesTo></env:Header><env:Body><ns1:processResponse xmlns:ns1='http://org.jboss.ws/samples/dar'><return><routes><busId>Bus1</busId></routes><routes><busId>Bus0</busId><stops><node>582</node><time>2008-02-05T11:31:36.443+01:00</time></stops><stops><node>662</node><time>2008-02-05T11:31:36.443+01:00</time></stops><stops><node>260</node><time>2008-02-05T11:31:36.444+01:00</time></stops><stops><node>962</node><time>2008-02-05T11:31:36.444+01:00</time></stops><stops><node>10</node><time>2008-02-05T11:31:36.444+01:00</time></stops><stops><node>959</node><time>2008-02-05T11:31:36.444+01:00</time></stops><stops><node>138</node><time>2008-02-05T11:31:36.444+01:00</time></stops><stops><node>722</node><time>2008-02-05T11:31:36.444+01:00</time></stops></routes><unservedRequests><from><node>425</node><time>2008-02-05T11:31:36.443+01:00</time></from><id>Req0</id><people>2</people><to><node>920</node><time>2008-02-05T11:31:36.443+01:00</time></to></unservedRequests></return></ns1:processResponse></env:Body></env:Envelope>
    
    11:31:42,362 INFO  [STDOUT] http://localhost:8080/dar-client/replyTo
    11:31:42,362 INFO  [DarProcessor] Processing DAR request... org.jboss.test.ws.jaxws.samples.dar.DarRequest@807653
    11:31:42,362 INFO  [DarProcessor] 3 person(s) from 89 to 328
    11:31:42,362 INFO  [DarProcessor] 1 person(s) from 293 to 743
    11:31:42,362 INFO  [DarProcessor] 3 person(s) from 651 to 371
    11:31:42,362 INFO  [DarProcessor] 2 person(s) from 283 to 326
    11:31:47,363 INFO  [DarProcessor] Done org.jboss.test.ws.jaxws.samples.dar.DarRequest@807653
    11:31:47,374 INFO  [STDOUT] ReplyTo sink:
    11:31:47,374 INFO  [STDOUT] <env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'><env:Header xmlns:wsa='http://www.w3.org/2005/08/addressing'><wsa:To>http://localhost:8080/dar-client/replyTo</wsa:To><wsa:Action>http://org.jboss.ws/samples/dar/DarEndpointPort/OUT</wsa:Action><wsa:RelatesTo>urn:uuid:b9076402-f52-4aca-9e5b-b6e80cf1c3e2</wsa:RelatesTo></env:Header><env:Body><ns1:processResponse xmlns:ns1='http://org.jboss.ws/samples/dar'><return><routes><busId>Bus1</busId><stops><node>283</node><time>2008-02-05T11:31:42.341+01:00</time></stops><stops><node>326</node><time>2008-02-05T11:31:42.341+01:00</time></stops></routes><routes><busId>Bus0</busId><stops><node>89</node><time>2008-02-05T11:31:42.341+01:00</time></stops><stops><node>328</node><time>2008-02-05T11:31:42.341+01:00</time></stops><stops><node>293</node><time>2008-02-05T11:31:42.341+01:00</time></stops><stops><node>743</node><time>2008-02-05T11:31:42.341+01:00</time></stops><stops><node>651</node><time>2008-02-05T11:31:42.341+01:00</time></stops><stops><node>371</node><time>2008-02-05T11:31:42.341+01:00</time></stops></routes><unservedRequests><from><node>205</node><time>2008-02-05T11:31:42.341+01:00</time></from><id>Req0</id><people>1</people><to><node>630</node><time>2008-02-05T11:31:42.341+01:00</time></to></unservedRequests></return></ns1:processResponse></env:Body></env:Envelope>
    
    11:31:47,527 INFO  [DarAddressingEndpoint] AsyncProcessRequest sent...
    11:31:47,538 INFO  [DarProcessor] Processing DAR request... org.jboss.test.ws.jaxws.samples.dar.DarRequest@149f848
    11:31:47,538 INFO  [DarProcessor] 1 person(s) from 209 to 184
    11:31:47,538 INFO  [DarProcessor] 2 person(s) from 167 to 524
    11:31:47,538 INFO  [DarProcessor] 2 person(s) from 805 to 883
    11:31:47,538 INFO  [DarProcessor] 1 person(s) from 101 to 224
    11:31:52,539 INFO  [DarProcessor] Done org.jboss.test.ws.jaxws.samples.dar.DarRequest@149f848
    11:31:52,549 INFO  [DarMessageBean] Response will be sent to: http://localhost:8080/dar-client/replyService
    11:31:52,887 INFO  [DarReplyEndpointImpl] Result received; relationship message id: urn:uuid:cc5d90d1-d95-450d-a0cc-3a025f49d6c3
    11:31:52,888 INFO  [DarReplyEndpointImpl] Bus1: 
    11:31:52,888 INFO  [DarReplyEndpointImpl] 209 184 101 224 
    11:31:52,888 INFO  [DarReplyEndpointImpl] Bus0: 
    11:31:52,888 INFO  [DarReplyEndpointImpl] 167 524 805 883 
    11:31:52,890 INFO  [DarMessageBean] Response sent.
    

    As you can see in the first and second invocations, simply having added the use of the addressing replyTo properties cause the response to be received from the sink servlet on the server side; however the client nevertheless hangs until the response is produced and sent.

    The third run, using the couple of oneway invocations, shows how the client is free almost immediately after the request has been sent; the response is received through the DarReplyEndpointImpl 5 seconds later. For this reason the latter solution proves to be the best one, even if it requires more development.

     

    JMS Endpoint

    The JBossWS stack also allows you to obtain a JMS endpoint exposing message driven beans as web services. This gives us another way of performing the asynchronous invocation required for the DaR sample.

     

    The service endpoint

    We use a bottom-up approach, thus we start coding an EJB3 message driven bean that extends org.jboss.ws.core.transport.jms.JMSTransportSupportEJB3; to make it a web service endpoint we use the usual annotations (@WebService, @WebMethod, etc.). Please note that even if we didn't do this here, you can fine tune the message consumption configuration like for any other message driven bean.

    @WebService (name = "DarEndpoint",
                 targetNamespace = "http://org.jboss.ws/samples/dar",
                 serviceName = "DarService")
    @WebContext(contextRoot="/dar")
    @SOAPBinding(style = SOAPBinding.Style.RPC)
    @MessageDriven(activationConfig = { 
          @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
          @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/DarRequestQueue")
      },
      messageListenerInterface = javax.jms.MessageListener.class
    )
    public class DarJMSEndpoint extends JMSTransportSupportEJB3
    {
       
       private static final Logger log = Logger.getLogger(DarJMSEndpoint.class);
    
       @WebMethod(operationName = "process", action = "http://org.jboss.test.ws.jaxws.samples.dar/action/processIn")
       public DarResponse process(DarRequest request)
       {
          DarProcessor processor = new DarProcessor();
          return processor.process(request);
       }
    
       @Override
       public void onMessage(Message message)
       {
          log.debug("onMessage: " + message);
          super.onMessage(message);
       }
    }
    

    Once this simple endpoint is ready, you can build the project and take a look at the generated archive:

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-jms.jar
         0 Fri Feb 01 17:55:20 CET 2008 META-INF/
       106 Fri Feb 01 17:55:18 CET 2008 META-INF/MANIFEST.MF
         0 Thu Jan 10 21:41:02 CET 2008 org/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/
       789 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Bus.class
      2318 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarJMSEndpoint.class
      4231 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarProcessor.class
      1585 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarRequest.class
      1340 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarResponse.class
      1085 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Route.class
      1314 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/ServiceRequest.class
       834 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/Stop.class
    

    Nothing special, the JMS endpoint and the DaR model classes. The JBossWS stack automatically generates the wsdl contract at deploy time and publishes it for http use. We are however going to use this endpoint through JMS...

     

    The service client

    The JMS client could be everything able to write to a JMS queue (here queue/DarRequestQueue on the server running the endpoint described above). In this sample the client is obtained through a simple servlet:

    public class JMSClient extends HttpServlet
    {
       private Logger log = Logger.getLogger(JMSClient.class);
       
       protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
          runMessageClient(new PrintStream(httpServletResponse.getOutputStream()));
       }
       
       private void runMessageClient(PrintStream ps)
       {
          String reqMessage = "<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>" +
             "<env:Header></env:Header>" +
               "<env:Body>" +
                 "<ns1:process xmlns:ns1='http://org.jboss.ws/samples/dar'>" +
                   ...
                 "</ns1:process>" +
               "</env:Body>" +
             "</env:Envelope>";
          
          QueueConnection con = null;
          QueueSession session = null;
          try
          {
             InitialContext context = new InitialContext();
             QueueConnectionFactory connectionFactory = (QueueConnectionFactory)context.lookup("ConnectionFactory");
             Queue reqQueue = (Queue)context.lookup("queue/DarRequestQueue");
             con = connectionFactory.createQueueConnection();
             session = con.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
             Queue resQueue = (Queue)context.lookup("queue/DarResponseQueue");
             con.start();
             TextMessage message = session.createTextMessage(reqMessage);
             message.setJMSReplyTo(resQueue);
             QueueSender sender = session.createSender(reqQueue);
             sender.send(message);
             sender.close();
             ps.println("Request message sent, doing something interesting in the mean time... ;-) ");
             con.stop();
          }
          catch (Exception e)
          {
             e.printStackTrace(ps);
          }
          finally
          {
             try
             {
                session.close();
             }
             catch(Exception e1) {}
             try
             {
                con.close();
             }
             catch(Exception e1) {}
          }
       }
       
    
       protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
          doGet(httpServletRequest,httpServletResponse);
       }
    }
    

    The reason for using a servlet here instead of a standalone java application we could launch through wsrunclient, is that running in the container makes the queue connection factory lookup much easier. Of course you can do this in another way.

     

    As you can see, the client specifies the response queue where the server has to reply, queue/DarResponseQueue, thus we'll need to define a message listener to get the responses from it.

     

    To invoke the JMS endpoint, we could have of course done like what is shown in the JAX-WS guide: manually add a port binding with a jms soap:address to the wsdl contract and invoke the endpoint as we would do for standard http invocations. This however is useless in cases like ours, since the invocation fails if the reply doesn't come within a short time period and the client hangs until the the response arrives or the timeout expires. So let's define our message listener:

    @MessageDriven(activationConfig = { 
          @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
          @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/DarResponseQueue")
      },
      messageListenerInterface = javax.jms.MessageListener.class
    )
    public class DarResponseMessageBean
    {
       private Logger log = Logger.getLogger(DarResponseMessageBean.class);
       
       public void onMessage(Message arg0)
       {
          try
          {
             TextMessage textMessage = (TextMessage)arg0;
             String result = textMessage.getText();
             log.info("DAR response received: " + result);
          }
          catch (Exception e)
          {
             e.printStackTrace();
          }
       }
    }
    

    It's another message driven bean that listens on queue/DarResponseQueue  and simply logs the received messages.

     

    Running the sample

    Now it's time to run the sample; we already built and deployed the server side (jaxws-samples-dar-jms.jar), thus let's take a look at the generated client archives first. You'll get a sar file containing two archives: the first one (jaxws-samples-dar-jms-client.war) is the invocation part of the client, containing the servlet...

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-jms-client.war 
         0 Fri Feb 01 19:34:22 CET 2008 META-INF/
       106 Fri Feb 01 19:34:20 CET 2008 META-INF/MANIFEST.MF
         0 Fri Feb 01 19:34:22 CET 2008 WEB-INF/
       625 Fri Feb 01 14:11:00 CET 2008 WEB-INF/web.xml
         0 Thu Jan 10 21:41:02 CET 2008 WEB-INF/classes/org/
         0 Thu Jan 10 21:07:24 CET 2008 WEB-INF/classes/org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 WEB-INF/classes/org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 WEB-INF/classes/org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/
      4407 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/ClientHelper.class
      4560 Fri Feb 01 19:34:10 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/JMSClient.class
         0 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/
      1017 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/Bus.class
      1447 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/DarEndpoint.class
      1619 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/DarRequest.class
      1389 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/DarResponse.class
      1597 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/DarService.class
      1681 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/ObjectFactory.class
      1267 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/Route.class
      1589 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/ServiceRequest.class
      1210 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/Stop.class
       282 Fri Feb 01 17:55:08 CET 2008 WEB-INF/classes/org/jboss/test/ws/jaxws/samples/dar/generated/package-info.class
       232 Fri Feb 01 14:11:00 CET 2008 WEB-INF/jboss-web.xml
    

    ... the second one (jaxws-samples-dar-jms-client.jar) ships the response message bean instead:

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-jms-client.jar 
         0 Fri Feb 01 19:34:22 CET 2008 META-INF/
       106 Fri Feb 01 19:34:20 CET 2008 META-INF/MANIFEST.MF
         0 Thu Jan 10 21:41:02 CET 2008 org/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/
         0 Thu Jan 10 21:07:24 CET 2008 org/jboss/test/
         0 Thu Jan 10 21:41:22 CET 2008 org/jboss/test/ws/
         0 Tue Jan 29 15:37:30 CET 2008 org/jboss/test/ws/jaxws/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/
         0 Fri Feb 01 17:55:08 CET 2008 org/jboss/test/ws/jaxws/samples/dar/
      1513 Fri Feb 01 19:34:10 CET 2008 org/jboss/test/ws/jaxws/samples/dar/DarResponseMessageBean.class
    

    Here is the jaxws-samples-dar-jms-client.sar archive:

    [alessio@localhost trunk]$ jar -tvf output/tests/libs/jaxws-samples-dar-jms-client.sar 
         0 Fri Feb 01 19:34:22 CET 2008 META-INF/
       106 Fri Feb 01 19:34:20 CET 2008 META-INF/MANIFEST.MF
      2156 Fri Feb 01 19:34:22 CET 2008 jaxws-samples-dar-jms-client.jar
     14138 Fri Feb 01 19:34:22 CET 2008 jaxws-samples-dar-jms-client.war
        58 Fri Feb 01 17:47:20 CET 2008 META-INF/jboss-service.xml
    

    As you can see from the sources, the jboss-service.xml doesn't define anything actually; it could be used to install the two queue we use, but since the application server automatically creates the queues any MDB listens to, we choose the easy way here.

     

    Finally we can run the sample! Please drop the sar archive in the deploy directory of you already running application server, then just browse to http://localhost:8080/dar-jms-client/JMSClient: you'll immediately get a simple page saying the request has been sent, meaning the invocation part of the client did his job. On the logs you'll see something like this:

    10:13:47,279 INFO  [DarProcessor] Processing DAR request... org.jboss.test.ws.jaxws.samples.dar.DarRequest@694b7e
    10:13:47,279 INFO  [DarProcessor] 1 person(s) from 18 to 575
    10:13:47,279 INFO  [DarProcessor] 2 person(s) from 713 to 845
    10:13:47,279 INFO  [DarProcessor] 2 person(s) from 117 to 140
    10:13:47,279 INFO  [DarProcessor] 1 person(s) from 318 to 57
    10:13:52,280 INFO  [DarProcessor] Done org.jboss.test.ws.jaxws.samples.dar.DarRequest@694b7e
    10:13:52,353 INFO  [AbstractJMSTransportSupport] Sent response
    10:13:52,360 INFO  [DarResponseMessageBean] DAR response received: <env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'><env:Header></env:Header><env:Body><ns1:processResponse xmlns:ns1='http://org.jboss.ws/samples/dar'><return><routes><busId>Bus1</busId><stops><node>117</node></stops><stops><node>140</node><time>2008-02-01T14:25:12.114+01:00</time></stops><stops><node>318</node><time>2008-02-01T14:25:12.114+01:00</time></stops><stops><node>57</node><time>2008-02-01T14:25:12.114+01:00</time></stops></routes><routes><busId>Bus0</busId><stops><node>18</node><time>2008-02-01T14:25:12.114+01:00</time></stops><stops><node>575</node><time>2008-02-01T14:25:12.114+01:00</time></stops><stops><node>713</node><time>2008-02-01T14:25:12.114+01:00</time></stops><stops><node>845</node><time>2008-02-01T14:25:12.114+01:00</time></stops></routes><unservedRequests><from><node>667</node><time>2008-02-01T14:25:12.114+01:00</time></from><id>Req0</id><people>2</people><to><node>17</node><time>2008-02-01T14:25:12.114+01:00</time></to></unservedRequests></return></ns1:processResponse></env:Body></env:Envelope>
    

    Once the DaR request processing is done, the response is put to the queue and the DarResponseMessageBean consumes it. We achieved the expected behavior using the JMS transport too, however...

     

    Client and server on different boxes

    As previously said, for ease we've been deploying both client and server sides of our application on the same JBoss instance. We all know that web services are about different systems interoperability thus running client and server on different boxes is the common situation. However obtaining this is not straightforward when playing with queues and message bean endpoints. The reason for this is that both the request and response queue belong to the service provider side, even if the client says which response queue has to be used. A real world architecture with client and server running on different boxes implies that:

    • you need to define the response queue on the server side, using a jboss-service.xml descriptor:
    <?xml version="1.0" encoding="UTF-8"?>
    <server>
      <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=DarResponseQueue">
        <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
      </mbean>
    </server>
    

    Note if you're running on JBoss5 and above, you need to define both request and response queues using jboss-service.xml descriptor:

    <?xml version="1.0" encoding="UTF-8"?>
    <server>
      <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=DarRequestQueue">
        <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
      </mbean>
      <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=DarResponseQueue">
        <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
      </mbean>
    </server>
    
    • when sending the request, your client has to lookup the queue using the remote provider; this is achieved properly configuring the InitialContext:
    Hashtable env = new Hashtable();
    env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
    env.put("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
    env.put(javax.naming.Context.PROVIDER_URL, service_provider_host_address);
    InitialContext context = new InitialContext(env);
    QueueConnectionFactory connectionFactory = (QueueConnectionFactory)context.lookup("ConnectionFactory");
    Queue reqQueue = (Queue)context.lookup("queue/DarRequestQueue");
    con = connectionFactory.createQueueConnection();
    session = con.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
    Queue resQueue = (Queue)context.lookup("queue/DarResponseQueue");
    
    • if you want to stick to the solution using the client response message bean, it has to listen to a remote queue. The instruction for doing so are available at the main JBoss wiki
    • finally, you'll have to cope with the need of defining and advertising different response queues for each service consumer, which might not be that good...