Showing posts with label jax-ws. Show all posts
Showing posts with label jax-ws. Show all posts

Sunday, March 13, 2011

Thread scoped JAX-WS webservices

Like Servlets, the container uses one instance of a web service class to handle incoming web service requests, which is the default behavior. Ofcourse, you can make the web service thread-safe by adding the synchronized method to the exposed methods like the following code:

@WebService
public class ExampleWS {
public synchronized void doSomething() {
;
}

public synchronized void doSomethingElse() {
;
}
}

By doing this, you eliminate the possibility of parallel or concurrent handling of incoming requests. In order to support concurrent processing, you can mark the web service class as thread scoped by using the @ThreadScope annotation.

import org.jvnet.jax_ws_commons.thread_scope.ThreadScope;

@WebService
@ThreadScope
public class ExampleWS {
public synchronized void doSomething() {
;
}

public synchronized void doSomethingElse() {
;
}
}

The added annotation will force the container to create a new instance of the web service class for every thread (ie. every request). Instances will not be shared by threads.

Monday, February 14, 2011

When @SchemaValidation in JAX-WS web services fails

I'm currently developing web services in an Oracle WebLogic environment. To enable schema validation on the web service, one only has to use the @SchemaValidation annotation in the web service class. Like this:


import com.sun.xml.ws.developer.SchemaValidation;

@WebService(...)
@SchemaValidation
public class WebService implements WebServiceInterface {
...
}


Unfortunately, that doesn't work for me. I get this error when I try to deploy my application:


Caused by: javax.xml.ws.WebServiceException:
Annotation@com.sun.xml.internal.ws.developer.SchemaValidation
(handler=class com.sun.xml.internal.ws.server.DraconianValidationErrorHandler)
is not recognizable,
atleast one constructor of class com.sun.xml.internal.ws.developer.SchemaValidationFeature
should be marked with @FeatureConstructor


I also tried creating my own validator to use in the annotation (@SchemaValidation(handler = ExampleValidationHandler.class)):


import org.xml.sax.SAXParseException;

import com.sun.xml.internal.ws.developer.ValidationErrorHandler;

public class ExampleValidationHandler extends ValidationErrorHandler {

public void warning(SAXParseException exception) {
;
}

public void error(SAXParseException exception) {
;
}

public void fatalError(SAXParseException exception) {
;
}
}


Then I enabled schema validation explicitly in the Oracle WebLogic web services deployment descriptor: weblogic-webservices.xml.


<?xml version="1.0" encoding="UTF-8"?>
<weblogic-webservices
xmlns="http://xmlns.oracle.com/weblogic/weblogic-webservices"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-webservices http://xmlns.oracle.com/weblogic/weblogic-webservices/1.1/weblogic-webservices.xsd">

<webservice-description>
<webservice-description-name>ExampleWSService</webservice-description-name>
<webservice-type>JAXWS</webservice-type>
<port-component>
<port-component-name>ExampleWSService</port-component-name>
<validate-request>true</validate-request>
<reliability-config>
<inactivity-timeout>P0DT600S</inactivity-timeout>
<base-retransmission-interval>P0DT3S</base-retransmission-interval>
<retransmission-exponential-backoff>true</retransmission-exponential-backoff>
<acknowledgement-interval>P0DT3S</acknowledgement-interval>
<sequence-expiration>P1D</sequence-expiration>
<buffer-retry-count>3</buffer-retry-count>
<buffer-retry-delay>P0DT5S</buffer-retry-delay>
</reliability-config>
</port-component>
</webservice-description>
</weblogic-webservices>


Unfortunately, that didn't work either. Google shows me three other developers with the same problem, but there is no working solution for me, and for them. So, another approach is needed to enable schema validation on incoming messages.

The solution presented in this blog post makes use of web service handlers. A web service handler is like an intercepting filter, which enables you to intercept incoming and outgoing messages, before they are passed on to the (external) system. The steps needed for this solution:

1. Create web service handler class
2. Define handler chain in XML-descriptor
3. Use annotation in web service class

1. Create web service handler class

There are two types of web service handlers:
- LogicalHandler
- SOAPHandler

The LogicalHandler provides access to the logical message, without protocol details like: SOAP envelopes. The SOAPHandler provides access to SOAP protocol details. For the purpose of message validation, the LogicalHandler is a better choice.


