Apache Axis - tutoriál k webovým službám


1. Úvod
2. Príprava projektu
3. Konštrukcia instantnej webovej služby
4. Konštrukcia klienta
4.1. Konštrukcia testovacej triedy
5. Konštrukcia štandardnej webovej služby
5.1. Metóda bottom-up
5.1.1. Popisovač nasadenia webovej služby a jej odstránenia zo serveru.
5.1.2. Konfiguračný súbor servera
5.1.3. Nasadenie webovej služby
5.1.4. Testovanie webovej služby
5.2. Metóda top-down
6. Pokročilé techniky
6.1. Generovanie tried pomocou WSDL2Java do slušnej adresárovej štruktúry.
6.2. Konfigurácia Axisu
6.2.1. Konfigurácia servera
6.2.2. Konfigurácia klienta
6.3. Vlastné mapovanie dátových typov
6.3.1. Serializátor
6.3.2. Deserializátor
6.3.3. Továrne pre serializátory a deserializátory.
6.3.4. Konfigurácia serializácie a deserializácie na strane servera
6.3.5. Konfigurácia serializácie a deserializácie na strane klienta

1. Úvod

2. Príprava projektu

Axis vyžaduje na svoje správne fungovanie webovú aplikáciu, ktorú je možno nasadiť v servletovom kontajneri. Na to je potrebné mať pripravenú adresárovú štruktúru zodpovedajúcu špecifikácii pre webové aplikácie.

V obľúbenom vývojovom prostredí teda vytvoríme nový projekt.

Do adresára WEB-INF/classes je nutné ukladať skompilované triedy. Do adresára knižníc WEB-INF/lib potrebujeme skopírovať všetky JAR knižnice z distribúcie Axisu.

Upravíme popisovač nasadenia webovej aplikácie, t. j. súbor WEB-INF/web.xml. Doň pridáme definíciu axisovského servletu spolu s jeho mapovaním.

