16 Kasım 2013 Cumartesi

JAX-WS (Java API for XML Web Services)

Aşağıda JAX-WS ile ilgili aldığım notlar var.

JAX-WS Anotasyonları
Bu konuyla ilgili olarak JAX-WS annotations başlıklı yazıya göz atmak faydalı. Yazıda geçen JSR 181 ile ilgili bilgi ise aşağıda. Daha yeni olan JSR 224 ile ilgili bilgi ise burada.

Servis ne Anlama Geliyor?
Servis programlama dünyasında bir çok farklı anlama gelir. Örneğin çok katmanlı mimarilerde, kullanıcı arayüzü ve veritabanı arasındaki, iş mantığını içeren kod parçası anlamına gelir. Bu tür yapılarda servisler veritabanına yapılan transaction'ı da kontrol ederler.

SOAP çağrılarında ise servis fiziksel bir uç nokta ve API'ler gibi düşünülmeli. Aşağıda bu API'lerin nasıl çalıştığı ve dışarıya nasıl açıldıkları ile ilgili bilgiler var.

JSR 181
JAX-WS ile JSR 181 ile kullanılması kararlaştırılan anotasyonlar kullanılıyor. Bu anotasyonlardan bazıları
@WebService, @WebMethod, @WebParam vs. Anotasyonlar javax.jws paketinde mevcut.


javax.jws.WebService ve serviceName

Eğer bir web servisine aşağıdaki gibi isim verilirse 
@WebService(name = "myService")
üretilen wsdl dosyasında aşağıdaki gibi bir XML üretiliyor

<service name="myService">
    <port name="myServicePort" binding="tns:myServicePortBinding">
    <soap:address location="http://localhost:8080/myApp/webservice"/>
    </port>
</service>
Burada kullanılan servis ismine ?wsdl eklenip sorgulama yapılırsa XML çıktısı görülebilir.

Bazen wsdl dosyasında wsdl: şeklinde kullanım da görülebilir. O zaman XML aşağıdaki gibi olacaktı.

<wsdl:service name="myService">
    <wsdl:port name="myServicePort" binding="tns:myServicePortBinding">
    <soap:address location="http://localhost:8080/myApp/webservice"/>
    </wsdl:port>
</wsdl:service>
Eğer serviceName atanmazsa, JAX-WS sınıfın ismine "Service" kelimesini ekliyor. Örnek:
Bu sınıf için wsdl dosyasını adresi aşağıdaki gibidir.Sınıfın ismine yapılan ektentiye dikkat.
http://localhost:9080/service/ServiceImplService?wsdl
javax.jws.WebService ve portName
Eğer WebService'e portName vererek aynı arayüzden türeyen ancak farklı çalışan sınıflar yazabiliriz. Örnek:
Üretilen wsdl aşağıdaki gibi olacaktır.

javax.jws.WebMethod

Eğer bir metodun dışarıya açılmasını istemiyorsak aşağıdaki gibi yapıyoruz.
@WebMethod(exclude = true)
javax.jws.OneWay

Eğer bir metodun geriye döneceği bir cevap yoksa onu OneWay olarak işaretlersek WSDL dosyasında port'a bağlı olan output tagleri üretilmez. Örnek:
@WebMethod
@OneWay
public void myCall ()...
javax.jws.soap.SOAPBinding

@WebService
@SOAPBinding(style = Style.DOCUMENT)
public interface HelloWorld{
}

JAX-WS document/literal tarzı çalıştığı için başka bir örnek te belki doğru olmazdı zaten.

JAX-WS RI ve WS-Addressing
WS-Addressing'in açıklayan gördüğüm en iyi yazı  What is WS-Addressing. Bu standart ile mesajın kimliği ve get/post aksiyonlarından hangisinin yapılmak istendiği Http protokolü yerine SOAP mesajının içine konuluyor.

JAX-WS RI ve WS-ReliableMessaging
Desteklemiyor
  
