<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.novotnyr</groupId>
<artifactId>jaxws-java-util-time-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>(3)
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>(1)
<artifactId>jaxws-rt</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.woodstox</groupId>(2)
<artifactId>woodstox-core</artifactId>
<version>6.4.0</version>
</dependency>
</dependencies>
</project>
Vytvorme SOAP web service pomocou JAX-WS 4.0 (Jakarta XML Web Services) a Eclipse Metro.
Ak používame základné dátové typy, všetko je v poriadku.
Vo chvíli, keď začneme používať dátumy a časy z knižnice java.time
, nastanú problémy.
Príprava pom.xml
Pripravme si mavenovský projekt, kde dodajme závislosti na Metre a podporu pre Javu 17.
1 | Dodajme závislosť na Eclipse Metro. |
2 | Explicitný woodstox-core uvádzame kvôli bezpečnostnej chybe v Eclipse Metro 4.0, kde použijeme novšiu verziu tejto knižnice s opravenou chybou. |
3 | Použijeme Javu 17. |
Webservis v SOAP
Pridajme si ukážkový SOAPový webservis, vrátane metódy main()
:
package com.github.novotnyr.soap;
import jakarta.jws.WebService;
import jakarta.xml.ws.Endpoint;
import java.time.LocalDateTime;
@WebService
public class TimeService {
public LocalDateTime getNow() { (1)
return LocalDateTime.now();
}
public static void main(String[] args) {
Endpoint.publish("http://localhost:18888/ws/now", new TimeService()); (2)
}
}
1 | Metóda vracia objekt s dátumom a časom java.time.LocalDateTime . |
2 | Pripravíme si endpoint a publikujeme ho. |
Ako vyzerá WSDL?
WSDL a jeho stav? Jedným slovom: nie veľmi dobre.
Pozrime sa na adresu http://localhost:18888/ws/now?xsd=1 a uvidíme zvláštnu schému:
<xs:complexType name="getNowResponse">
<xs:sequence>
<xs:element name="return" type="tns:localDateTime" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="localDateTime" final="extension restriction">
<xs:sequence/>
</xs:complexType>
Operácia getNowResponse
vracia akýsi XML dátový typ localDateTime
, ktorá je definovaný ako prázdna sekvencia.
JAX-WS síce vygenerovalo WSDL, ale nikto ho nevie normálne spracovať. |
Ak by sme skúsili požiadavku v SoapUI, dostaneme odpoveď:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getNowResponse xmlns:ns2="http://soap.novotnyr.github.com/">
<return/> (1)
</ns2:getNowResponse>
</S:Body>
</S:Envelope>
1 | Element return rozhodne neobsahuje nič užitočné, hoci by sme čakali aktuálny dátum. |
JAX-WS nevie normálne pracovať s objektami z balíčka java.time .
|
Knižnica threeten-jaxb
, dostavte sa do projektu!
Prevod medzi objektami a XML v Jakarta XML Web Services zabezpečuje samostatná špecifikácia JAXB (Jakarta XML Binding). Súčasťou Metra je aj jej referenčná implementácia. |
Knižnica threeten-jaxb
predstavuje XML adaptéry pre konverziu tried, ktoré sa nedostali do jadra referenčnej implementácie JAXB`.
Špeciálne je tam podpora pre dátumy a časy.
Dodajme do pom.xml
závislosť:
<dependency>
<groupId>io.github.threeten-jaxb</groupId>
<artifactId>threeten-jaxb-core</artifactId>
<version>2.1.0</version>
</dependency>
Plán rekonštrukcie
Na to, aby sme to rozbehali korektne, potrebujeme 4 kroky:
-
pridať závislosť na Three Ten: to sme spravili
-
vytvoriť vlastnú doménovú triedu, v ktorej vrátime aktuálny čas
-
vracať z SOAP operácie doménovú triedu
-
pripraviť anotáciu, ktorá prevedie všetky
LocalDateTime
na normálnu konštrukciu v XML
Vlastná doménová trieda
Vytvorme vlastnú doménovú triedu.
package com.github.novotnyr.soap;
import java.time.LocalDateTime;
public class CurrentLocalDateTime {
private LocalDateTime dateTime;
public CurrentLocalDateTime() {
this.dateTime = LocalDateTime.now();
}
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
}
Trieda je úplne bežná, neobsahuje nič špeciálne.
Úprava operácie v SOAP webservise
Operácia v SOAPovej webservice nech vracia našu doménovú triedu:
@WebService
public class TimeService {
public CurrentLocalDateTime getNow() { (1)
return new CurrentLocalDateTime();
}
}
1 | Zrazu vraciame doménový objekt. |
Zapojenie XML adaptéra na prevod
V balíčku com.github.novotnyr.soap
vytvorme súbor package-info.java
, kde zavedieme pravidlo pre prevody medzi LocalDateTime
cez adaptér LocalDateTimeXmlAdapter
z knižnice ThreeTen na reťazce.
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(value = LocalDateTimeXmlAdapter.class, (1)
type = LocalDateTime.class) (2)
})
package com.github.novotnyr.soap;
import io.github.threetenjaxb.core.LocalDateTimeXmlAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import java.time.LocalDateTime;
1 | Zavedieme adaptér LocalDateTimeXmlAdapter.class , ktorý sa použije na serializáciu a deserializáciu. |
2 | Budeme pracovať s objektami typu LocalDateTime . |
Anotácia je nad celým balíčkom s našim serverom. |
Reštart webservisy
Reštartnime webservisu a pozrime sa, ako vyzerá XML schéma.
Navštívme opäť http://localhost:18888/ws/now?xsd=1 a uvidíme:
<xs:complexType name="getNowResponse">
<xs:sequence>
<xs:element name="return" type="tns:currentLocalDateTime" minOccurs="0"/> (1)
</xs:sequence>
</xs:complexType>
<xs:complexType name="currentLocalDateTime">
<xs:sequence>
<xs:element name="dateTime" type="xs:string" minOccurs="0"/> (2)
</xs:sequence>
</xs:complexType>
1 | Výstupný element je teraz typu currentLocalDateTime , ktorý sa rozoberie v ďalšom kroku. |
2 | Tento dátový typ v XML schéme obsahuje jediný atribút: dateTime typu String , ktorý môže byť vynechaný (minOccurs=0 ). |
Vďaka adaptéru sa budú dátumy a časy typu LocalDateTime prevádzať na reťazce v XML.
|
Ak aktualizujeme definíciu SOAPovej služby v SoapUI, uvidíme inú odpoveď:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getNowResponse xmlns:ns2="http://soap.novotnyr.github.com/">
<return>
<dateTime>2022-10-30T22:26:07.848894</dateTime> (1)
</return>
</ns2:getNowResponse>
</S:Body>
</S:Envelope>
1 | Dátumy a čas už chodia ako reťazce vo formáte ISO-8601. |
Ako by vyzeral klient?
Ak by sme si vygenerovali SOAP klienta v Jave na základe WSDL, uvideli by sme triedu, kde je dátum a čas reprezentovaný ako reťazec:
package com.github.novotnyr.soap;
public class CurrentLocalDateTime {
protected String dateTime; (1)
public String getDateTime() {
return dateTime;
}
public void setDateTime(String value) {
this.dateTime = value;
}
}
1 | Dátum a čas je reprezentovaný ako reťazec.
Je to presne preto, že v XML schéme máme uvedený dátový typ xsd:string . |
Ako upraviť XML schému?
Vieme upraviť serverovský kód tak, aby v schéme XML vo WSDL vracal dátum a čas? Veď existuje primitívny dátový typ dateTime!
Na toto musíme dodať ďalšiu anotáciu do serverovskej doménovej triedy.
import jakarta.xml.bind.annotation.XmlSchemaType;
import java.time.LocalDate;
import java.time.LocalDateTime;
public class CurrentLocalDateTime {
//...
@XmlSchemaType(name = "dateTime", type = LocalDate.class) (1)
public LocalDateTime getDateTime() {
//...
}
//...
1 | Do gettera doménového objektu dodáme anotáciu @XmlSchemaType .
Uvedieme dve vlastnosti:
|
Anotáciu dávame nad getter, nie nad inštančnú premennú, pretože inak uvidíme chybu s duplicitnou deklaráciou atribútu dateTime .
|
Reštartnime SOAP server a pozrime si schému pre XSD na http://localhost:18888/ws/now?xsd=1.
Uvidíme pozitívne zmeny:
<xs:complexType name="currentLocalDateTime">
<xs:sequence>
<xs:element name="dateTime" type="xs:dateTime" minOccurs="0"/> (1)
</xs:sequence>
</xs:complexType>
1 | Element dateTime je už zo štandadného primitívneho typu zo XML schémy xs:dateTime a nie reťazec! |
Pregenerovanie XML klienta
Ak pregenerujeme klienta cez JAX-WS 4.0, uvidíme zmeny:
package com.github.novotnyr.soap;
import javax.xml.datatype.XMLGregorianCalendar;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlSchemaType;
import jakarta.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "currentLocalDateTime", propOrder = {
"dateTime"
})
public class CurrentLocalDateTime {
@XmlSchemaType(name = "dateTime") (2)
protected XMLGregorianCalendar dateTime; (1)
public XMLGregorianCalendar getDateTime() {
return dateTime;
}
public void setDateTime(XMLGregorianCalendar value) {
this.dateTime = value;
}
}
1 | Generátor klienta vytvoril premennú typu XMLGregorianCalendar |
2 | Premennú namapoval na dátový typ dateTime zo XML schémy. |
V kóde potom vieme previesť XMLGregorianCalendar
na LocalDateTime
:
//...zavoláme webservis
CurrentLocalDateTime currentLocalDateTime = timeService.getNow();
XMLGregorianCalendar dateTime = currentLocalDateTime.getDateTime(); (1)
LocalDateTime localDateTime = dateTime
.toGregorianCalendar()
.toZonedDateTime()
.toLocalDateTime(); (2)
System.out.println(localDateTime);
1 | Získame surový XML objekt s dátumom a časom. |
2 | Prevedieme ho na LocalDateTime . |
Ak by sme sa chceli zbaviť komplikovaného ručného prevodu, museli by sme použiť mechanizmus JAXB Bindings, resp. XJC Bindings, ktorý je ale už mimo záber tohto článku. |