<servlet>
  <servlet-name>AxisServlet</servlet-name>
  <display-name>Apache-Axis Servlet</display-name>
  <servlet-class>
    org.apache.axis.transport.http.AxisServlet
  </servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>AxisServlet</servlet-name>
  <url-pattern>/services/*</url-pattern>
</servlet-mapping>  

Ak mienime používať aj instantné webové služby (JWS), musíme pridať mapovanie aj pre ne.

<servlet-mapping>
  <servlet-name>AxisServlet</servlet-name>
  <url-pattern>*.jws</url-pattern>
</servlet-mapping>

Správnu funkcionalitu AxisServletu overíme navštívením príslušnej adresy. Ak sa naša aplikácia volá wstest a beží v kontajneri na porte 8080, navštívime adresu http://localhost:8080/wstest/services

3. Konštrukcia instantnej webovej služby

Najjednoduchší (quick-and-dirty) spôsob sprevádzkovanie webovej služby je vytvorenie instantnej triedy JWS. JWS je nič iné, než jednoduchá Java fazuľa uložená v súbore s príponou jws.

Vytvoríme teda súbor InstantWeatherService.jws a umiestnime ho do koreňa webovej aplikácie (teda do adresára nadradeného adresáru WEB-INF).

public class InstantWeatherService {
  public String getCurrentWeather(String cityName) {
    return "V " + cityName + " je práve veľká zima.";
  }

  public float getCurrentTemperature(String cityName) {
    return 3.14f;
  }  

  public float[] getTemperaturePrediction(String cityName, int howManyDays) {
    float[] temperatures = new float[howManyDays];
    for(int i = 0; i < howManyDays; i++) {
      temperatures[i] = new Double((Math.random() * 45) - 10).floatValue();
    }
    return temperatures;
  }  
}

Trieda nesmie mať vo svojom zdrojovom kóde uvedenú deklaráciu package, inak ju Axis nenájde.

Otestovanie základnej funkčnosti je jednoduché. Browserom zamierime na adresu http://localhost:8080/wstest/InstantWeatherService.jws. Axis skompiluje túto triedu a jej metódy dá k dispozícii ako webovú službu. Ak všetko funguje správne, v browseri sa zobrazí jednoduchá stránka s uvítaním. Z nej zároveň vedie odkaz na WSDL našej čerstvej webovej služby.

Figure 1. Uvítanie fungujúcej instantnej JWS webovej služby

Uvítanie fungujúcej instantnej JWS webovej služby

4. Konštrukcia klienta

Výhodou Axisu je možnosť vygenerovať klientské triedy pre prístup k webservicu a to len na základe WSDL. Predpokladajme, že k nášmu primitívnemu WebServiceu chceme vygenerovať java triedy.

To môžeme urobiť pomocou nástroja WSDL2Java dodávaného s Axisom. Ak predpokladáme, že v premennej prostredia AXICLASSPATH sú uvedené všetky knižnice JAR z distribúcie Axisu, tak príslušný príkaz vo Windowse je nasledovný.

java -cp %AXISCLASSPATH% org.apache.axis.wsdl.WSDL2Java --output . http://localhost:8080/wstest/PrimitiveWebService.jws?wsdl

Parameter --output umožní špecifikovať cieľový adresár, do ktorého sa vygenerujú klientské triedy. Najdôležitejšou je ale adresa (prípadne cesta v súborovom systéme), kde sa nachádza dokument WSDL.

Po spustení príkazu sa nám v cieľovom adresári vytvorí niekoľko Java súborov (minimálne 4):

  • localhost/wstest/PrimitiveWebService_jws/PrimitiveWebService.java je java interface obsahujúci práve tie metódy, ktoré webservice poskytuje. Pre každú vzdialenú procedúru, či funkciu webservicu je v tomto súbore uvedená príslušná java metóda. Samotný interface dedí od interfacu java.rmi.Remote a každá z metód vie hádzať výnimku java.rmi.RemoteException.

  • localhost/wstest/PrimitiveWebService_jws/PrimitiveWebServiceService.java zodpovedá elementu service.

  • localhost/wstest/PrimitiveWebService_jws/PrimitiveWebServiceServiceLocator.java je v podstate továreň, ktorá vracia inštanciu triedy predstavujúcej WebService.

  • localhost/wstest/PrimitiveWebService_jws/PrimitiveWebServiceSoapBindingStub.java je z hľadiska klienta najdôležitejšia trieda. V nej sa vykonáva špinavá práca - generujú sa v nej kódy klientského volania, definujú mapovania java objektov na XML a podobne.

4.1. Konštrukcia testovacej triedy

Teraz nám už môžeme poľahky vytvoriť jednoduchú testovaciu triedu na prístup k webserviceu.

import localhost.wstest.PrimitiveWebService_jws.PrimitiveWebService;
import localhost.wstest.PrimitiveWebService_jws.PrimitiveWebServiceServiceLocator;

public class PrimitiveWebServiceTest {

  public static void main(String[] args) throws Exception {
    PrimitiveWebServiceServiceLocator locator = new PrimitiveWebServiceServiceLocator();
    PrimitiveWebService ws = locator.getPrimitiveWebService();
    System.out.println(ws.echo("Ako sa do hory vola, tak sa ma z hory ozyvat"));
    System.out.println(ws.add(12, 13));
    System.out.println(ws.getCurrentDateTime());
    
  }
}

Po spustení triedy by nám program mal vypísať to, čo nám vrátila webová služba.

5. Konštrukcia štandardnej webovej služby

Na konštrukciu webovej služby sa používajú principiálne dva postupy:

  • top-down: zhora nadol. Najprv sa manuálne pripraví WSDL a na jeho základe sa vytvoria serverovské i klientské triedy

  • bottom-up: zdola nadol. Existujúce triedy sa prispôsobia na použitie v úlohe webovej služby.

Najprv sa budeme venovať spôsobu zdola nahor. Vytvoríme najprv triedy pre serverovskú stranu, z ktorých vygenerujeme WSDL a na základe tohto dokumentu vygenerujeme klientské triedy.

5.1. Metóda bottom-up

Predpokladajme, že chceme vytvoriť webovú službu, ktorá bude reprezentovať meteorologický servis. Základným dátovým typom bude trieda Weather

package axis.weather;

import java.util.Date;

public class Weather {
  private Date date;
  
  private float windSpeed;
  
  private float temperature;
  
  private String textStatus;

  /* nasleduju gettery a settery */
}  

Tento objekt bude využívať interface WeatherService poskytujúci predpoveď počasia.

package axis.weather;

import java.util.Date;

