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.

Przesyłanie plików za pomocą Web Service

Zleco no mi ostatnio wykonanie Web Service’u, którego zadanie polegało na przesyłaniu niewielkich plików binarnych. Początkowo (w pośpiechu) payload wiadomości transmitowałem jako tablicę bajtów w klasie POJO reprezentującej obiekt odpowiedzi. Niestety, podczas testów przeprowadzanych na serwerze aplikacyjnym WLS, owy mechanizm doprowadził do całkowitego padu serwera podczas przetwarzania pliku o wielkości około 10 MB. 10 MB to dziesięć razy więcej niż maksymalna rzeczywista przesyłana wielkość paczki. Szybki wgląd w szkolenie odnośnie BEA Web Services zaowocował niemal natychmiastową odpowiedzią dotyczącą sposobu przesyłania plików w technologi Web Service. Wszelkie obiekty pokaźnych rozmiarów należy bowiem stream’ować jako załączniki komunikatu SOAP.

Implementacja

Podczas implementacji WS skorzystałem z adnotacji @HandlerChain wskazującej na deskryptor zawierający definicję łańcucha wywołań kolejnych “interceptorów” metody serwisowej (patrz poniższe listingi). Plik ten znajdować się powinien w tym samym katalogu co skompilowane klasy serwisu. Każde ogniwo łańcucha wywołań spowodować może zaprzestanie dalszego przetwarzania wiadomości. Dzięki temu adnotację @HandlerChain stosuje się często podczas realizacji niestandardowego procesu autentykacji, autoryzacji, czy też pomiaru wydajności. Zawartość pliku definiującego handler chain przedstawia następujący listing:

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
  <handler-chain name="SampleAttachmentHandler">
    <handler>
      <handler-class>edu.lantoniak.service.handler.AttachmentHandler</handler-class>
    </handler>
  </handler-chain>
</handler-chains>

Kod handler’a dodającego załącznik gdy ustawiono określoną zmienną kontekstową:

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.File;
import java.util.Collections;
import java.util.Set;

public class AttachmentHandler implements SOAPHandler<SOAPMessageContext> {
   private static final Logger log = Logger.getLogger(AttachmentHandler.class);

   public Set<QName> getHeaders() {
      return Collections.emptySet();
   }

   public boolean handleMessage(SOAPMessageContext context) {
      if (shouldAddAttachment(context)) {
         SOAPMessage soapMessage = context.getMessage();
         String path = (String) context.get(SampleServiceImpl.MY_KEY);
         FileDataSource fileDataSource = new FileDataSource(path);
         DataHandler dataHandler = new DataHandler(fileDataSource);
         AttachmentPart attachmentPart = soapMessage.createAttachmentPart(dataHandler);
         attachmentPart.setContentId(fileDataSource.getFile().getName());
         soapMessage.addAttachmentPart(attachmentPart);

         context.put(SampleServiceImpl.MY_KEY, null); // Cleanup
      }
      return true;
   }

   public boolean handleFault(SOAPMessageContext context) {
      return true;
   }

   public void close(MessageContext context) {
   }

   /*
    * Add attachment only if SampleServiceImpl.MY_KEY is set.
    */
   private boolean shouldAddAttachment(MessageContext context) {
      String patchKey = (String) context.get(SampleServiceImpl.MY_KEY);
      return patchKey != null;
   }
}

W przypadku zwrócenie false przez metodę handleMessage, któregoś z ogniw łańcucha, dalsze przetwarzanie pozostaje przerwane (czytaj dokumentację JAX-WS). Sama klasa serwisu powinna wyglądać następująco:

@Stateless
@WebService(serviceName="SampleService",
            targetNamespace="http://edu.lantoniak",
            wsdlLocation = "META-INF/wsdl/SampleService.wsdl",
            portName="SampleServicePort",
            endpointInterface="edu.lantoniak.service.SampleService")
@HandlerChain(file="handler-chain.xml")
public class SampleServiceImpl implements SampleService {
   public static final String MY_KEY = "edu.lantoniak.service.MY_KEY";

   @Resource
   WebServiceContext webServiceContext;

   @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
   public GetFileRequestElement getSampleFile(GetFileResponseElement request) {
      ...
      webServiceContext.getMessageContext().put(MY_KEY, request.getAbsoluteFilePath());
      ...
      return response;
   }
}