package com.javaeenotes;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.ws.LogicalMessage;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class ValidationHandler implements LogicalHandler<LogicalMessageContext> {
private String schemaUrl = "http://127.0.0.1:8080/webservice/schema.xsd";

@Override
public boolean handleMessage(LogicalMessageContext context) {
Boolean isOutBound = (Boolean) context
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

if (isOutBound) {
return true;
}

LogicalMessage lm = context.getMessage();
Source payload = lm.getPayload();
StreamResult res = new StreamResult(new StringWriter());
String message = "";

try {
Transformer trans;
trans = TransformerFactory.newInstance().newTransformer();
trans.transform(payload, res);
message = res.getWriter().toString();
// Validate
validate(message, schemaUrl);
} catch (TransformerConfigurationException e) {
// When Source payload Transformer object could not be obtained.
throw new WebServiceException(e);
} catch (TransformerFactoryConfigurationError e) {
// When Source payload Transformer object could not be obtained.
throw new WebServiceException(e);
} catch (TransformerException e) {
// When Source payload could not be transformed to String.
throw new WebServiceException(e);
} catch (MalformedURLException e) {
// When URL to schema is invalid.
throw new WebServiceException(e);
} catch (ParserConfigurationException e) {
// When parser needed for validation could not be obtained.
throw new WebServiceException(e);
} catch (IOException e) {
// When something is wrong with IO.
throw new WebServiceException(e);
} catch (SAXException e) {
// When XSD-schema validation fails.
throw new WebServiceException(e);
}

return true;
}

private void validate(String xml, String schemaUrl)
throws ParserConfigurationException, IOException,
MalformedURLException, SAXException {

DocumentBuilder parser = null;
Document document = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
parser = dbf.newDocumentBuilder();

byte bytes[] = xml.getBytes();
document = parser.parse(new ByteArrayInputStream(bytes));
SchemaFactory factory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

Schema schema = null;
schema = factory.newSchema(new URL(schemaUrl));
javax.xml.validation.Validator validator = schema.newValidator();
validator.validate(new DOMSource(document));
}

@Override
public boolean handleFault(LogicalMessageContext context) {
return true;
}

@Override
public void close(MessageContext context) {
;
}

}


It is important to note that the payload must be defined in an XSD-schema. A definition in a WSDL file cannot be used. Use the boolean property: MessageContext.MESSAGE_OUTBOUND_PROPERTY to differentiate between out-bound or in-bound messages. Throw a WebServiceException when something goes wrong.

2. Define handler chain in XML-descriptor

Now, create a handler chain XML-descriptor file, which refers to the handler created in the previous step. In this example, I named the file handlers.xml and placed it in the same directory as the handler. The location of the file is important in the last step.


<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/javaee_web_services_metadata_handler_2_0.xsd">
<handler-chain>
<handler>
<handler-name>ValidationHandler</handler-name>
<handler-class>com.javaeenotes.ValidationHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>


3. Use annotation in web service class

The final step is to use the @HandlerChain annotation in the web service class. The file parameter must be a relative path to the XML-descriptor file from the annotated class file or an absolute URL.


package com.javaeenotes;

import javax.jws.HandlerChain;
import javax.jws.WebService;

import com.javaeenotes.ws.ExampleWS;
//import com.sun.xml.ws.developer.SchemaValidation;

@WebService(name = "ERSService",
targetNamespace = "http://javaeenotes.com/",
serviceName = "ExampleWSService",
portName = "ExampleWSPort",
endpointInterface = "com.javaeenotes.ws.ExampleWS",
wsdlLocation = "WEB-INF/wsdl/ExampleWSService.wsdl")
//@SchemaValidation
@HandlerChain(file = "handlers.xml")
public class WsImplementation implements ExampleWS {

@Override
public int sum(int arg0, int arg1) {
return 0;
}

@Override
public String greet(String arg0) {
return null;
}

@Override
public int multiply(int arg0, int arg1) {
return 0;
}
}

Tuesday, February 8, 2011

Building a contract-first webservice with JAX-WS

In this tutorial, we'll be building a webservice contract-first. This means we build the webservice based on a contract that already exists. This contract is the WSDL file that describes the webservice formally.

