22 May 2014

A POJO service using an axis2 servlet within embedded jetty

“Alice came to a fork in the road. 'Which road do I take?' she asked.'Where do you want to go?' responded the Cheshire Cat.'I don't know,' Alice answered.'Then,' said the Cat, 'it doesn't matter.” 


“The sun is simple. A sword is simple. A storm is simple. Behind everything simple is a huge tail of complicated.” 


Embedded Jetty

Embedding Jetty 1.9.5 in your application is simple and in fact encouraged by it's creators. That there is a whole bunch of complexity behind the scenes is for moment irrelevant. We create a class that extends AbstractHandler, attach it to the server and start the server. The only requirement is the jetty.jar, which is easily found with maven.

HelloHandler.java
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class HelloHandler extends AbstractHandler {
  
    final String _greeting;
 
    final String _body;
 
    public HelloHandler() {
        _greeting = "Hello World";
        _body = null;
    }
 
    public HelloHandler(String greeting) {
        _greeting = greeting;
        _body = null;
    }
 
    public HelloHandler(String greeting, String body) {
        _greeting = greeting;
        _body = body;
    }
 
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        baseRequest.setHandled(true);
        response.getWriter().println("<h1>
" + _greeting + "<h1>
");
        if (_body != null) response.getWriter().println(_body);
    }
}

And then adding the Handler to a Jetty server.

SimplestServer.java
import org.eclipse.jetty.server.Server;

public class SimplestServer {
 public static void main(String[] args) throws Exception {
  Server server = new Server(1111);
  HelloHandler root = new HelloHandler();
  server.setHandler(root);

  server.start();
  server.join();
 }
}

Easy as two pies!

Run the application and test the server:
http://localhost:1111/

Embedded Axis

Running an Axis 1.6.2 HTTP server is almost as easy. Axis allows us to expose a POJO as a webservice and it takes care of the wsdl and xsd generation. This makes writing services very easy, but the trade-off is that the server configuration is a bit more tricky. Here we will create an Axis Server configuration from scratch (no axis2.xml). We only need a few jars: org.apache.axis2 - axis2 - 1.6.2, axis2-transport-http, axis2-transport-local and log4j - log4j - 1.2.17.

First we create our POJO. It's a simple service that can say "Hello" and subtract two numbers.

HelloPojo.java
public class HelloPojo {
 public String sayHello(String name) {
  return "Hello " + name;
 }

 public int subtract(int a, int b) {
  return a - b;
 }
}

Now we need to create an axis2 context from scratch. Remember we did not install axis.

Axis2SimpleServer.java
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.engine.AxisConfiguration;

public class Axis2SimpleServer {
 public static void main(String[] args) throws Exception {
  // create empty config context
  ConfigurationContext context = ConfigurationContextFactory
    .createDefaultConfigurationContext();
  AxisConfiguration cfg = context.getAxisConfiguration();
  // add a POJO to it
  AxisService service = AxisService.createService(
    HelloPojo.class.getName(), cfg);
  cfg.addService(service);
 }
}

and now we need to use this configuration to start our server.

  // Use the SimpleHTTPServer to specify a port
  HttpFactory f = new HttpFactory(context);
  f.setPort(1111);
  SimpleHTTPServer server = new SimpleHTTPServer(f);
  server.start();

If you run the application now and test the server you will not notice the problem immediately:
http://localhost:1111/
forwards to:
http://localhost:1111/axis2/services
and displays the POJO as a service. Click on the service an you get the WSDL.
All good so far!

The problem comes in when you execute the service:
http://localhost:1111/axis2/services/HelloPojo/subtract?a=99&b=12
No output is generated and a NullPointerException is generated at org.apache.axis2.transport.http.CommonsHTTPTransportSender.invoke(CommonsHTTPTransportSender.java:172) !
This happens because there is no output transport defined. 