JAX-WS RI ve WS-Security
Desteklemiyor. WS-Security SSL anlamına gelmiyor. Eğer web servis noktadan noktaya çağrı şeklinde ise transport seviyesinde SSL kullanmak güvenliği sağlamak için yeterli ancak servis ara bağlantı sistemler tarafından iletiliyorsa her yere SSL kurmak yerine mesajların güvenliğini sağlamak için  WS-Security kullanılıyor. Bu standart ile web servis çağrısına security token yerleştirmek mümkün. Aşağıdaki örnekte WS-Security desteklenmese bile UsernameToken yerleştirme örneği var. SOAPHandler'dan türeyen bir sınıf aracılığıyla iş başarılıyor.
 
API arayüzü
Çoğu Java standardında olduğu gibi JAX-WS için de bir API arayüzü bir de reference implementation bulunuyor. Sun tarafından sağlanan jaxws-rt.jar (JAX-WS Implementation Package), JDK ile gelmiyor. Bu dosyayı Maven ile projeye dahil edince aşağıdakine benzer bir hiyerarşi oluşuyor.


Bu hiyerarşide jaxb-impl.jar dosyası Java Architecture for XML Binding standardını gerçekleştiren kütüphane. Bu kütüphane aracılığıyla Java sınıfları XML'e dönüştürülebiliyor.

SAAJ (SOAP with Attachments API for Java) Nedir?
saaj-impl.jar dosyası ise SOAP with Attachments API for Java standardını gerçekleştiren kütüphane. Bu kütüphane aracılığıyla binary SOAP mesajları eklenti olarak gönderilebiliyor. JAX-WS daha üst seviye bir API ancak altta SOAP mesajı oluşturmak için SAAJ kullanıyor.

SAAJ örneklerinde Factory sınıflarının da newInstance() metodu ile yaratıldığı görülebilir. Böylece sağlayıcılar (vendor) kendi gerçekleştirimlerini sunabilirler. Factory sınıfı system property'lerini okuyarak (örneğin javax.xml.soap.MessageFactory gibi ) gerçek gerçekleştirimi sınıfını bulur.

javax.xml.soap.SOAPConnectionFactory sınıfı
Örnek:
// Create SOAP Connection
SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
SOAPConnection soapConnection = soapConnectionFactory.createConnection();
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
//Populate message
// Send SOAP Message to SOAP Server
String url = "http://mycompany.com/test.asmx";
SOAPMessage soapResponse = soapConnection.call(message);

Diğer Kütüphaneler?
JAX-WS bir standart olduğu için bu standardı gerçekleştiren diğer kütüphaneler de mevcut. Bunlardan bazıları JAX-WS-Refrence Implementation, JBossWS, CFX, METRO, AXIS2 sayılabilir. Neden diğer kütüphanelere gerek duyuluyor ? JDK ile gelen RI sadece WS-Addressing'i destekliyor. Eğer diğer standartları da kullanmak istiyorsak başka bir kütüphane seçmemiz lazım.

Contract Last ne demek?
Java: JAX-WS Mapping sorusunda da anlatıldığı gibi contract last Java kodundan XSD dosyasının üretilmesi anlamına geliyor.

What happens to generic class in jax-ws webservice? sorusunda web-servisi yazarken uyulması iyi olan kurallar bulunabilir.

JAX-WS 2.0/2.1 uyumlu olmak
What does JAX WS 2.0/2.1 compliant means? sorusunda da cevaplandığı gibi JAX-WS'nin WSDL dosyasından kendi kendine bir POJO modeli oluşturabilmesi anlamına geliyor.

JAX-WS ve WSServlet
JAX-WS de bir çok diğer çatı gibi servlet teknolojisi kullanıyor.WSServlet JAX-WS tarafıdan kullanılan dispatcher servlet. Aşağıda WSServlet'ı tanımlama örneği var.
<listener>
    <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<servlet>
    <servlet-name>TempConvertWS</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.WSServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>TempConvertWS</servlet-name>
    <url-pattern>/tc</url-pattern>
</servlet-mapping>


