JAX-WS i WS-Addressing


Standard WS-Addressing wykorzystuje się powszechnie w przypadku asynchronicznej komunikacji między dwoma Web Service’ami. Nadawca wiadomości określa w nagłówku komunikatu SOAP docelowy adres (endpoint), na który dostarczyć należy odpowiedź po ukończeniu długotrwałego przetwarzania. Standard JAX-WS wspiera WS-Addressing co pokrótce zaprezentuję w niniejszym tutorial’u.

Kod źródłowy asynchronicznego serwisu zamieściłem poniżej. Listingi zawierają wszystkie instrukcje typu import, ponieważ większość z wykorzystywanych klas znajduje się zarówno w pakiecie com.sun.xml.internal.ws.api, jak i com.sun.xml.ws.api. Odwoływanie się do niewłaściwej klasy skutkuje pojawieniem się stosownego komunikatu o błędzie podczas deploy’mentu. Przykładowa aplikacja zostanie uruchomiona na serwerze aplikacyjnym WebLogic 10.3.5.

import javax.jws.Oneway;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService(serviceName = "svSampleService",
            targetNamespace = "http://lantoniak.edu/wsaddressing/service",
            portName = "prSampleService")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, parameterStyle=SOAPBinding.ParameterStyle.BARE)
public interface SampleService {
   @Oneway
   public void initiateAccountCreation(Client client);
}
import com.sun.xml.ws.api.SOAPVersion;
import com.sun.xml.ws.api.addressing.AddressingVersion;
import com.sun.xml.ws.api.addressing.OneWayFeature;
import com.sun.xml.ws.api.addressing.WSEndpointReference;
import com.sun.xml.ws.api.message.HeaderList;
import com.sun.xml.ws.api.message.Headers;
import com.sun.xml.ws.developer.JAXWSProperties;
import com.sun.xml.ws.developer.WSBindingProvider;
import edu.lantoniak.wsaddressing.Account;
import edu.lantoniak.wsaddressing.Client;
import edu.lantoniak.wsaddressing.SampleCallback;
import edu.lantoniak.wsaddressing.SampleService;
import org.apache.log4j.Logger;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.jws.WebService;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.soap.Addressing;
import java.net.URL;

@Stateless
@WebService(endpointInterface="edu.lantoniak.wsaddressing.SampleService",
            serviceName = "svSampleService",
            targetNamespace = "http://lantoniak.edu/wsaddressing/service",
            portName = "prSampleService")
@Addressing(enabled = true, required = true)
public class SampleServiceImpl implements SampleService {
   private static final Logger log = Logger.getLogger(SampleServiceImpl.class);

   @Resource
   private WebServiceContext context;

   @Override
   public void initiateAccountCreation(Client client) {
      // Get message details...
      HeaderList hl = (HeaderList) context.getMessageContext().get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY);
      WSEndpointReference reference = hl.getReplyTo(AddressingVersion.W3C, SOAPVersion.SOAP_11);
      String messageId = hl.getMessageID(AddressingVersion.W3C, SOAPVersion.SOAP_11);
      String callbackAddress = reference.getAddress();
      log.info("Received message id: " + messageId);
      log.info("Received callback address: " + callbackAddress);

      // Process something...
      Account account = new Account();
      account.setType("lukasz".equalsIgnoreCase(client.getName()) ? "saving" : "standard");

      // Send response...
      try {
         Service service = Service.create(new URL(reference.getAddress() + "?WSDL"),
                                          new QName("http://lantoniak.edu/wsaddressing/service", "svSampleCallback"));
         SampleCallback callbackPort = service.getPort(new QName("http://lantoniak.edu/wsaddressing/service", "prSampleCallback"),
                                                       SampleCallback.class, new OneWayFeature());
         WSBindingProvider bindingProvider = (WSBindingProvider) callbackPort;
         bindingProvider.setAddress(reference.getAddress());
         bindingProvider.setOutboundHeaders(Headers.create(AddressingVersion.W3C.relatesToTag, messageId));
         bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, reference.getAddress());

         callbackPort.reviewAccountDetails(account);
      } catch (Exception e) {
         log.error("Error while calling callback operation.", e);
         throw new WebServiceException(e);
      }
   }
}

