Monday, January 27, 2014

RESTEasy services with older application servers

If you are not blessed with the latest application server and want to use all the fancy new features offered by RESTEasy, fear not! You can still use RESTEasy framework with older application servers like JBoss 4.x.


Add the 4 dependencies below. If you are using maven, here is a shortcut: 
<dependency>
 <groupId>org.jboss.resteasy</groupId>
 <artifactId>resteasy-jaxrs</artifactId>
 <version>3.0.6.Final</version>
</dependency>

<dependency>
 <groupId>org.jboss.resteasy</groupId>
 <artifactId>jaxrs-api</artifactId>
 <version>3.0.6.Final</version>
</dependency>

<dependency>
 <groupId>org.jboss.resteasy</groupId>
 <artifactId>resteasy-jaxb-provider</artifactId>
 <version>3.0.6.Final</version>
</dependency>

<dependency>
 <groupId>org.jboss.resteasy</groupId>
 <artifactId>resteasy-jackson2-provider</artifactId>
 <version>3.0.6.Final</version>
</dependency>  

Loading the RESTEasy framework is very easy. Simple configure it in web.xml.
<servlet>
 <servlet-name>Resteasy</servlet-name>
 <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
</servlet>

<!-- Set servlet-mapping for the Resteasy servlet if it has a url-pattern other than /* -->
<servlet-mapping>
 <servlet-name>Resteasy</servlet-name>
 <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

<context-param>
 <param-name>resteasy.scan</param-name>
 <param-value>true</param-value>
</context-param>

<context-param>
 <param-name>resteasy.servlet.mapping.prefix</param-name>
 <param-value>/rest</param-value>
</context-param>

<listener>
 <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>

To make spring available in the rest services, add another listener in the web.xml.
<listener>
 <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
</listener>

And now a simple JSON REST service, to check everything works.

A simple POJO class:
public class RestObject {

 private long id;
 private String name;
 private String value;

 public long getId() {
  return this.id;
 }

 public void setId(long id) {
  this.id = id;
 }

 public String getName() {
  return this.name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getValue() {
  return this.value;
 }

 public void setValue(String value) {
  this.value = value;
 }
}

And a service who offers two methods. One who consumes and produces a JSON POST request and another who produces JSON.
@Path("/v1")
public class RestService {

 @POST
 @Path("/post-service")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
 public Response addRestObject(RestObject restObject) {
  restObject.setId(123L);
  return Response.status(HttpResponseCodes.SC_OK).entity(restObject).build();
 }

 @GET
 @Path("/get-service/{id}")
 @Produces(MediaType.APPLICATION_JSON)
 public Response getRestObject(@PathParam("id") long id) {
  RestObject restObject = new RestObject();
  restObject.setId(id);
  restObject.setName("your name");
  restObject.setValue("JP");
  return Response.status(HttpResponseCodes.SC_OK).entity(restObject).build();
 }
}

Accessing the GET method, http://localhost:8080/war-file/rest/v1/get-service/2 will output:
{
    "id": 2,
    "name": "your name",
    "value": "JP"
}

Accessing the POST, http://localhost:8080/war-file/rest/v1/post-service method with payload:

{
    "id": 1,
    "name": "my name",
    "value": "JP"
}

Will output:

{
    "id": 123,
    "name": "my name",
    "value": "JP"
}

That is all it takes to get started with RESTEasy using an older application server, happy REST/SOAP service development :).

Tuesday, January 21, 2014

JBoss 4.2.2 to Wildfly8/Jboss7 CXF migration

Prelude

Recently I was facing a migration project, migrating from from JBoss 4.2.2 to Wildfly 8. The old project was using Spring 2.5.5, Hibernate 3.6.7 and CXF 2.2.7.
Everything except CXF was fairly easy to migrate. Wildfly 8 comes with CXF 2.7.7 bundled and I wanted to use the managed dependencies. 
I have made this post to hopefully help other people facing a migration from an older JBoss to a more resent version.

Before starting any migration I would strongly recommend making sure you have at least some smoke tests of all services.
For old legacy systems no test might exist, but making a few smoke tests that exercise as much as possible of the service could work.   
An excellent tool for testing SOAP web service is SoapUI.

I have added short description of the project configuration in the bottom of the post.

Happy reading! 


Here we go!

First I made sure all CXF dependencies was removed from the war file, but WSDL and XSD files were causing a problem.


First try deploying, resulted in some problems loading the CXF beans from cxf-beans.xml.


ERROR [org.springframework.web.context.ContextLoader] (MSC service thread 1-5) Context initialization failed: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to import bean definitions from URL location [classpath:/cxf-beans.xml]
Offending resource: ServletContext resource [/WEB-INF/classes/applicationContext.xml]; nested exception is org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://cxf.apache.org/jaxws]
Offending resource: class path resource [cxf-beans.xml]
        at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68) [spring-2.5.5.jar:2.5.5]
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85) [spring-2.5.5.jar:2.5.5]
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:76) [spring-2.5.5.jar:2.5.5]
I solved this by completely removing cxf-beans.xml and let Wildfly manage the services.
Unfortunately this didn't solve all my problems. Now I was facing problems with the XSD's.
We used to create web services by, first creating a WSDL and XSD files and then running wsdl2jave, generating model and interface. 

Caused by: org.apache.ws.commons.schema.XmlSchemaException: Unable to locate imported document at '/ServiceObject.xsd', relative to 'vfs:/C:/Development/wildfly-8.0.0.CR1/bin/content/project.war/WEB-INF/classes/ServiceV1.wsdl#types1'.

After hours of googling about the problem, I decided to get rid of all WSDL and XSD's and let Wildfly handle everything. I removed the wsdlLocation = "classpath:ServiceV1.wsdl", from @WebService annotation and deleted alle WSDL and XSD's from the project. 

First success! The project was now able to deploy. However Wildfly was not showing any registered services in the console :(. 
The problem here was, that I was using standalone-full.xml, which did not have web services enabled by default.
Easy solved by adding:

<extension module="org.jboss.as.webservices">   
and
<subsystem xmlns="urn:jboss:domain:webservices:1.2">
 <modify-wsdl-address>true</modify-wsdl-address>
 <wsdl-host>${jboss.bind.address:127.0.0.1}</wsdl-host>
 <endpoint-config name="Standard-Endpoint-Config"/>
 <endpoint-config name="Recording-Endpoint-Config">
  <pre-handler-chain name="recording-handlers" protocol-bindings="##SOAP11_HTTP ##SOAP11_HTTP_MTOM ##SOAP12_HTTP ##SOAP12_HTTP_MTOM">
   <handler name="RecordingHandler" class="org.jboss.ws.common.invocation.RecordingServerHandler"/>
  </pre-handler-chain>
 </endpoint-config>
 <client-config name="Standard-Client-Config"/>
</subsystem>   

to standalone-full.xml.

The Wildfly console was now showing all my services. Wohoo!
However my newly gained success only lasted until i clicked the WSDL link :(.
I was now presented to a nice stack trace...
Exception handling request to /project/ServiceV1: java.lang.AbstractMethodError: org.apache.xerces.dom.DeferredDocumentImpl.setXmlStandalone(Z)V
If you have any dependencies to xerces either remove it if possible or use the version provided by Wildfly.

A quick check list to run through: 
  • Remove all WSDL and XSD files
  • Remove wsdlLocation in @WebService element
  • If Services was previously mapped using a name in the WSDL, change "serviceName" to reflect the right name of the service
  • Service endpoints can be found in the web console: http://localhost:9990/console/App.html#webservice-runtime
  • Make sure model classes is properly decorated with XML annotations according to XSD's. If originally generated from XSD's, it should not be a problem
  • Make sure xerces dependency is managed by Wildfly

Sample project configuration

The project structure was looking something like this:

war \
---- WEB-INF \
-------- web.xml
-------- lib \
------------ cxf-2.2.7.jar
-------- classes \
------------ applicationContext.xml
------------ cxf-beans.xml
------------ ServiceV1.wsdl
------------ ServiceObjectV1.xsd
------------ com.blogspot.jpdevelopment.v1.ServiceV1.class
------------ com.blogspot.jpdevelopment.v1.ServiceV1Impl.class
------------ com.blogspot.jpdevelopment.v1.ServiceObjectV1.class   

Web service implementation was looking like this:
@WebService(name = "ServiceV1",
   serviceName = "ServiceV1",
   portName = ServiceV1LOCAL",
   targetNamespace = "http://schema.jpdevelopment.blogspot.com/v1",
   wsdlLocation = "classpath:ServiceV1.wsdl",
   endpointInterface = "com.blogspot.jpdevelopment.v1.ServiceV1")

public class ServiceV1Impl implements ServiceV1 {            

And the interface:
@WebService(targetNamespace = "http://schema.jpdevelopment.blogspot.com/v1", name = "ServiceV1")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface ServiceV1 {

CXF was started in web.xml
<web-app>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>WEB-INF/classes/applicationContext.xml</param-value>
 </context-param>
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <listener>
  <listener-class>com.blogspot.jpdevelopment.ContextLoadListener</listener-class>
 </listener>
 <servlet>
  <servlet-name>CXFServlet</servlet-name>
  <display-name>CXF Servlet</display-name>
  <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>CXFServlet</servlet-name>
  <url-pattern>/ws/*</url-pattern>
 </servlet-mapping>
</web-app> 

Web service end points was configured in cxf-beans.xml
<bean name="serviceV1Impl" class="com.blogspot.jpdevelopment.v1.ServiceV1Impl">
 <property name="someService" ref="someService" />
</bean>

<jaxws:endpoint id="serviceV1"
 implementor="#serviceV1Impl" address="/serviceV1"
 wsdlLocation="classpath:ServiceV1.wsdl">
</jaxws:endpoint>  

XSD's was referenced inside the WSDL with relative paths, because the project should be deployable to both test and production with any modifications to the generated was file.