JAX-WS ve WebServiceContext 
Her web servis çağrısı bir servlet içinde işlenir. javax.xml.ws.WebServiceContext sınıfı servlet ve çağrı ile ilgili bilgilere erişebilmemizi sağlar.
Örnek:

JAX-WS ve WSServletContextListener
Bu konuyu daha sonra yazacağım.

JAX-WS ve Spring Servlet

JAX-WS ve Spring'i birleştirmenin 3 tane yolu var. Her yöntemi ve aralarındaki farkları JAX–WS with Spring and Maven Tutorial yazısında görebilirsiniz.

WSSpringServlet Yöntemi
Bu yöntem Java EE platformlarına uygun değilmiş. Sebebini tam bilmiyorum ancak Tomcat gibi platformlara uygunmuş. Bu yöntemde WebServis sınıfımızın hayat döngüsü Spring tarafından yönetiliyor.

jaxws-spring projesini Maven ile projeye dahil edince JAX-WS ve Spring beraber çalışabilir hale geliyorlar ve aşağıdakine benzer bir hiyerarşi oluşuyor.

JAX-WS projelerinde normalde dispatcher servlet olarak jaxws-rt-2.2.6-2.jar dosyasında bulunan com.sun.xml.ws.transport.http.servlet.WSServlet sınıfı kullanılıyor. 
What is com.sun.xml.ws.transport.http.servlet.WSServlet and any documentation on this. sorusu bu servlet hakkında biraz daha bilgi veriyor. Ancak Spring ile entegre olabilmek için jaxws-spring-1.8.jar dosyasında bulunan com.sun.xml.ws.transport.http.servlet.WSSpringServlet sınıfı kullanılmalı.

Aslında her iki sınıf ta gelen istekleri WSServletDelegate sınıfına yolluyorlar.

SimpleJaxWsServiceExporter Yöntemi
Spring ile gelen bir diğer sınıf olan SimpleJaxWsServiceExporter JAX-WS ile gelen gömülü Http Server'ı kullanarak web servisleri de çalıştırabiliyor. Bu sınıfı kullanmaya hiç ihtiyaç duymadım ama konsol tipi uygulamalarda işe yarayacağını tahmin ediyorum. Aşağıda kod örnekleri var.
Bu sınıf aşağıdaki örnekte olduğu gibi @Service ve @WebService ile işaretli kodu web servis olarak dışarıya açmamızı sağlıyor.



JAX-WS ve javax.xml.ws.handler.MessageContext Arayüzü
MessageContext arayüzü ile SOAP mesajında olmayan ancak transport protokolü içinde bulunan meta-data'ya erişmek mümkün. A little bit about Message Context in JAX-WS başlıklı yazı bu konuda bilgilendirici.
HttpServletRequest aracılığıyla HttpSession'a erişmek
http://sirinsevinc.wordpress.com/category/jaxws/ adresinde WebServis sınıfı içinden ServletRequest sınıfına erişmek için kod örneği var. Aşağıdaki örnekte MessageContext ve MessageContext.SERVLET_REQUEST beraber kullanılıyor.
HttpServletRequest aracılığıyla Client IP'sine erişmek
jax ws getting client ip sorusunda örnek var.
Eğer gömülü bir Http sunucusu kullanılıyorsa, servlet ortamı olmayacaktır. O zaman da aşağıdaki kodu kullanmak gerekir.

Spring XML Tanımlamaları
Web servisini spring tarafından yönetilen bir bean haline getirmek için aşağıdakine benzer bir tanımlama yapmak lazım.
<wss:binding url="/webservice">
    <wss:service>
        <ws:service bean="#mybean"/>
    </wss:service>
</wss:binding>
Ancak wss namespace'ini tanıtmak bir problemli çünkü Spring and Jax-WS : where are xsd schema?  sorusunda da söylendiği gibi jax-ws.dev.java.net sitesindeki url tanımları değişmiş ve internetteki örnekleri kullanınca SAXParseException alınabiliyor. Örneğin internetteki örnekte

http://jax-ws.java.net/spring/core http://jax-ws.java.net/spring/core.xsd
http://jax-ws.java.net/spring/servlet http://jax-ws.java.net/spring/servlet.xsd