The first step is to generate JAX-WS classes using wsgen that comes with the Java EE SDK. You can find the executable in your SDK/Glassfish installation directory. We'll be using the WSDL file of an earlier blogpost. Save this file as: ExampleWSService.wsdl. If you're using Eclipse, you might want to save the files in WebContent/WEB-INF/wsdl in the project directory.


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions targetNamespace="http://javaeenotes.com/"
name="ExampleWSService"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://javaeenotes.com/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<types>
<xsd:schema>
<xsd:import namespace="http://javaeenotes.com/" schemaLocation="ExampleWSService.xsd"/>
</xsd:schema>
</types>
<message name="sum">
<part name="parameters" element="tns:sum"/>
</message>
<message name="sumResponse">
<part name="parameters" element="tns:sumResponse"/>
</message>
<message name="greet">
<part name="parameters" element="tns:greet"/>
</message>
<message name="greetResponse">
<part name="parameters" element="tns:greetResponse"/>
</message>
<message name="multiply">
<part name="parameters" element="tns:multiply"/>
</message>
<message name="multiplyResponse">
<part name="parameters" element="tns:multiplyResponse"/>
</message>
<portType name="ExampleWS">
<operation name="sum">
<input message="tns:sum"/>
<output message="tns:sumResponse"/>
</operation>
<operation name="greet">
<input message="tns:greet"/>
<output message="tns:greetResponse"/>
</operation>
<operation name="multiply">
<input message="tns:multiply"/>
<output message="tns:multiplyResponse"/>
</operation>
</portType>
<binding name="ExampleWSPortBinding" type="tns:ExampleWS">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="sum">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="greet">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="multiply">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="ExampleWSService">
<port name="ExampleWSPort" binding="tns:ExampleWSPortBinding">
<soap:address location="http://127.0.0.1:8080/webservice/ExampleWSService"/>
</port>
</service>
</definitions>


This WSDL file imports an XSD file. Make sure this is placed in the same directory as the WSDL file, and name it ExampleWSService.xsd. Also update the location/URL in the WSDL file. This example assumes the application is called webservice and the service name is ExampleWSService.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
targetNamespace="http://javaeenotes.com/"
xmlns:tns="http://javaeenotes.com/"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="greet" type="tns:greet"/>

<xs:element name="greetResponse" type="tns:greetResponse"/>

<xs:element name="multiply" type="tns:multiply"/>

<xs:element name="multiplyResponse" type="tns:multiplyResponse"/>

<xs:element name="sum" type="tns:sum"/>

<xs:element name="sumResponse" type="tns:sumResponse"/>

<xs:complexType name="greet">
<xs:sequence>
<xs:element name="arg0" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="greetResponse">
<xs:sequence>
<xs:element name="return" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="sum">
<xs:sequence>
<xs:element name="arg0" type="xs:int"/>
<xs:element name="arg1" type="xs:int"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="sumResponse">
<xs:sequence>
<xs:element name="return" type="xs:int"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="multiply">
<xs:sequence>
<xs:element name="arg0" type="xs:int"/>
<xs:element name="arg1" type="xs:int"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="multiplyResponse">
<xs:sequence>
<xs:element name="return" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:schema>


Now use the wsgen command to generate the server stubs. I generally put the complete command in a Windows batch file, so I can easily rerun the command in the future if necessary. Now, execute the following command:

wsimport -p com.javaeenotes.ws -d build/classes -s src WebContent/WEB-INF/wsdl/ExampleWSService.wsdl


This will generate all classes needed to setup the webservice. Next is to create a class that implements the webservice.


package com.javaeenotes;

import javax.jws.WebService;

import com.javaeenotes.ws.ExampleWS;

@WebService(name = "ERSService",
targetNamespace = "http://javaeenotes.com/",
serviceName = "ExampleWSService",
portName = "ExampleWSPort",
endpointInterface = "com.javaeenotes.ws.ExampleWS",
wsdlLocation = "WEB-INF/wsdl/ExampleWSService.wsdl")
public class WsImplementation implements ExampleWS {

@Override
public int sum(int arg0, int arg1) {
return 0;
}

@Override
public String greet(String arg0) {
return null;
}

@Override
public int multiply(int arg0, int arg1) {
return 0;
}
}


This class implements the webservice interface we generated from the WSDL file. Using simple annotations, we can easily deploy it without editting any deployment descriptors. Nothing more is needed for successful deployment. If you used the default URL in the WSDL file, you can now reach the webservice using this URL:

http://127.0.0.1:8080/webservice/ExampleWSService?wsdl

I recommend SOAP-UI to test the webservice.

Thursday, November 11, 2010

Retrieve remote web service client host and IP

If you've created a web service, and you want to find out what the host or IP-address of the remote client is? The following code will be useful.

@WebService
public class WebService {
@Resource
WebServiceContext wsc;

@WebMethod
public String webMethod() {
MessageContext mc = wsc.getMessageContext();
HttpServletRequest req = (HttpServletRequest)
mc.get(MessageContext.SERVLET_REQUEST);
return "Client: " +
req.getRemoteHost() + " (" +
req.getRemoteAddr() + ").";
}
}

The code above will return the host and IP-address of the remote web service client back to the client.

Friday, October 8, 2010

Web service client with JAX-WS in Eclipse

In this blogpost, I will use JAX-WS to show how easy it is to create a simple client that makes use of the web service we created in the previous blogpost. The client can be implemented in various ways, like a web application or an EJB. In both mentioned ways, you run the client in a Java container. Therefore, you can take advantage of the resource injection facility provided by the container. This means you can use annotations to automatically inject the web service object where needed. We start out by creating a standard client, which does not run in a container.

