Accueil > Apache CXF, Mongo JEE > Mongo JEE [step4]

Mongo JEE [step4]


In [step3] we have improved our LogsServlet by using Mongo JEE. Servlet works well but it’s an old school mean.

Using servlet in this case will causes problem when you will wish to manage several operations (find all logs, find paginated logs, insert logs, etc). To manage that, you will have to create a servlet per operations or manage this dispatch at hand (for instance with a HTTP request dispatch parameter) like this :

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  DBCursor cursor = null;
  String dispatch = request.getParameter("dispatch");
  if ("all".equals(dispatch) {
	DB db = mongo.getDB("websites");
	DBCollection coll = db.getCollection("logs");

	DBCursor cursor = coll.find();
	ServletHelper.writeJson(cursor, response);
  } else if ("page".equals(dispatch) {
     ...
  }
}

It exists a lot of Java Web Framwework (Spring MVC, GWT, Wicket, Play!, JSF, Struts2, etc) which provides a controller framework which fix this problem. JAX-RS provides too an elegant mean to fix that : you develop a service class and you create a method for each operations. Here a JAX-RS sample :

@Path("/logs")
public class LogsService {

	@Path("/all")
	public Response findAll() {
	...
	}
	
	@Path("/page")
	public Response findPage() {
	...
	}
	
}

Once this JAX-RS service will be deployed, you will able to call :

In this article we will transform our LogsServlet with JAX-RS LogsService. We will use Apache CXF as JAX-RS implementation.

Download

You can download step4.zip which hosts the Eclipse Project which contains the explained sources in this article. To use it, unzip it and import this project in your Eclipse workspace. As soon as you will do that, your workspace will look like this :

This Eclipse project contains:

  • LogService is JAX-RS service which uses JAX-RS StreamingOutput.
  • MyJaxrsApplication is JAX-RS application which register the JAX-RS LogsService.
  • web.xml declares the Apache CXF JAX-RS implementation and set MyJaxrsApplication as JAX-RS Application

Download JARs with maven

This project contains the same dependencies than step3, and additional dependencies :

  • Apache CXF as JAX-RS implementation. To do that I use this maven dependency :
    <dependency>
    	<groupId>org.apache.cxf</groupId>
    	<artifactId>cxf-rt-frontend-jaxrs</artifactId>
    	<version>2.7.4</version>
    </dependency>
  • The project contains the JARs in the lib, but if you wish to download it with maven , see Download JARs with maven [step1].

    JAX-RS Overview

    JAX-RS: Java API for RESTful Web Services is a Java programming language API that provides support in creating web services according to the Representational State Transfer (REST) architectural pattern. JAX-RS uses annotations, introduced in Java SE 5, to simplify the development and deployment of web service clients and endpoints.

    It exists several JAX-RS implementation like :

    In this article we will use Apache CXF but the code of our JAX-RS service is not linked to Apache CXF and other JAX-RS implementation could be used.

    JAX-RS LogsService

    Create org.samples.mongodb.services.LogsService like this:

    package org.samples.mongodb.services;
    
    import java.io.IOException;
    import java.io.OutputStream;
    
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.core.StreamingOutput;
    
    import com.mongodb.DB;
    import com.mongodb.DBCollection;
    import com.mongodb.DBCursor;
    import com.mongodb.jee.MongoHolder;
    
    @Path("/logs")
    public class LogsService {
    
    	@GET
    	@Path("/all")
    	@Produces(MediaType.APPLICATION_JSON)
    	public Response findAll() {
    		DB db = MongoHolder.connect().getDB("websites");
    		DBCollection coll = db.getCollection("logs");
    		final DBCursor cursor = coll.find();
    
    		StreamingOutput output = new StreamingOutput() {
    			public void write(OutputStream out) throws IOException,
    					WebApplicationException {
    				com.mongodb.jee.util.JSON.serialize(cursor, out);
    			}
    		};
    		return Response.ok(output).build();
    	}
    
    }

    Here some explanation about this code :

    • To tell that a service should be deployed as JAX-RS service, the class must be annoted with javax.ws.rs.@Path.
      Here we use /logs :

      @Path("/logs")
      public class LogsService...
      

      It means that the URL which will consumes the service will starts with http://localhost:8080/jaxrs/logs/. If we declare with empty quote @Path(«  »), the URL will start with http://localhost:8080/jaxrs.

    • The method findAll is annotated with @GET, it means that our service will be available with HTTP GET method.
    • The method findAll is annotated with @Path(« /all »), it means that the service will be available with the following URL http://localhost:8080/jaxrs/logs/all
    • The method findAll is annotated with @Produces(MediaType.APPLICATION_JSON), it means that our service will update the HTTP response content type with « application/json ».
    • javax.ws.rs.core.StreamingOutput#write is called when service must write some content in the HTTP response OutputStream. In our case
      we implement it like this to write the mongo cursor as JSON array :

        
      StreamingOutput output = new StreamingOutput() {
      	public void write(OutputStream out) throws IOException,
      			WebApplicationException {
      		com.mongodb.jee.util.JSON.serialize(cursor, out);
      	}
      };
      

    MyJaxrsApplication – getClasses

    At this step we have defined our JAX-RS LogsService. Now we must indicate to the JAX-RS processor that it must deploy the service. To do that, there are several means (according the JAX-RS processor implementation). A generic mean which works with any JAX-RS processor is to extend the javax.ws.rs.core.Application which provides :

    • getClasses() which must return a Set of classes of JAX-RS services (and providers : we will see this notion of JAX-RS provider in the next article [step5]). The whole JAX-RS services classes which are included in the Set will be instantiated for each request (as soon as the service is called).
    • getSingletons() which must return a Set of instance which must be singleton. The whole JAX-RS services classes which are included in the Set will be instantiated just one time.

    Here we start using getClasses. Create Java class org.samples.mongodb.jaxrs.MyJaxrsApplication like this:

    package org.samples.mongodb.jaxrs;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.ws.rs.core.Application;
    
    import org.samples.mongodb.services.LogsService;
    
    public class MyJaxrsApplication extends Application {
    
    	private final Set<Class<?>> classes;
    
    	public MyJaxrsApplication() {
    		classes = new HashSet<Class<?>>();
    		addClass(LogsService.class);
    	}
    
    	@Override
    	public Set<Class<?>> getClasses() {
    		return classes;
    	}
    
    	protected void addClass(Class<?> clazz) {
    		classes.add(clazz);
    	}
    }

    In our case we override Application#getClasses() to add our LogsService class.

    web.xml

    At this step we have created our JAX-RS LogsService and registered it in our JAX-RS Application MyJaxrsApplication. Now we must configure the web.xml to declare the Apache CXF servlet which manages JAX-RS :

    • we should declare the Apache CXF servlet by setting our JAX-RS Application :
      <servlet>
      	<servlet-name>CXFServlet</servlet-name>
      	<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
      	<init-param>
      		<param-name>javax.ws.rs.Application</param-name>
      		<param-value>
      			org.samples.mongodb.jaxrs.MyJaxrsApplication
      		</param-value>
      	</init-param>
      </servlet>
      
    • In our case we wish to deploy our JAX-RS service with URL which starts with /jaxrs/* :
      <servlet-mapping>
      	<servlet-name>CXFServlet</servlet-name>
      	<url-pattern>/jaxrs/*</url-pattern>
      </servlet-mapping>
      

    Here the full code of web.xml :

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	id="WebApp_ID" version="2.5">
    	<display-name>Mongo JEE</display-name>
    
    	<servlet>
    		<servlet-name>CXFServlet</servlet-name>
    		<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
    		<init-param>
    			<param-name>javax.ws.rs.Application</param-name>
    			<param-value>
    				org.samples.mongodb.jaxrs.MyJaxrsApplication
    			</param-value>
    		</init-param>
    	</servlet>
    
    	<servlet-mapping>
    		<servlet-name>CXFServlet</servlet-name>
    		<url-pattern>/jaxrs/*</url-pattern>
    	</servlet-mapping>
    
    	<listener>
    		<listener-class>com.mongodb.jee.servlet.MongoServletContextListener</listener-class>
    	</listener>
    
    	<context-param>
    		<param-name>mongoURI</param-name>
    		<param-value>mongodb://localhost:27017</param-value>
    	</context-param>
    
    </web-app>

    Test LogsService

    • run the StartServer class to start the server.
    • access with your webbrowser to the URL http://localhost:8081/mongo/jaxrs/logs/all/ to see the JSON array of logs and see the « application/json » of the content type :

    LogsService – MongoStreamingOutput

    Mongo JEE provides the com.mongodb.jee.jaxrs.MongoStreamingOutput classes. So you can replace this code :

    StreamingOutput output = new StreamingOutput() {
    	public void write(OutputStream out) throws IOException, WebApplicationException {
    		com.mongodb.jee.util.JSON.serialize(cursor, out);
    	}
    };

    with MongoStreamingOutput :

    StreamingOutput output = new MongoStreamingOutput(cursor);

    MyJaxrsApplication – getSingletons

    At this step, our logs service is instantiated as soon as is called. To test that, you can add a counter in the LogsService constructor :

    private static int i = 0;
    
      public LogsService() {
        System.err.println(i++);
    }
    

    You will see on the console that each time you call http://localhost:8081/mongo/jaxrs/logs/all, the i variable is incremented, it means that LogsService is instanciated for each request.

    In our case it’s not interesting, and LogsService can be a singleton. To do that, Application#getSingletons() should be override to return an instance of LogsService.

    Modify the JAX-RS Application MyJaxrsApplication like this :

    package org.samples.mongodb.jaxrs;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.ws.rs.core.Application;
    
    import org.samples.mongodb.services.LogsService;
    
    public class MyJaxrsApplication extends Application {
    
    	private final Set<Object> singletons;
    
    	public MyJaxrsApplication() {
    		singletons = new HashSet<Object>();
    		addSingleton(new LogsService());
    	}
    
    	protected void addSingleton(Object singleton) {
    		singletons.add(singleton);
    	}
    
    	@Override
    	public Set<Object> getSingletons() {
    		return singletons;
    	}
    }

    Restart the server to check that counter is called just one time.

    Conclusion

    In this article we have seen how to transform our LogsServlet with a JAX-RS LogsService which uses StreamingOutput. Our LogsService is more elegant than LogServlet but code is linked to the JAX-RS API because it returns javax.ws.rs.core.Response. So it’s difficult to test the result of the LogService with a simple JUnit.

    In the next article [step5] we will fix this problem by changing the signature of the findAll method to return a Pojo Log instead of returning a JAX-RS Response.

    Catégories :Apache CXF, Mongo JEE
    1. août 5, 2013 à 1:41

      Hello again,

      This step appears to go a bit wrong under JBoss AS 7. I realize this tutorial uses Jetty (and that works great), but presumably Mongo JEE itself is intended to support other application servers? Could you please investigate?

      It appears JBoss AS 7 is finding JaxrsMongoApplication inside WEB-INF/lib/mongo-jee-1.0.0.jar and considering that to be *the* Application. So if you try to define your own app you get:

      « JBAS011232: Only one JAX-RS Application Class allowed. com.mongodb.jee.jaxrs.JaxrsMongoApplication com.myapp.server.MyApplication »

      Equally if you just try to add a web.xml mapping without defining your own app you get:

      « JBAS011233: Please use either @ApplicationPath or servlet mapping for url path config »

      Regards,

      Richard.

    2. août 5, 2013 à 3:34

      FYI: if I manually remove JaxrsMongoApplication from mongo-jee-1.0.0.jar then it all works great on JBoss AS 7!

      • août 6, 2013 à 9:24

        Hi Richard,

        Many thank’s for this info.

        I have never used JBoss. If I understand the problem JaxrsMongoApplication shouldnot inside the mongo-jee JAR.
        I think this class can be helpfull. Could you try to set this class as abstract and tell me if it works.

        It should be cool if you create an issue with JBoss at https://github.com/angelozerr/mongo-jee/issues

        Many thank’s

        Regards Angelo

    1. Mai 14, 2013 à 1:19
    2. Mai 14, 2013 à 10:43
    3. Mai 15, 2013 à 9:42

    Laisser un commentaire