şeklinde kullanılmış. Ancak benim sistemimde

http://jax-ws.dev.java.net/spring/core spring-jax-ws-core.xsd      
http://jax-ws.dev.java.net/spring/servlet spring-jax-ws-servlet.xsd

şeklinde kullanmak gerekti.

Doğru url'yi bulmak için jaxws-spring-1.8.jar dosyası altındaki META-INF/spring.schemas dosyasındaki url'leri kullanmak problemi çözüyor.

SOAP mesajları
Konu için SOAP Anatomisi başlıklı yazıya bakılabilir.

JAXB
JAXB yazısına taşıdım.

WSDL ve XSD
WSDL (Web Services Description Language) web servislerinin sunduğu arayüzlerin tanımlanabilmesi için kullanılan XML tabanlı tanımlama dilidir.

Yazılan servisin wsdl ve xsd dosyalarını görmek için
http://localhost:8080/MyApp/webservice?wsdl ve http://localhost:8080/MyApp/webservice?xsd=1 adreslerine bağlanmak yeterli.

wsimport kullanmadan dynamic proxy yöntemi ile client yaratmak
Aşağıdakine benzer bir kod ile dynamic proxy yaratarak herhangi bir webservisini kullanmak mümkün.
URL url = null;
try {
    url = new URL("http://localhost:8080/MyApp/webservice?wsdl");
} catch (MalformedURLException e1) {
    e1.printStackTrace();
}
QName qname = new QName("http://webservice.mycompany.com.tr/", "SorgulaWsService");

Service service = Service.create(url, qname);

ISorgulaWs sorgulaws = service.getPort(ISorgulaWs.class);
sorgulaws.getUcret();
Burada dikkat edilmesi gereken husus JAXWS client erroring out when calling a stateless session bean exposed as a webservice sorusunda da açıklandığı gibi WebServisimizin bir interface kullanarak yazılması.

Bir başka örnek ise aşağıda.

QName serviceName = new QName(Namespace.SERVICE, "FileService");
service = new FileService(new URL("http://localhost:8059/WS/FileService?wsdl"), serviceName);
file_service = service.getSOAP11();


Bu yöntemin dezavantajı her çağrı için karşıdan wsdl dosyasının istenmesi. How to cache JAX-WS stub/port in Java? sorusuna verilen cevapta bir çözüm sunulmuş.

wsimport
WSDL dosyasından java sınıfları üretmek için wsimport komutunu kullanmak lazım. wsimport komutu JDK ile geliyor ve bin dizininde kurulu. Örnek:

wsimport.exe http://localhost:8080/MyApp/webservice?wsdl
How to use wsimport when server expects client certificate? sorusunda bir başka örnek var.

-d : seçeneği ile dosyaların yaratılacağı dizin belirtilebiliyor.
-keep : seçeneği ile üretilen dosyalar silinmiyor. Örneği buradan aldım

-p : seçeneği ile üretilecek dosyalarda kullanılacak paket ismi belirtilebiliyor.
-Xnocompile : seçeneği ile üretilen dosyalar derlenmiyor

Eğer hem webservisimiz hem de test kodu aynı projede olacaksa -p ile test kodu için farklı bir paket ismi verilmesi daha uygun olur.
wsgen
Burada yazdığına göre bu komut Java 8 ile kaldırılacak.Bu komut ile wsimport birbirlerine benziyor. Aralarındaki fark wsimport WSDL dosyasından client kodu üretirken wsgen direkt SEI'den (Service Endpoint Implementation yani Java kodu) client kodu üretiyor.
-cp : seçeneği ile classpath olarak kullanılacak dizin veriyor. SEI kodunun belirtilen cp altında olması lazım.
-keep : Yukarıdaki ile aynı
-s : seçeneği ile üretilecek kodun konulacağı dizin veriliyor.
Örnek :

wsdl2java
Bu komut ta Apache CFX ile geliyor ve wsimport gibi wsdl dosyasından java kodu üretmeye yarıyor.