Let's get started by creating a standard Java project in Eclipse. When done, start up a command prompt and navigate it to the root directory of the newly created project. Use the wsimport command to generate the classes you need to access the web service. You need the URL of the WSDL for this to work. The following command generates the sources and classes, and place them in the default Eclipse directories.

wsimport -s src
-d bin
http://localhost:8080/webservice/ExampleWSService?wsdl

Now, create a new class, which acts as the client. An example client:

package com.javaeenotes;

import javax.xml.ws.WebServiceRef;


public class WebserviceClient {

// This annotation only has effect in a container.
@WebServiceRef(
wsdlLocation =
"http://127.0.0.1:8080/webservice/ExampleWSService?wsdl"
)
private static ExampleWSService service;

public static void main(String[] args) {
WebserviceClient wsc = new WebserviceClient();
wsc.start();
}

public void start() {
// This statement is not needed when run in container.
service = new ExampleWSService();
ExampleWS port = service.getExampleWSPort();
System.out.println(port.greet("Peter"));
System.out.println(port.multiply(3, 4));
}
}

The resource injection annotation is not necessary here, because the client does not run in a container. I left it here to show how to use annotation to inject the resource into your class when run in a container.

Sunday, October 3, 2010

Web service with JAX-WS in Eclipse

In this tutorial, I'll show you how to use JAX-WS to build a web service in Eclipse. JAX-WS stands for Java API for XML Web Services. You can use it to build web services and clients that communicate using XML messages. We are not using the built-in web service generation tool provided by Eclipse. How to build a sample client will be explained in a future blog post.

The basic steps for building a web service:

  1. Code the implementation class
  2. Annotate the class
  3. Use wsgen to generate web service artifacts
  4. Deploy in Glassfish from Eclipse

1. Code the implementation class

First create a dynamic web project in Eclipse. I called my project webservice, but you call your project anything you like. Make sure the target runtime is Glassfish, and select Minimal Configuration in the configuration screen.



In the next screen, accept the default output folder or choose a custom one. Memorize the output folder, because we need this when we generate the web service artifacts.




You don't have to select the last checkbox to auto generate the web.xml deployment descriptor, but you'll need it when your application is also a web application.



Click finish, and your project is ready to use.
Create a new class in the newly created project. This will be the implementation class of the web service. I called mine ExampleWS.



2. Annotate the class

Now it's time to complete the class and use JAX-WS annotation to make it a valid web service.

package com.javaeenotes;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public class ExampleWS {

@WebMethod
public int sum(int a, int b) {
return a + b;
}

@WebMethod
public int multiply(int a, int b) {
return a * b;
}

@WebMethod
public String greet(String name) {
return "Hello " + name + "!";
}
}

3. Use wsgen to generate web service artifacts

Start a command prompt and navigate to the root of the project directory. Make sure the JDK is in your PATH, or else you can't execute the wsgen command. Create a wsdl directory in WebContent/WEB-INF/ or else wsgen will complain. Use the wsgen command to generate the artifacts like this:

wsgen -classpath build/classes/ -wsdl
-r WebContent/WEB-INF/wsdl -s src
-d build/classes/ com.javaeenotes.ExampleWS

This command will generate the source, class files, and WSDL file for the web service implemented as com.javaeenotes.ExampleWS. When wsgen complains about not finding the class, you have to build the project first. When the command is successful, you can refresh the project and you'll find the artifacts in your project tree.

The generated WSDL-file needs some editting, before you can use it. Open the WSDL-file and search for REPLACE_WITH_ACTUAL_URL. Replace this string with the web service URL: http://127.0.0.1:8080/webservice/ExampleWSService, and save the file.

4. Deploy in Glassfish from Eclipse

I assume you have Glassfish integrated in Eclipse, or else you have to build a WAR-file using ANT and manually deploy it into Glassfish. There is a blog post about integrating Glassfish into Eclipse. You can use the search engine to find it.

Deploy the project in Glassfish by right-clicking the project, click Run As, and finally Run on Server. Select the Glassfish server in the selection screen, and complete this step. When the configuration is finished, Eclipse tries to open a web page, which fails with a 404 error. Don't worry, this is normal, because we just created a web application without pages. Our project contains only a web service.

We can test the newly created web service by using SOAP-UI or the integrated Web Services Explorer in Eclipse. To use the Web Sercice Explorer in Eclipse, browse to the WSDL-file, right-click on it, select Web Services, and click on Test with Web Services Explorer. This will present you the web services test screen, which is pretty easy to use.