Axis2 generates a default http input transport when we send it an empty configuration, but does not do the same for the output. This is easily fixed by adding a TransportSender to the configuration before we pass it to the server:

  TransportOutDescription transportOut = new TransportOutDescription("http");
  CommonsHTTPTransportSender sender = new CommonsHTTPTransportSender();
  sender.init(context, transportOut);
  transportOut.setSender(sender);
  cfg.addTransportOut(transportOut);

and now our class looks like this:
Axis2SimpleServer.java
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.transport.http.CommonsHTTPTransportSender;
import org.apache.axis2.transport.http.SimpleHTTPServer;
import org.apache.axis2.transport.http.server.HttpFactory;

public class Axis2SimpleServer {
 public static void main(String[] args) throws Exception {

  // create empty config context
  ConfigurationContext context = ConfigurationContextFactory
    .createDefaultConfigurationContext();
  AxisConfiguration cfg = context.getAxisConfiguration();
  // add a POJO to it
  cfg.addService(AxisService.createService(
    HelloPojo.class.getName(), cfg));
  
  // create a sender
  TransportOutDescription transportOut = new TransportOutDescription("http");
  CommonsHTTPTransportSender sender = new CommonsHTTPTransportSender();
  sender.init(context, transportOut);
  transportOut.setSender(sender);
  cfg.addTransportOut(transportOut);

  // deploy on an Axis2 server on default port 6060
  // new AxisServer().deployService(HelloPojo.class.getName());
  
  // Use the SimpleHTTPServer to specify a port
  HttpFactory f = new HttpFactory(context);
  f.setPort(1111);
  SimpleHTTPServer server = new SimpleHTTPServer(f);
  server.start();
 }
}

Adding services is now as easy as creating a POJO and adding it with:

  cfg.addService(AxisService.createService(
    GoodbyePojo.class.getName(), cfg));

Running an axis2 servlet inside Jetty

Put both of these together and we can run axis2 in Jetty by creating the configuration programmatically.

JettyAxisServer.java
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.AxisServiceGroup;
import org.apache.axis2.description.TransportInDescription;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.transport.http.AxisServlet;
import org.apache.axis2.transport.http.AxisServletListener;
import org.apache.axis2.transport.http.CommonsHTTPTransportSender;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;

public class JettyAxisServer {
 public static void main(String[] args) throws Exception {

  // create an empty configuration
  ConfigurationContext context = ConfigurationContextFactory
    .createDefaultConfigurationContext();
  AxisConfiguration cfg = context.getAxisConfiguration();

  // create a receiver
  TransportInDescription transportIn = new TransportInDescription("http");
  AxisServletListener receiver = new AxisServletListener();
  receiver.init(context, transportIn);
  receiver.setPort(1111);
  transportIn.setReceiver(receiver);
  cfg.addTransportIn(transportIn);

  // create a sender
  TransportOutDescription transportOut = new TransportOutDescription("http");
  CommonsHTTPTransportSender sender = new CommonsHTTPTransportSender();
  sender.init(context, transportOut);
  transportOut.setSender(sender);
  cfg.addTransportOut(transportOut);
  
  // add a group and a service
  AxisServiceGroup group = new AxisServiceGroup(cfg);
  group.setServiceGroupName("POJOs");
  AxisService service = AxisService.createService(
    HelloPojo.class.getName(), cfg);
  group.addService(service);
  cfg.addServiceGroup(group);
  cfg.addToAllServicesMap(service);

  // deploy as a servlet in Jetty
  Server server = new Server(1111);
  WebAppContext root = new WebAppContext();
  // pass the axis context to the servlet context
  root.setAttribute(AxisServlet.CONFIGURATION_CONTEXT, context);
  // create a servlet
  AxisServlet s = new AxisServlet();
  ServletHolder holder = new ServletHolder(s);
  root.addServlet(holder, "/axis2/*");
  root.setResourceBase("./src/main/webapp");
  server.setHandler(root);

  server.start();
  server.join();
 }
}

Note

For this project I suggest you use maven2 because it lets you find the dependencies quickly and also gets any required dependencies for you.