Zwracam uwagę na współdzielenie kontekstu wiadomości MessageContext. Plik reprezentowany przez ścieżkę request.getAbsoluteFilePath() dodawany jest jako zacznik podczas generacji komunikatu odpowiedzi SOAP (po ukończeniu przetwarzania instrukcji zawartych w metodzie getSampleFile). Handler wykonuje się zawsze przed i po wywołaniu docelowej procedury (patrz poniższy diagram, źródło: http://www.javaworld.com/javaworld/jw-02-2007/jw-02-handler.html). Etap przetwarzania komunikatu poznać można za pomocą np. atrybutu MessageContext.MESSAGE_OUTBOUND_PROPERTY znajdującego się w obiekcie typu MessageContext.

Źródło: http://www.javaworld.com/javaworld/jw-02-2007/jw-02-handler.html

Piszę tutaj zapewne o rzeczach dla większości znanych. Teraz przejdę jednak do sedna. W adnotacji @WebService wskazałem na własnoręcznie stworzony plik WSDL. Według specyfikacji Attachments Profile Version 1.0 (http://www.ws-i.org/profiles/attachmentsprofile-1.0.html) chęć przesyłania załączników powinna zostać specjalnie oznaczona we wspomnianym deskryptorze. Relewantne fragmenty WSDL zamieszczam poniżej:

<xs:element name="AttachmentElement" type="xs:base64Binary"/>

<wsdl:message name="GetFileRequestMessage">
  <wsdl:part name="request" element="tns:GetFileRequestElement"/>
</wsdl:message>
<wsdl:message name="GetFileResponseMessage">
  <wsdl:part name="response" element="tns:GetFileResponseElement"/>
  <wsdl:part name="fileAttachment" element="tns:AttachmentElement"/>
</wsdl:message>

<wsdl:portType name="SampleServicePort">
  <wsdl:operation name="getSampleFile">
    <wsdl:input message="tns:GetFileRequestMessage"/>
    <wsdl:output message="tns:GetFileResponseMessage"/>
  </wsdl:operation>
</wsdl:portType>

<wsdl:binding name="SampleServiceBinding" type="tns:SampleServicePort">
  <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
  <wsdl:operation name="getSampleFile">
    <soap:operation soapAction=""/>
    <wsdl:input>
      <soap:body use="literal"/>
    </wsdl:input>
    <wsdl:output>
      <mime:multipartRelated>
        <mime:part>
          <soap:body parts="response" use="literal"/>
        </mime:part>
        <mime:part>
          <mime:content part="fileAttachment" type="application/octet-stream"/>
        </mime:part>
      </mime:multipartRelated>
    </wsdl:output>
  </wsdl:operation>
</wsdl:binding>

Oczywiście w przypadku pominięcia wstawek określających załączniki (linie 8 i 26 – 33), pliki są w dalszym ciągu wysyłane oraz prawidłowo odbierane przez klienta! Specyfikacja jest jednak specyfikacją i nie powinna być lekceważona. Alternatywną metodę wysyłania i odbierania plików przez Web Service (bez użycia @HandlerChain) opisał Geemon Kadambalil: http://geemonkb.blogspot.com/2009/01/jax-wx-streaming-soap-attachments.html. Spoglądając z obecnej perspektywy na efekt mojej pracy, dochodzę do wnioski, iż rozwiązanie Geemon’a wydaje się bardziej eleganckie i w przyszłości będę je stosował. Własnoręczne pisanie WSDL spowodowane było koniecznością trzymania się wewnętrznych standardów nazewniczych. Może któryś z czytelników zna adnotację JAXB pozwalającą na zmianę atrybutu name tag’a wsdl:binding? Co prawda po zapoznaniu się z dokumentem JSR224 v2.2 mrel3, wątpie w istnienie owej funkcjonalności: The value of the name attribute of the wsdl:binding is not significant, by convention it contains the qualified name of the corresponding wsdl:portType suffixed with “Binding”.

Przydatne linki:

JBoss Web Services

Po długiej batalii z EJB przyszedł czas na wyjazd do Wenecji. Chwila relaksu i oderwania od klawiatury – smażenie się na plaży w Lido, zwiedzanie wcale nie śmierdzących kanałów miasta, przejażdżka gondolą i niemal bójka w Padwie. Po tych jakże odprężających ćwiczeniach usiadłem na moment w domowym zaciszu przed komputerem i wpadła mi do głowy jedna myśl – jak skorzystać z Web Services w Jboss? Z technologią tą miałem sporo do czynienia w implementacji Oracle’owej na serwerze aplikacyjnym Weblogic. Tam jednak umysł programisty wyręcza myszka i JDeveloper. Generowany przez IDE kod jest nadmiarowy lecz o dziwo zazwyczaj działa :P.

Przystąpmy więc do części bardziej merytorycznej. Zacznę od krótkiego przypomnienia technologii Web Services, standardu SOAP oraz takich terminów jak np. WSDL. Web Service wykorzystuje jako warstwę transportu protokół HTTP (zazwyczaj, inne możliwości: SMTP, FTP, JMS) oraz język XML (w celu strukturyzacji przesyłanych danych). Komunikat SOAP ma formę XML’a składającego się z <Envelope>, oraz zagnieżdżonych w nim nagłówku <Header> i ciele <Body>.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:edu="edu.lantoniak.ws">
  <soapenv:Header/>
  <soapenv:Body>
    <edu:sayHello>Lukasz</edu:sayHello>
  </soapenv:Body>
</soapenv:Envelope>

Za opis udostępnianego przez Nas serwisu odpowiada WSDL. Jest on zwyczajowo umieszczony na serwerze aplikacyjnym i dostępny z poziomu przeglądarki internetowej. Klienci WS, na podstawie informacji zawartych w tym pliku, określić mogą typy wymaganych parametrów poszczególnych metod oraz wartości przez nich zwracanych. Jako deweloper Java rzadko piszę WSDL ręcznie. Deskryptor ów generowany jest automatycznie na podstawie klasy Java (implementacji Web Service’u) opatrzonej odpowiednimi adnotacjami. Poniżej zamieściłem przykładowy WSDL.

<definitions name="HelloService" targetNamespace="edu.lantoniak.ws">
  <types>
    <xs:schema targetNamespace="edu.lantoniak.ws" version="1.0">
      <xs:element name="sayHello" nillable="true" type="xs:string"/>
      <xs:element name="sayHelloResponse" nillable="true" type="xs:string"/>
    </xs:schema>
  </types>
  <message name="HelloWSRemote_sayHelloResponse">
    <part element="tns:sayHelloResponse" name="sayHelloResponse"/>
  </message>
  <message name="HelloWSRemote_sayHello">
    <part element="tns:sayHello" name="sayHello"/>
  </message>
  <portType name="HelloWSRemote">
    <operation name="sayHello" parameterOrder="sayHello">
      <input message="tns:HelloWSRemote_sayHello"/>
      <output message="tns:HelloWSRemote_sayHelloResponse"/>
    </operation>
  </portType>
  <binding name="HelloWSRemoteBinding" type="tns:HelloWSRemote">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="sayHello">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="HelloService">
    <port binding="tns:HelloWSRemoteBinding" name="HelloWSPort">
      <soap:address location="http://localhost:8080/Tutorial7/HelloWSImpl"/>
    </port>
  </service>
</definitions>

Wewnątrz tagu <types> określa się wszystkie typy danych stosowanych w serwisie (typy parametrów oraz wartości zwracanych wykorzystywanych w metodach). W Naszym przypadku, zdefiniowane są dwa proste typy klasy xs:string: sayHello oraz sayHelloResponse. Tag <message> specyfikuje wszystkie rodzaje wiadomości jakie obsługiwane są przez Web Service. Linijki 14 – 19 wiążą rodzaje wykorzystywanych wiadomości z poszczególnymi operacjami (metodami). Sekcja <binding> wyróżnia poszczególne operacje serwisu oraz definiuje szczegóły dotyczące stosowanego protokołu (w tym przypadku stylu document – por. RPC). Wreszcie, tag <service> specyfikuje nazwę serwisu oraz jego adres.

Implementacja prostego Web Service’u

W Eclipse, podobnie jak w JDeveloper, implementację Web Service’u oraz projektu proxy stosowanego przez klienta, można ponoć (mi się nie udało) w stosunkowo łatwy sposób wyklikać (rodzaje projektu: “Create a Sample Web Service” oraz “Web Service Client”). Zaprezentuję jednak drogą równie przyjemną metodę, nie opierającą się na kreatorach graficznych i klikologii, lecz na nieco bardziej przemyślanym użyciu klawiatury. Web Service tworzyć można na dwa sposoby: bottom up (najpierw klasy Java, a potem WSDL), lub top down (inaczej zwane contract first – najpierw WSDL, a następnie implementacja).

Stwórzmy nowy projekt EJB, a w nim bezstanowe (@Statefull nie jest wspierane, polecam zapoznać się z terminem conversational Web Service) ziarno EJB.

@Remote
@WebService(targetNamespace = "edu.lantoniak.ws")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT,
             parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface HelloWSRemote {
   public String sayHello(String name);
}

@Stateless
@WebService(serviceName = "HelloService", targetNamespace = "edu.lantoniak.ws",
            name = "HelloWS", endpointInterface = "edu.lantoniak.ws.HelloWSRemote")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT,
             parameterStyle = SOAPBinding.ParameterStyle.BARE)
public class HelloWSImpl implements HelloWSRemote {
   public String sayHello(String name) {
      return "Hello " + name + "!";
   }
}

Następnie deploy’ujemy aplikację. Uwaga: nazwa pod którą deploy’owana jest aplikacja nie może zawierać spacji! Aby upewnić się, że aplikacja wgrana została pomyślnie, przejdź do http://localhost:8080/jbossws/services i sprawdź, czy na liście serwisów widnieje Twoja nowo dodana usługa. Działanie samego Web Service’u weryfikujemy za pomocą narzędzia SoapUI.

W celu dalszej zabawy, polecam zapoznanie się z podstawowymi adnotacjami @WebMethod, @WebParam, @WebResult, oraz bardziej zaawansowanymi @OneWay i @HandlerChain.

Implementacja klienta standalone

Poniżej znajduje się kod prostego klienta, wywołującego wcześniej stworzony Web Service z poziomu Javy.

public class Main {
   public static void main(String[] args) {
      String endpointURI = "http://localhost:8080/Tutorial7/HelloWSImpl?wsdl";
      try {
         String hello = getPort(endpointURI).sayHello("Lukasz");
         System.out.println(hello);
      } catch (MalformedURLException e) {
         e.printStackTrace();
      }
   }

   private static HelloWSRemote getPort(String endpointURI) throws MalformedURLException {
      QName serviceName = new QName("edu.lantoniak.ws", "HelloService");
      URL wsdlURL = new URL(endpointURI);
      Service service = Service.create(wsdlURL, serviceName);
      return service.getPort(HelloWSRemote.class);
   }
}

Wstrzykiwanie proxy serwisu do ziaren EJB

W celu wywołania serwisu w ziarnie EJB, skorzystać należy z narzędzia wsconsume wchodzącego w skład serwera aplikacyjnego JBoss. Narzędzie to generuje klasy proxy Web Service’u, które następnie wepniemy za pomocą adnotacji @WebServiceRef. Stwórzmy nowy, pusty katalog w dowolnym miejscu na dysku twardym i wywołajmy polecenie: $JBOSS_HOME\bin\wsconsume.bat -p edu.lantoniak.ws.proxy http://localhost:8080/Tutorial7/HelloWSImpl?wsdl. Wygenerowane klasy (wraz z katalogami pakietu – w tym przypadku edu.lantoniak.ws.proxy) kopiujemy np. do katalogu generated w projekcie modułu EJB. Następnie w Eclipse klikamy prawym przyciskiem na projekt -> Properties -> Java Build Path -> Libraries -> Add Class Folder i wskazujemy na katalog generated. Pozostało Nam jedynie stworzenie nowego ziarna EJB korzystającego z Web Service’u:

@Stateless
public class StatelessSampleEJB implements StatelessSampleEJBRemote {
   @WebServiceRef(wsdlLocation = "http://localhost:8080/Tutorial7/HelloWSImpl?wsdl")
   private HelloService helloService;

   public String sayHello(String name) {
      return helloService.getHelloWSPort().sayHello(name);
   }
}

Źródła projektów: Tutorial 7 All.zip.