<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
Úvod
Navrhli sme webovú službu pre SOAP pomocou contract-first? Máme teda kontrakt reprezentovaný súborom WSDL?
Poďme vygenerovať kód pre server na platforme Java 11!
Použijeme na to štandard JAX-WS 2.0 a jeho tradičnú implementáciu Metro.
Java 11 je prvá verzia, ktorá už neobsahuje v základnej knižnici implementáciu JAX-WS 2.0. Zároveň je to posledná verzia, na ktorej referenčná implementácia Metro zatiaľ funguje. Keďže technická podpora tejto Javy končí v roku 2026, dúfajme, že sa to dovtedy podarí zladiť. |
Príklad a štruktúra dát
Vybudujme si službu pre objednanie parkovania. V požiadavke pošlem EČV vozidla a parkovaciu zónu a v odpovedi získame identifikátor parkovacieho lístka a dátum platnosti.
Pripravíme si:
-
schému pre správy v tvare XSD (XML Schema)
-
popisný súbor WSDL
-
mavenovský projekt s
pom.xml
XML schéma
Vezmeme schému pre správy pomocou XML Schemy.
Kompletný súbor schémy parking.xsd nájdeme na konci článku, resp. na GitHube[https://github.com/novotnyr/kopr-wsdl-server-2021/blob/main/src/main/resources/parking.xsd]
|
WSDL súbor
Vezmeme si hotový súbor pre WSDL.
Kompletný súbor schémy parking.wsdl nájdeme na konci článku, resp. na GitHube[https://github.com/novotnyr/kopr-wsdl-server-2021/blob/main/src/main/resources/parking.wsdl]
|
Projekt pre serverovskú časť
Založme si nový projekt založený na buildovacom nástroji Maven.
V pom.xml
definujeme:
-
verziu kódu pre kompilátor: použijeme Javu 11
-
závislosť na knižnici Metro
-
Maven plugin pre generovanie kódu servera
Verzia kompilátora
Dodajme nasledovné projektové vlastnosti:
Závislosť na knižnici Metro
Pridajme si závislosť na knižnici Metro:
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.3</version>
</dependency>
Maven Plugin
V starších verziách Javy sme mali k dispozícii nástroj wsimport
, ktorý však v takejto verzii Metra už nie je dostupný.
Pomocou neho sme dokázali vygenerovať kód pre server ale i klienta.
Jeho funkcionalitu nahraďme pluginom pre Maven:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.6</version>
<configuration>
<wsdlDirectory>${project.basedir}/src/main/resources</wsdlDirectory>(2)
<wsdlFiles>
<wsdlFile>parking.wsdl</wsdlFile>(1)
</wsdlFiles>
<wsdlLocation>/parking.wsdl</wsdlLocation>(3)
</configuration>
</plugin>
1 | Definujme odkaz na názov WSDL, z ktorého vygenerujeme kód. |
2 | Uvedieme cestu k adresáru v projekte, z ktorého vytiahneme WSDL súbor. |
3 | Aby sa v generovanom kóde zbytočne neobjavovali celé cesty k lokálnemu súborovému systému používateľa, uveďme explicitnú adresu ku kódu. |
Ak umiestnime WSDL súbor medzi prostriedky (resources), objaví sa vo výslednom JAR archíve a teda v ceste |
Lokácia wsdlLocation s lomkou na začiatku znamená, že WSDL súbor pri budovaní klientskeho kódu sa vytiahne z cesty CLASSPATH .
|
Celý pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sk.upjs.ics.kopr</groupId>
<artifactId>kopr-parking-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.6</version>
<configuration>
<wsdlDirectory>${project.basedir}/src/main/resources</wsdlDirectory>
<wsdlFiles>
<wsdlFile>parking.wsdl</wsdlFile>
</wsdlFiles>
<wsdlLocation>/parking.wsdl</wsdlLocation>
</configuration>
</plugin>
</plugins>
</build>
</project>
Rozmiestnenie súborov
Oba súbory — parking.wsdl
aj parking.xsd
umiestnime do src/main/resources
, pretože tak sme to nastavili v pom.xml
v adresári wsdlDirectory
.
Zostavenie projektu
Zdrojáky pre server nagenerujeme nasledovne:
mvn jaxws:wsimport package
Ak spúšťame Maven zo shellu, dajme si pozor, aby Java, v ktorej sa spúšťal Maven, bola verzie 11 — nie novšej (Metro tam nefunguje) a nie staršej (kompilácia očakáva verziu 11)! |
Zdrojáky sa nagenerujú do priečinka:
target/generated-sources/wsimport/
Pre naše WSDL sa vygeneruje nasledovná štruktúra:
org
└── example
└── parking
├── ObjectFactory.java
├── package-info.java
├── ParkingPortType.java
├── ParkingRequest.java
├── ParkingServices.java
└── ParkingTicket.java
Názvy priečinkov / balíčkov sa odvodia z targetNamespace vo WSDL, či schéme.
|
Konfigurácia projektu
Adresár target/generated-sources/wsimport/
je užitočné potrebné pridať do projektu ako miesto so zdrojovými kódmi.
V Eclipse je to pravý klik na adresár v strome Package List, a z kontextového menu Build Path | Use as Source Folder. |
Implementácia servera
Implementácia servera znamená vytvorenie triedy, ktorá bude implementovať triedu org.example.ParkingPortType
.
package sk.upjs.ics.kopr;
import javax.jws.WebService;
import org.example.parking.ParkingPortType;
import org.example.parking.ParkingRequest;
import org.example.parking.ParkingTicket;
@WebService(endpointInterface = "org.example.parking.ParkingPortType") (2)
public class ParkingServiceImpl implements ParkingPortType { (1)
public ParkingTicket getTicket(ParkingRequest part) {
// implementacia metody
return new ParkingTicket(); (3)
}
}
1 | Implementujeme interfejs, ktorý vznikol generovaním kódu. |
2 | V atribúte endpointInterface uvedieme interfejs s kontraktom webovej služby pre JAX-WS. |
3 | Pripravíme implementáciu metódy — v tomto prípade veľmi jednoduchú. |
Interfejs
Ak vynecháme atribút |
Spustenie servera
Server môžeme spustiť jednoducho:
public static void main(String[] args) throws Exception {
Endpoint.publish("http://localhost:8888/parking", new ParkingServiceImpl());
}
Toto je klasický jednoduchý spôsob, ktorý ale vygeneruje WSDL automaticky na základe zdrojového kódu.
Spustenie servera s naším WSDL
Ak chceme použiť existujúce WSDL a to zverejniť klientovi, musíme prispôsobiť nasadenie služby.
Budeme predpokladať, že v CLASSPATH máme aj WSDL (parking.wsdl
) aj XSD (parking.xsd
).
public static void main(String[] args) throws Exception {
List<Source> metadata = new ArrayList<Source>(); (1)
var wsdlSource = new StreamSource(ParkingServiceImpl.class.getResourceAsStream("/parking.wsdl")); (2)
wsdlSource.setSystemId("http://www.example.org/parking(parking.wsdl"); (3)
metadata.add(wsdlSource); (4)
var xsdSource = new StreamSource(ParkingServiceImpl.class.getResourceAsStream("/parking.xsd"));(2)
xsdSource.setSystemId("http://www.example.org/parking/parking.xsd");(3)
metadata.add(xsdSource);(4)
var filter = new HashMap<String, Object>();
filter.put(Endpoint.WSDL_SERVICE, new QName("http://www.example.org/parking", "ParkingServices")); (5)
filter.put(Endpoint.WSDL_PORT, new QName("http://www.example.org/parking", "ParkingService")); (6)
var endpoint = Endpoint.create(new ParkingServiceImpl()); (7)
endpoint.setProperties(filter); (8)
endpoint.setMetadata(metadata); (9)
endpoint.publish("http://localhost:8888/parking"); (10)
}
1 | Musíme si pripraviť zoznam pre metadáta: teda WSDL a XSD. |
2 | Vytvoríme objekt Source reprezentujúci XML súbor pre WSDL či schému.
Tento objekt načítame z CLASSPATH : to je reprezentované lomkou v argumente getResourceAsStream . |
3 | Každý takýto objekt Source potrebuje jednoznačný identifikátor v tvare URL s použitím protokolu HTTP alebo file .
Keďže na konkrétnej hodnote nezáleží, vytvoríme si vymyslený ukážkový identifikátor. |
4 | Súbor dodáme do metadát. |
5 | Pomocou properties definujeme filter na službu service a port z WSDL, na ktorý použijeme naše metadáta, teda na ktorom zmeníme WSDL a XML schému XSD.
Pomocou WSDL_SERVICE určíme kvalifikované meno (menný priestor a názov elementu) pre element wsdl:service z WSDL. |
6 | Pomocou WSDL_PORT určíme kvalifikované meno (menný priestor a názov elementu) pre element wsdl:port z WSDL. |
7 | Vytvoríme nový endpoint nad našou triedou s implementáciou servera. |
8 | Nastavíme filter cez properties. |
9 | Nastavíme nové metadáta služby. |
10 | Endpoint vypublikujeme na danej adrese. |
Backend teraz môžeme spustiť ako Java aplikáciu!
Na adrese http://localhost:8888/parking?wsdl uvidíme našej ručne písané WSDL!
|
Pri deklarovaní filtra (properties) sa musia kvalifikované názvy presne zhodovať s názvami vo WSDL. Menný priestor v kvalifikovanom mene |
Výsledný repozitár
Výsledný repozitár je na GitHube, v repozitári novotnyr/kopr-wsdl-server-2021.
Zdroje
Zdrojáky
XML Schéma
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/parking"
elementFormDefault="qualified">
<element name="parkingRequest">
<complexType>
<sequence>
<element name="carId" type="string" />
<element name="zone" type="int" />
</sequence>
</complexType>
</element>
<element name="parkingTicket">
<complexType>
<sequence>
<element name="id" type="string" />
<element name="validUntil" type="dateTime" />
</sequence>
</complexType>
</element>
</schema>
WSDL
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions
name="parking"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
targetNamespace="http://www.example.org/parking"
xmlns:tns="http://www.example.org/parking"
>
<wsdl:types>
<xsd:schema targetNamespace="http://www.example.org/parking">
<xsd:include schemaLocation="parking.xsd" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="request">
<wsdl:part name="part" element="tns:parkingRequest" />
</wsdl:message>
<wsdl:message name="response">
<wsdl:part name="part" element="tns:parkingTicket" />
</wsdl:message>
<wsdl:portType name="ParkingPortType">
<wsdl:operation name="getTicket">
<wsdl:input message="tns:request" />
<wsdl:output message="tns:response" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="ParkingBinding" type="tns:ParkingPortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<wsdl:operation name="getTicket">
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="ParkingServices">
<wsdl:port name="ParkingService" binding="tns:ParkingBinding">
<soap:address location="http://localhost:8888/parking"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>