public interface WeatherService {
  /**
   * Vráti stav počasia na dnešný deň.
   * 
   */
  public Weather getWeatherForToday();

  /**
   * Vráti stav počasia zodpovedajúci príslušnému dátumu.  
   */
  public Weather getWeather(Date date);
  
  /**
   * Vráti pole stavov počasia na požadovaný počet dní dopredu.
   * 
   */
  public Weather[] getWeatherPrediction(int daysCount);
  
  /**
   * Vráti textové informácie o tejto službe
   * 
   */
  public String getServiceInfo();
}

Vo fáze nasadenia webovej služby musíme prirodzene vytvoriť implementáciu tohto interfaceu, budeme ju označovať WeatherServiceImpl.

package axis.weather;

import java.util.Date;

public class WeatherServiceImpl implements WeatherService {

  public Weather getWeatherForToday() {
    return getRandomWeather(new Date());
  }
  
  public Weather getWeather(Date date) {
    return getRandomWeather(date);
  }

  public Weather[] getWeatherPrediction(int daysCount) {
    Weather[] weathers = new Weather[daysCount];
    for (int i = 0; i < daysCount; i++) {
      Date d = new Date();
      d.setDate(d.getDay() + i);
      weathers[i] = getRandomWeather(d);
    }
    return weathers;
  }

  public String getServiceInfo() {
    return "This is WeatherService.";
  }

  private Weather getRandomWeather(Date d) {
    Weather w = new Weather();
    w.setDate(d);
    w.setTemperature(new Double((Math.random() * 45) - 10).floatValue());
    w.setTextStatus("Hmlisto.");
    w.setWindSpeed(new Double((Math.random() * 120) - 0).floatValue());
    
    return w;
  }
}

Takúto hotovú triedu môžeme pomerne priamočiaro nasadiť do Axisu ako webovú službu. Predtým však potrebujeme špecifikovať a doriešiť niekoľko konfiguračných záležitostí.

5.1.1. Popisovač nasadenia webovej služby a jej odstránenia zo serveru.

V slušnej spoločnosti Axisovej je to tak, že každej množine webových služieb, ktoré môžu byť jednotne nasadené na serverovskú stranu prislúchajú dva súbory: deploy.wsdd a undeploy.wsdd.

  • deploy.wsdd - špecifikuje parametre webovej služby: jej názov; triedu, ktorá ju implementuje, prípadný zoznam metód a názov portu a cieľového menného priestoru vo WSDL.

    Tento súbor zároveň obsahuje zoznam spôsobov mapovaní vlastných dátových typov na XML.

  • undeploy.wsdd - jednoduchý súbor, pomocou ktorého možno príslušnú službu (či služby) odstrániť zo serveru.

Vytvorme teda pre náš budúci meteorologický servis súbor nasadenia a odstránenia - deploy.wsdd a umiestnime ho do adresára WEB-INF našej webovej aplikácie.

<deployment xmlns="http://xml.apache.org/axis/wsdd/" 
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

  <service name="WeatherService" provider="java:RPC">  
    <parameter name="className" value="axis.weather.WeatherServiceImpl"/>
    <parameter name="allowedMethods" value="*"/>
    <parameter name="wsdlPortType" value="WeatherService"/>
    <parameter name="wsdlTargetNamespace" value="urn:kb-ws-SiteWS"/>
  </service>

  <beanMapping xmlns:ns="urn:axis-weather"
     qname="ns:Weather"
     type="java:axis.weather.Weather"
  />      
</deployment>

Tento súbor nasadenia popisuje nasledovné záležitosti webovej služby:

  • webová služba sa volá WeatherService

  • trieda, ktorá bude implementovať túto webovú službu sa volá axis.weather.WeatherServiceImpl

  • vo webovej služby dávame klientom k dispozícii všetky metódy implementujúcej triedy (hviezdička v parametri allowedMethods). Ak nemienime zverejniť všetky metódy tried môžeme v tomto parametri uviesť explicitný zoznam názvov metód oddelený čiarkami.

  • môžeme špecifikovať port a cieľový menný priestor pre vygenerovaný WSDL.

  • element beanmapping špecifikuje, že fazuľa Weather bude mapovaná na XML pomocou axisovských nástrojov na serializáciu a deserializáciu fazúľ.

5.1.2. Konfiguračný súbor servera

Servlet Axisu načítava svoje konfiguračné nastavenia zo súboru server-config.wsdd. Tento súbor obsahuje všeobecné nastavenia pre Axis a principiálne dáta z popisovačov nasadenia všetkých webových služieb, ktoré sú na danom serveri k dispozícii.

Konfiguračný súbor sa automaticky vygeneruje pri nasadení prvého webserviceu a pri nasadení ďalších webových služieb sa tiež automaticky upravuje.

5.1.3. Nasadenie webovej služby

Na nasadenie webovej služby potrebujeme mať pripravenú a skompilovanú implementáciu a popisovač nasadenia. Spustíme aplikáciu a presvedčíme sa, že nám beží servlet Axisu (v browseri skúsime navštívíť http://localhost:8080/wstest/servlet/AxisServlet; mali by sme vidieť zoznam dosiaľ nasadených webových služieb). Korektný beh tohto servletu je dôležitý preto, lebo samotný proces nasadenia je implementovaný ako webová služba.

Samotné nasadenie môžeme spraviť napr. pomocou nasledovného BAT súboru make-server-config.bat.

rem Vygeneruje server-config na zaklade deployment descriptora

call setaxiscp.bat

set AXIS_SERVLET_LOCATION=http://localhost:8080/wstest/services
set DEPLOYMENT_DESCRIPTOR=c:\projects\wstest\web\WEB-INF\deploy.wsdd

java -cp %AXISCLASSPATH%;. org.apache.axis.client.AdminClient -l%AXIS_SERVLET_LOCATION% %DEPLOYMENT_DESCRIPTOR%

Axis by nám mal vypísať okrem nejakych ladiacich hlášok hlavne

Processing file c:\projects\wstest\web\WEB-INF\deploy.wsdd
<Admin>Done processing</Admin>

Po návšteve URL AxisServletu (http://localhost:8080/wstest/services) by sme mali vidieť v zozname nasadených webových služieb aj náš WeatherService spolu s odkazom na WSDL a zoznamom poskytovaných metód (naša webová služba poskytuje 4 metódy).

Figure 2. Zoznam nasadených webových služieb v Axise

Zoznam nasadených webových služieb v Axise

5.1.4. Testovanie webovej služby

Opäť ako v prípade primitívnej webovej služby si môžeme z WSDL dokumentu vygenerovať klientské triedy.

java -cp %AXISCLASSPATH% org.apache.axis.wsdl.WSDL2Java --output . http://localhost:8080/wstest/services/WeatherService?wsdl

Samozrejme, pri generovaní klientských tried z webovej adresy musíme mať axisovskú webaplikáciu stále spustenú.

Po vygenerovaní získame opäť štyri triedy: WeatherService, WeatherServiceService, WeatherServiceServiceLocator a WeatherServiceSoapBindingStub. Z nich môžeme opäť jednoduchým spôsobom zostaviť testovaciu triedu.

package axis.weather.test;

import java.net.URL;
import java.util.Calendar;

import axis_wstest_WeatherService.WeatherService;
import axis_wstest_WeatherService.WeatherServiceServiceLocator;

public class WeatherServiceTest {

  public static void main(String[] args) throws Exception {
    WeatherServiceServiceLocator locator = new WeatherServiceServiceLocator();
    WeatherService ws = locator.getWeatherService(new URL("http://alhambra:8080/wstest/services/WeatherService"));
    
    // v mapovani XML typov na javovske typy je nekonzistencia: java.util.Date -> xsd:dateTime -> java.util.Calendar
    // to nam narusi jednoliatost tried.
    System.out.println(ws.getWeather(Calendar.getInstance()));
    System.out.println(ws.getServiceInfo());
    System.out.println(ws.getWeatherForToday().getTextStatus());    
  }
}

Až na zmienený zádrheľ [1]medzi mapovaným dátumových dátových typov by všetko malo prebehnúť v poriadku a mali by sme bez problémov získať výsledky z webovej služby.

5.2. Metóda top-down

Ak máme vytvorené, či zhora dané WSDL, problematika klientskeho kódu je jednoduchá. Vygenerujeme ho pomocou nástroja WSDL2Java podobne ako v prípade, keď sme generovali klientské triedy k nášej instantnej webovej službe.

Triedy pre server vieme tiež vygenerovať nástrojom WSDL2JAVA, ale s inou sadou parametrov.

call setaxiscp.bat

java -cp %AXISCLASSPATH%;. org.apache.axis.wsdl.WSDL2Java --server-side --output . http://localhost:8080/wstest/InstantWeatherService.jws?wsdl

Dôležitý je parameter --server-side, ten hovorí, že z WSDL sa majú vygenerovať nielen klientské, ale aj serverovské triedy. Po spustení tohto príkazu sa nám okrem klasických štyroch základných súborov vygeneruje súbor InstantWeatherServiceSoapBindingImpl.java. Ten reprezentuje triedu, ktorá obsahuje výkonný kód webovej služby. Napr. pre WSDL našej instantnej webovej služby sa nám vygeneroval nasledovný zdrojový kód pre implementačnú triedu.

/**
 * InstantWeatherServiceSoapBindingImpl.java
 *
 * This file was auto-generated from WSDL
 * by the Apache Axis 1.3 Aug 31, 2005 (10:05:42 GMT+00:00) WSDL2Java emitter.
 */

package localhost.wstest.InstantWeatherService_jws;

public class InstantWeatherServiceSoapBindingImpl implements localhost.wstest.InstantWeatherService_jws.InstantWeatherService{
    public float getCurrentTemperature(java.lang.String cityName) throws java.rmi.RemoteException {
        return -3;
    }

    public java.lang.String getCurrentWeather(java.lang.String cityName) throws java.rmi.RemoteException {
        return null;
    }

    public float[] getTemperaturePrediction(java.lang.String cityName, int howManyDays) throws java.rmi.RemoteException {
        return null;
    }

}

Do tejto triedy môžeme doplniť príslušný výkonný kód.

Okrem príslušných súborov nám WSDL2Java vygeneroval aj deskriptor nasadenia webovej služby (deploy.wsdd) a odstránenie (undeploy.wsdd), ktorý môžeme použiť pri generovaní súboru server-config.wsdd.

6. Pokročilé techniky

6.1. Generovanie tried pomocou WSDL2Java do slušnej adresárovej štruktúry.

Štandardne generuje WSDL2JAVA triedy do adresárových štruktúr, ktoré zodpovedajú menným priestorom vo WSDL. Často je ale požiadavkou generovať triedy do vopred daných adresárov. Na to slúži parameter --fileNStoPkg. Tento parameter udáva cestu k property súboru, v ktorom sú mapovania menných priestorov na balíčky v Jave. Použitie je potom nasledovné:

java -cp %AXISCLASSPATH%;. org.apache.axis.wsdl.WSDL2Java --output . --fileNStoPkg NStoPkg.properties adresaURL 

Súbor NStoPkg.properties vyzerá napr. nasledovne:

# 
# mapovanie namespaceov vo WSDL na packages v Jave pouzivane pri generovani klientskeho kodu z WSDL
#
http\://localhost\:8080/wstest/services/InstantWeatherService_jws=axis.wstest.ws

Tento súbor musí spĺňať všetky obmedzenia vlastné properties súborom jazyka Java. Musí byť uložený v kódovaní iso-8859-1 a nepovolené znaky musí mať ošetrené pomocou spätnej lomky (najčastejšie je problémom znak dvojbodky.

6.2. Konfigurácia Axisu

Konfigurácia Axisu prebieha na tradičných dvoch stranách: na serveri a v klientoch.

6.2.1. Konfigurácia servera

Konfigurácia servera sa nachádza v XML súbore server-config.wsdd, ktorý sa nachádza v adresári WEB-INF webovej aplikácie. Ako sme spomínali v častiach o konfiguračnom súbore webservera, tento súbor obsahuje informácie zo súborov deploy.wsdd nasadených webových služieb.

Príklad konfiguračného súboru je napr.

<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
 <globalConfiguration>
  <parameter name="sendMultiRefs" value="true"/>
  <parameter name="disablePrettyXML" value="true"/>
  <parameter name="adminPassword" value="admin"/>
  <parameter name="attachments.Directory" value="c:\Projects\WSTest\web\WEB-INF\attachments"/>
  <parameter name="dotNetSoapEncFix" value="true"/>
  <parameter name="enableNamespacePrefixOptimization" value="false"/>
  <parameter name="sendXMLDeclaration" value="true"/>
  <parameter name="sendXsiTypes" value="true"/>
  <parameter name="attachments.implementation" value="org.apache.axis.attachments.AttachmentsImpl"/>
  <requestFlow>
   <handler type="java:org.apache.axis.handlers.JWSHandler">
    <parameter name="scope" value="session"/>
   </handler>
   <handler type="java:org.apache.axis.handlers.JWSHandler">
    <parameter name="scope" value="request"/>
    <parameter name="extension" value=".jwr"/>
   </handler>
  </requestFlow>
 </globalConfiguration>
 <handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/>
 <handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/>
 <handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/>
 <service name="AdminService" provider="java:MSG">
  <parameter name="allowedMethods" value="AdminService"/>
  <parameter name="enableRemoteAdmin" value="false"/>
  <parameter name="className" value="org.apache.axis.utils.Admin"/>
  <namespace>http://xml.apache.org/axis/wsdd/</namespace>
 </service>
 <service name="Version" provider="java:RPC">
  <parameter name="allowedMethods" value="getVersion"/>
  <parameter name="className" value="org.apache.axis.Version"/>
 </service>
 <transport name="http">
  <requestFlow>
   <handler type="URLMapper"/>
   <handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/>
  </requestFlow>
  <parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler"/>
  <parameter name="qs:wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/>
  <parameter name="qs.list" value="org.apache.axis.transport.http.QSListHandler"/>
  <parameter name="qs.method" value="org.apache.axis.transport.http.QSMethodHandler"/>
  <parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler"/>
  <parameter name="qs.wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/>
 </transport>
 <transport name="local">
  <responseFlow>
   <handler type="LocalResponder"/>
  </responseFlow>
 </transport>
</deployment>

Tento konfiguračný súbor ukazuje, že máme nasadené dve webové služby (AdminService a Version), dva režimy transportu (http a local). V úvode sú dôležité konfiguračné direktívy.

Note

Z praktického hľadiska patrí medzi tie dôležitejšie direktíva sendMultiRefs. Tá má umožňovať optimalizáciu elementov v XML dokumente. (V prípade, že sa prenáša napr. pole, ktoré obsahuje 15 rovnakých prvkov sa pri zapnutej optimalizácii namiesto pätnásťnásobného duplikovania rovnakého elementu (ktorý môže byť pomerne rozsiahly) odošle pole s 15 referenciami na daný prvok a ten sa potom uvedie v SOAP správe len raz.). Žiaľ, podľa našich skúseností je táto funkcia implementovaná nespoľahlivo a preto je pravdepodobne lepšie ju vypnúť (nastaviť hodnotu sendMultirefs na false).

Note

Bližší popis jednotlivých elementov WSDD súborov možno nájsť napr. na http://www.osmoticweb.com/axis-wsdd/

6.2.2. Konfigurácia klienta

Konfiguráciu klienta riadi súbor, ktorý je analogický konfiguračnému súboru servera. Súbor sa nazýva client-config.wsdd a je ho potrebné uložiť do CLASSPATH klienta. Ak klient pri spustení nenájde v CLASSPATH užívateľov konfiguračný súbor, použije implicitnú verziu uloženú vo vnútri Axisu.

Príklad konfiguračného súboru:

<deployment name="defaultClientConfig"
            xmlns="http://xml.apache.org/axis/wsdd/"
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
 <globalConfiguration>
   <parameter name="disablePrettyXML" value="true"/>
   <parameter name="sendMultiRefs" value="false"/>
   <parameter name="enableNamespacePrefixOptimization" value="false"/>
 </globalConfiguration>
 <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/>
 <transport name="local" pivot="java:org.apache.axis.transport.local.LocalSender"/>
 <transport name="java" pivot="java:org.apache.axis.transport.java.JavaSender"/> 
</deployment>

Note

Analogicky ako v prípade servera máme z praxe skúsenosti s nastavením direktívy sendMultiRefs na false.

6.3. Vlastné mapovanie dátových typov

Axis automaticky podporuje ,,od prírody" mapovanie rôznych dátových typov na XML. V praxi možno bez problémov využívať

  • primitívne dátové typy

  • polia zložené z primitívnych dátových typov

  • triedy zodpovedajúce odporúčaniu Java Beans obsahujúce vo svojich dátových členoch primitívne dátové typy a Java Beans

  • niektoré ďalšie dátové typy z Javy (java.util.Date).

V praxi však niekedy nastane situácia, keď máme vlastnú triedu, ktorá sa namapuje na XML nesprávne, prípadne sa vôbec nenamapuje. Typickým prípadom sú objekty typu java.sql.Timestamp, ktoré sa serializujú v prípade kontajnera Tomcat správne. Problém však nastáva pri použití Axisu v aplikačnom serveri WebSphere. V ňom sa z neznámych dôvodov objekty serializujú a deserializujú nesprávne.[2] V takomto prípade je potrebné si vytvoriť vlastné mapovanie objektov. To pozostáva z dvoch procesov:

  • mapovanie štruktúry triedy na XML schému (XSD)

  • samotné mapovanie inštancií tried na XML v správe SOAP.

Samotný proces mapovania objektov je v Axise realizovaný pomocou dvoch párov tried: jeden pár (trieda a jej továreň) slúži na serializáciu objektu, druhý na deserializáciu.

6.3.1. Serializátor

Príklad serializátora:

public class SQLTimestampSerializer implements SimpleValueSerializer {

          /* Získa stringovú reprezentáciu objektu, v tomto prípade volaním metódy toString().
           *  
           * @see org.apache.axis.encoding.SimpleValueSerializer#getValueAsString(java.lang.Object, org.apache.axis.encoding.SerializationContext)
           */
          public String getValueAsString(Object value, SerializationContext context) {
                    Timestamp timestamp = (Timestamp) value;
                    return Long.toString(timestamp.getTime());
                  }

          /* Serializuje timestamp do stringu pomocou metódy getValueAsString()
           *  
           * @see org.apache.axis.encoding.Serializer#serialize(javax.xml.namespace.QName, org.xml.sax.Attributes, java.lang.Object, org.apache.axis.encoding.SerializationContext)
           */
          public void serialize(QName name, Attributes attributes, Object value,
              SerializationContext context) throws IOException {

                    context.startElement(name, attributes);
                    context.writeString(getValueAsString(value, context));
                    context.endElement();
                  }

          /* Zapise prislusnu schemu do WSDL
           * @see org.apache.axis.encoding.Serializer#writeSchema(java.lang.Class, org.apache.axis.wsdl.fromJava.Types)
           */
          public Element writeSchema(Class javaType, Types types) throws Exception {
                    /*
                      Schema vyzera nasledovne:
                      <complexType>
                        <all>
                           <element name="time" type="xsd:long/>
                        </all>
                      </complexType>     
                    */

                    Element complexType = types.createElement("complexType");
                    Element sequence = types.createElement("all");
                    Element element = types.createElement("element");
                    element.setAttribute("name", "time");
                    element.setAttribute("type", "xsd:long");

                    complexType.appendChild(sequence);
                    sequence.appendChild(element);

                    return complexType;    
                  }

          /* 
           * @see javax.xml.rpc.encoding.Serializer#getMechanismType()
           */
          public String getMechanismType() {
                    return Constants.AXIS_SAX;
          }
}

Trieda zabezpečuje oba spomínané procesy: pomocou metódy serialize sa pomocou známych metód manipulácie s XML uzlami vloží do SOAP správy serializovaná podoba objektu. Metóda writeSchema zase zabezpečuje zápis XML schémy príslušnej triedy do WSDL dokumentu.

Náš serializátor je primitívny. z objektu typu java.sql.Timestamp jednoducho serializuje jeho milisekundovú hodnotu (typu long) a uloží ju do SOAP správy ako reťazec.

Note

Zdalo by sa, že prirodzenejším spôsobom je vytvoriť schému použitím elementov restriction a base v uložených v elemente simpleType. Zdá sa však, že Axis namapuje takýto ,,obmedzený" dátový typ na základný typ v Jave (bez ohľadu na to, že v schéme je definované obmedzenie). Napr restriction s bázou xsd:long sa namapuje priamo na long, to však asi nie je želaný efekt.

6.3.2. Deserializátor

Deserializátor je jednoduchší. Nemusí sa totiž starať o tvorbu elementov pre schému triedy vo WSDL. Jeho úlohou je len samotná deserializácia XML a vytvorenie objektu z elementov v SOAP správe.

public class SQLTimestampDeserializer extends SimpleDeserializer {

          public SQLTimestampDeserializer(Class javaType, QName xmlType) {
                    super(javaType, xmlType);
                  }

          /* 
           * Konvertuje serializovanu hodnotu reprezentovanu Stringom na Timestamp.
           * 
           * @see org.apache.axis.encoding.ser.SimpleDeserializer#makeValue(java.lang.String)
           */
          public Object makeValue(String source) throws Exception {
                    long l = Long.parseLong(source);
                    return new Timestamp(l);
          }
}

Jediná užitočná metóda konvertuje reťazec v správe na typ long a pomocou neho vytvorí inštanciu triedy Timestamp.

6.3.3. Továrne pre serializátory a deserializátory.

Každému serializátoru a deserializátoru zodpovedá jedna továreň. Našťastie, samotné továrne sú veľmi jednoduché. Navyše, továrne pre serializátory a deserializátory sú rovnaké, líšia sa len v názvoch a v názvoch tried, ktoré vytvárajú.

public class SQLTimestampDeserializerFactory extends BaseDeserializerFactory {

          public SQLTimestampDeserializerFactory(Class javaType, QName xmlType) {
                    super(SQLTimestampDeserializer.class, xmlType, javaType);
                  }
}

6.3.4. Konfigurácia serializácie a deserializácie na strane servera

Po skompilovaní a sprístupnení tried serveru však ešte musíme upovedomiť Axis, že má na príslušné triedy používať náš vlastný serializátor. To urobíme pomocou elementu typemapping, ktorý môžeme vložiť buď do deskriptora nasadenia príslušnej webovej služby, alebo do konfiguračného súboru servera. Samozrejme nezabudneme v prípade potreby webovú službu nasadiť znovu.

Príslušný element bude pre náš príklad vyzerať nasledovne:

<typeMapping 
  xmlns:kb="urn:java-sql"
  qname="testws:timestamp"
   languageSpecificType="java:java.sql.Timestamp"
   serializer="novotnyr.ws.SQLTimestampSerializerFactory"
   deserializer="novotnyr.ws.SQLTimestampDeserializerFactory"
   encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
/>

Ak sa všetko podarilo správne, môžeme vygenerovať pre nasadenú webovú službu vygenerovať klientské triedy. Dôkazom toho, že Axis na strane servera našiel naše vlastne serializátory a deserializátory, by malo byť zobrazenie WSDL pre príslušnú webovú službu v prehliadači. Ak je všetko v poriadku, vo zobrazenom WSDL by mala XML schéma elementu pre java.sql.Timestamp zodpovedať schéme, ktorú sme si skonštruovali v metóde writeSchema v serializátore.

6.3.5. Konfigurácia serializácie a deserializácie na strane klienta

Situácia na strane klienta je v tomto prípade komplikovanejšia. Pri generovaní tried na strane klienta totiž nie je možné nastaviť vlastné mapovanie typov. Axis automaticky pridelí triedam, ktoré nevie namapovať štandardný serializátor/deserializátor určený pre Java Beans. Žiaľ, tento postup je nesprávny - Java Beans serializátor totiž nevie interpretovať hodnoty, ktoré do SOAP správy uložil SQLTimestampSerializer.

Preto je potrebné ručne vygenerovanú klientskú triedu upraviť ručne a nahradiť nesprávny serializátor/deserializátor korektnou verziou. V konštruktore triedy xxxxxxSoapBindingStub nájdeme kód, ktorý asociuje triedu java.sql.Timestamp s príslušnými továrňami. Korektná verzia kódu má byť

qName = new javax.xml.namespace.QName("urn:java-sql", "timestamp");
cachedSerQNames.add(qName);
cls = java.sql.Timestamp.class;
cachedSerClasses.add(cls);
cachedSerFactories.add(new SQLTimestampSerializerFactory(cls, qName));
cachedDeserFactories.add(new SQLTimestampDeserializerFactory(cls, qName));

Použitie klienta ostáva nezmenené.



[1] Jeden spôsob, ako ho obísť, je popísaný na IBM developerWorks, http://www-128.ibm.com/developerworks/library/ws-tip-roundtrip1.html

[2] Pri používaní Axisu vo WebSphere a deserializácii SOAP správy, ktorá obsahovala java.sql.Timestamp sme sa stretli so zaujímavou hláškou Cannot serialize class long.