Güvenlik
JAX-WS Password Type PasswordText sorusunda WebServis güvenliği ve HTTP güvenliği arasındaki farktan bahsediliyor.

Maven
jaxws-rt kütüphanesini projeye eklemek için aşağıdaki gibi tanımlama yapmak yeterli.
<dependency>
    <groupId>com.sun.xml.ws</groupId>
    <artifactId>jaxws-rt</artifactId>
    <version>2.2.6-2</version>
</dependency>
Ancak eğer jBoss gibi bir  sunucu kullanıyorsak Tomcat'in tersine bu sunucular jaxws-rt ile yüklü geliyorlar. O zaman scope=provided yapılmalı.

Which is the best maven's plugin to generate a Web Service Client? sorusuna göz atmakta fayda var.

Bazı JAX-WS sınıfları
SOAPHandler
Bu konu ile ilgili A little bit about Handlers in JAX-WS sorusuna bakılabilir.Bir handler'ı tanıtmak için örnekteki gibi @HandlerChain (file = "soaphandlerchain.xml") anotasyonu ile tanıtılabilir.

SOAPPart'a erişmek için aşağıdaki kod kullanılabilir.Aşağıda SOAP mesajının yapısı var.

@Override
public boolean handleMessage(SOAPMessageContext messageContext) {
    SOAPMessage msg = messageContext.getMessage ();
    SOAPPart sp = msg.getSOAPPart ();
    SOAPEnvelope env = sp.getEnvelope ();
}

javax.xml.ws.BindingProvider
Bu sınıf ile servisi çağırırken kullanılacak bazı parametreleri atamak mümkün. Genel kullanım şekli aşağıdaki gibi.

ENDPOINT_ADDRESS_PROPERTY
Bu alan ile çağrılacak URL'yi atamak mümkün.
HTTP_REQUEST_HEADERS
Bu alan ile kullanılacak encoding'i atamak mümkün.
Map<String, Object> reqContext = ((BindingProvider) service).getRequestContext();
Map httpHeaders = new HashMap();
httpHeaders.put("Content-type",Collections.singletonList("text/xml;charset=ISO-8859-1"));
reqContext.put(MessageContext.HTTP_REQUEST_HEADERS, httpHeaders);
USERNAME_PROPERTY ve PASSWORD_PROPERTY
Bu alanlar ile Basic Http Authentication yapmak mümkün. Örnek:

CONNECT_TIMEOUT ve REQUEST_TIMEOUT
Bağlanma ve istekte zaman aşımı verebilmek mümkün. Örnek:

MyInterface myInterface = new MyInterfaceService().getMyInterfaceSOAP();
Map<String, Object> requestContext = ((BindingProvider)myInterface).getRequestContext();
requestContext.put(BindingProviderProperties.REQUEST_TIMEOUT, 3000); // Timeout in millis
requestContext.put(BindingProviderProperties.CONNECT_TIMEOUT, 1000); // Timeout in millis
myInterface.callMyRemoteMethodWith(myParameter);

SSLSocketfactory
Bu alan ile servisi çağırırken kullanılacak SSL ayarlarını atamak mümkün

BindingProvider bindingProvider = (BindingProvider) service;
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());

Aşağıda ise sınıf hiyeraşisi var.
javax.xml.ws.EndPoint
publish metodu
Bu metod ile JVM içinde gömülü bulunan bir sunucu çalıştırılıyor. Örneği burada görmek mümkün.

WSBindingProvider
Bu sınıf sadece JAX-WS RI tarafından sağlanıyor. Aşağıdaki örnekte bu sınıf aracılığıyla bazı header parametrelerini atama örneği mevcut.


1 yorum:

  1. Merhaba,
    Örnek için teşekkürler.
    Eğer Spring Restful Web Servise konusu ile de ilgileniyorsanız,
    Spring Framework ile Restful Web Service Nasıl Yazılır? konulu videomu aşağıdaki adresten kontrol edebilirsiniz.

    http://www.bilgicekici.com/2015/07/05/spring-framework-ile-restful-webservice/

    YanıtlaSil