Metoda SampleServiceImpl#initiateAccountCreation(Client) pobiera identyfikator komunikatu i docelowy endpoint, a następnie przetwarza otrzymaną wiadomość oraz kieruje odpowiedź na zadany adres (wywołuje zdalny Web Service). Poza serwisem, zaimplementować należy także sam interfejs (bądź kompletną usługę) typu callback, patrz linia 56. Główną metodę serwisu oraz callback oznaczyłem adnotacją @Oneway, aby proces konsumenta nie oczekiwał na zwrócenie wyniku.

import javax.jws.Oneway;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService(serviceName = "svSampleCallback",
            targetNamespace = "http://lantoniak.edu/wsaddressing/service",
            portName = "prSampleCallback")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, parameterStyle=SOAPBinding.ParameterStyle.BARE)
public interface SampleCallback {
   @Oneway
   public void reviewAccountDetails(Account account);
}
@Stateless
@WebService(endpointInterface="edu.lantoniak.wsaddressing.SampleCallback",
            serviceName = "svSampleCallback",
            targetNamespace = "http://lantoniak.edu/wsaddressing/service",
            portName = "prSampleCallback")
public class SampleCallbackImpl implements SampleCallback {
   private static final Logger log = Logger.getLogger(SampleCallbackImpl.class);

   @Override
   public void reviewAccountDetails(Account account) {
      log.info("Received new account type " + account.getType() + ".");
   }
}

Zaimplementowaną usługę wywołałem z poziomu SoapUI. Skorzystałem przy tym z zakładki “WS-A” w celu określenia docelowego miejsca wysłania odpowiedzi. Atrybut “Replay to:” ustawiłem na adres serwisu typu callback zdeploy’owanego na serwerze aplikacyjnym (http://192.168.56.1:7001/SampleCallbackImpl/svSampleCallback). Po wysłaniu żądania SOAP, w log’ach WLS otrzymałem następujące komunikaty:

INFO SampleServiceImpl:49 - Received message id: uuid:327aa8e3-6a1e-4cc2-8c17-2c71ff92205e
INFO SampleServiceImpl:50 - Received callback address: http://192.168.56.1:7001/SampleCallbackImpl/svSampleCallback
INFO SampleCallbackImpl:23 - Received new account type saving.

Twórcy narzędzia SoapUI wyposażyli je w funkcję “Mock Service”, za pomocą której w łatwy sposób zasymulować można przykładowy serwis na podstawie kontraktu WSDL.

Wywołanie asynchronicznego serwisu z poziomu SoapUI (odpowiedź kierowana do usługi działającej na serwerze WebLogic):

Wywołanie asynchronicznego serwisu z poziomu SoapUI (odpowiedź kierowana do tzw. Mock Service):

Przykładowy komunikat SOAP wysłany przez SoapUI do WLS:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://lantoniak.edu/wsaddressing/service">
   <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
      <wsa:Action>http://lantoniak.edu/wsaddressing/service/SampleService/initiateAccountCreation</wsa:Action>
      <wsa:ReplyTo>
         <wsa:Address>http://localhost:8088/prSampleCallbackBinding</wsa:Address>
      </wsa:ReplyTo>
      <wsa:MessageID>uuid:73c98c07-a9bc-47ab-b1f5-0ae216ea8f64</wsa:MessageID>
      <wsa:To>http://192.168.56.1:7001/SampleServiceImpl/svSampleService</wsa:To>
   </soapenv:Header>
   <soapenv:Body>
      <ser:initiateAccountCreation>
         <id>1</id>
         <name>Lukasz</name>
      </ser:initiateAccountCreation>
   </soapenv:Body>
</soapenv:Envelope>

Przykładowy komunikat SOAP wysłany przez WLS do Mock Service SoapUI (zwróć uwagę na powtarzający się identyfikator wiadomości):

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Header>
      <wsa:RelatesTo xmlns:wsa="http://www.w3.org/2005/08/addressing">uuid:73c98c07-a9bc-47ab-b1f5-0ae216ea8f64</wsa:RelatesTo>
   </S:Header>
   <S:Body>
      <ns2:reviewAccountDetails xmlns:ns2="http://lantoniak.edu/wsaddressing/service">
         <type>saving</type>
      </ns2:reviewAccountDetails>
   </S:Body>
</S:Envelope>

Kompletny kod źródłowy aplikacji znajduje się tutaj.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: