Archive
Mongo JEE [step6]
In [step5] we have modified our JAX-RS LogsService to returns List of Pojo by using :
- MongoJack to get List of Pojo from Mongo DB.
- Apache CXF JSONProvider based on Jettison to serialize the Pojo Log to JSON stream.
With Pojo Mapper MongoJack, the LogsService looks like this:
@GET @Path("/all") @Produces(MediaType.APPLICATION_JSON) public List<Log> findAll() { DB db = MongoHolder.connect().getDB("websites"); DBCollection dbColl = db.getCollection("logs"); JacksonDBCollection<Log, String> coll = JacksonDBCollection.wrap( dbColl, Log.class, String.class); return coll.find().toArray(); }
When I have studied how to use Mongo in JEE Application with JAX-RS, I have read the great article Modern Web Apps using JAX-RS, MongoDB, JSON, and jQuery. After reading this article, I told me « why we need to use a Pojo Mapper with JAX-RS although Mongo manages their data with BSON representation? », on other words why we could not develop our JAX-RS service like this :
@GET @Path("/all") @Produces(MediaType.APPLICATION_JSON) public DBCursor findAll() { DB db = MongoHolder.connect().getDB("websites"); DBCollection coll = db.getCollection("logs"); return coll.find(); }
Using DBCursor avoids to create a Pojo and avoids to serialize the Pojo to JSON. So I have decided to create Mongo JEE project. In this article we will modify our JAX-RS LogsService to use Mongo DBCursor in the service by using the JAX-RS provider of the Mongo JEE.
Mongo JEE [step5]
In [step4] we have tranformed our LogsServlet to a JAX-RS LogsService which uses JAX-RS StreamingOutput. At this step the service returns JAX-RS Response :
@Path("/logs") public class LogsService { @Path("/all") public Response findAll() { ... } }
It’s very difficult to use this service in other context than JAX-RS (like using the service in fat client, using the service in a JUnit etc). Imagine you wish to write some JUnits which tests that our LogsService service returns some logs. With our current logs service, it’s hard to write that :
@Test public void testNotEmptyLogs() { LogsService service = new LogsService(); Response jaxrsResponse = service.findAll(); // How to get the list of logs to test that logs are not empty? }
When you develop a service, the best mean is that you don’t link it to a framework. For instance :
- if your service returns list of Pojo :
@Path("/logs") public class LogsService { @Path("/all") public List<Log> findAll() { ... } }
you can write this test :
@Test public void testNotEmptyLogs() { LogsService service = new LogsService(); List<Pojo> pojoList = service.findAll(); Assert.assertFalse(pojoList.isEmpty()); }
- if your service returns a Mongo DBCursor :
@Path("/logs") public class LogsService { @Path("/all") public DBCursor findAll() { ... } }
you can write this test :
@Test public void testNotEmptyLogs() { LogsService service = new LogsService(); DBCursor cursor= service.findAll(); Assert.assertFalse(cursor.toArray().isEmpty()); }
With JAX-RS you can write services which returns anything (List of Pojo, Mongo DBCursor), but it’s not magic and you need JAX-RS provider to serialize (in our case) List of Pojo, DBCursor etc into JSON stream :
- in this article we will see how to manage JAX-RS service with Pojo. We will use MongoJack to get list of Log Pojo from Mongo DB and the Apache CXF JSONProvider, the JAX-RS provider to serialize Pojo to a JSON stream.
- in the next article [step6] we will see how to manage JAX-RS service with Mongo DBCursor by using the Mongo JEE JAX-RS providers.
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 :
- http://localhost:8081/mongo/jaxrs/logs/all to returns list of logs.
- http://localhost:8081/mongo/jaxrs/logs/page to returns list of paginated logs.
In this article we will transform our LogsServlet with JAX-RS LogsService. We will use Apache CXF as JAX-RS implementation.
Mongo JEE [step0]
Several months ago, I have studied how to use Mongo DB in a JEE Application. I read the great article Modern Web Apps using JAX-RS, MongoDB, JSON, and jQuery which explains how to develop modern Web Apps :
- on client side, AJAX with jQuery is used to consume JSON by calling a REST service.
- on server side, REST service managed with JAX-RS produces JSON stream coming from MongoDB. The REST service calls MongoDB with mongo-jackson-mapper which gets JSON stream from MongoDB (which stores their data with BSON format which looks like JSON) and serializes the JSON to build a Pojo. The REST service returns the Pojo and JAX-RS implementation deserializes the Pojo to produce JSON for the jQuery client.
On other words, the server produces JSON and not the whole HTML page and the client consumes JSON to refresh the UI.
I like this architecture, but I tell me why we could to use directly the JSON stream from MongoDB instead of serialize/deserialize a Pojo. That’s why I have created Mongo JEE project which helps you to use Mongo in a JEE Application.
I have decided to write Mongo JEE articles which shows in action the feature of Mongo JEE. Those articles will explain step by step how to develop a Dojo Grid with pagination which consumes a JAX-RS REST service which produces JSON coming directly from the MongoDB with Java Driver and not with Pojo-Mapper. At the end of those articles we will have a paginated grid populated with logs data :
- step [1] : the user call the index.htm page.
- step [2] : the server returns the content of the index.htm page which contains the code of the Dojo grid (without the data).
- step [3] : once the dojo grid is built, it calls the REST service to populate the grid by sending on the HTTP Header the « Range » parameter (ex : items=0-9). This Range parameter tells to the server the from and to item index that grid needs to populate data.
- step [4] : the REST service request the MongoDB and returns the JSON coming from the MongoDB to the Web client. The dojo grid receives the JSON and populate the grid. When page link is clicked, the step [3] and [4] are re-played.
In this article, we will install and initialize MongoDB by creating a websites database which contains logs collection. A logs is just composed with date created and url.
Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step4]
In [step3] we have managed our UserService on Client and Server side with JAX-RS and Apache CXF DOSGi.
Embedding an HTTP server in Equinox
At this step the server side works with the HTTP Server Jetty which is an OSGi bundles. This mode is called Embedding an HTTP server in OSGi container : it means that HTTP server and WEB application are OSGi bundles. The OSGi container starts the HTTP server bundle. Embedding an HTTP server in Equinox explains how this mean is done with Equinox.
You can resume « embedding an HTTP server in OSGi container » like this :
OSGi container -> HTTP Server (bundle) -> WEB Application (bundle).
Equinox in a Servlet Container (ServletBridge)
It works great but sometimes you have not the luck to have an HTTP Server which supports OSGi like Apache Tomcat. For instance in our case, when we wished to deploy our XDocReport RAP Application on CloudBees, whish provides an Non OSGI HTTP Server Apache Tomcat.
Fortunatly, Server-Side Equinox provides Embedding OSGi container in a servlet container mode : it means that it’s a WEB Application which contains the OSGi container (and another bundles) which start the OSGi container. Equinox in a Servlet Container explains how this mean is done with Equinox.
You can resume « embedding OSGi container in a servlet container » like this :
HTTP Server -> WEB Application -> OSGi container.
To manage this mode you must create a classic WAR which hosts your OSGi plugins and ServletBridge. In this article we will explain how to create a WAR with our UserService on server side with the Eclipse Plug-In Libra WAR Product which was created for creating WAR from RAP Application launches. But it can be used too with OSGi launches (like we need in this article).
To create a WAR of CXF DOSGi application (server side), you must :
- register your JAX-RS service with HttpService :
<!-- HttpService --> <entry key="org.apache.cxf.rs.httpservice.context" value="/UserService" />
- create a WAR which hosts OSGi bundles and ServletBridge. WAR Product gives you the capability to create a *.warproduct file from a launch (RAP, RCP, OSGi lanches, etc). This *.warproduct file is used to export it into a WAR to generate WAR.
At the end of this article we will create the the cxf-dosgi-jpa.war WAR. I have deployed this WAR on the CloudBees to see it on action. You can test for instance go at http://cxf-dosgi-war.opensagres.cloudbees.net/UserService/user/findAll to see JSON of the all users.
Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step3]
In [step2] we have implemented the JAX-RS Client which consumes the UserService#findAll() with JAX-RS:
@Path("/user") public interface UserService { @GET @Path("/findAll") @Produces(MediaType.APPLICATION_JSON) Collection<User> findAll(); ... }
We have seen that JAX-RS Client fails when UserService#findAll(Pageable pageable) is called, because we have not annoted this method with JAX-RS:
1031 [Thread-5] ERROR org.apache.cxf.jaxrs.client.ClientProxyImpl - Method fr.opensagres.services.UserService.findAll is not a valid resource method at org.apache.cxf.jaxrs.client.ClientProxyImpl.reportInvalidResourceMethod(ClientProxyImpl.java:546)
In this article we wish to export and import with JAX-RS the UserService#findAll(Pageable pageable) which returns the paginated list of the User which uses Spring Data – Commons structures :
- Pageable : this parameter is the pagination request.
- Page: the findAll method returns this structure which is the result of the pagination.
On other words we will annotate the UserService#findAll(Pageable pageable) like this :
@POST @Path("/findAllPage") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) Page<User> findAll(Pageable pageable);
However when I have managed JAX-RS with Spring Data structure, I had 2 big problems coming from :
- Spring Data – Commons: Spring Data Page and Pageable are interfaces and not Pojo. JAX-RS works with Pojo which are annotated with JAXB annotations. How do manage Spring Data Page and Pageable interfaces with JAX-RS? Fortunately, JAXB 2.0 provides XmlJavaTypeAdapter (please read Using JAXB 2.0’s XmlJavaTypeAdapter) which gives the capability to serialize Page and Pageable interfaces with JAXB. I have suggested this idea to Oliver Gierke, the lead of the Spring Data and he is developing JAXB Adapter with SpringDataJaxb.java. However in this article we will not use this class (because this class is not finished) but we will create our own JAXB adapter which will be more simply to understand how to work the JAXB Adapter.
- Apache CXF: on server side when CXF tries to serialize the Page class with JAXB, it fails because the JAXBContext doesn’t know the User class. This problem comes from that CXF is not able to populate the JAXBContext automaticly with Parameterized class (in our case , we have Page<T> and T=User, the JAXBContext is filled just with Page but not with User). You can fix the problem by setting extra class to the JSONProvider :
<bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.JSONProvider"> <property name="singleJaxbContext" value="true" /> <property name="extraClass"> <list> <value>fr.opensagres.domain.User</value> </list> </property> </bean>
We will do that in this article, but I have created the patch CXF-4359 which fixes the explained problem below to avoid declaring extra classes for JSONProvider.
Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step2]
In [step1] we have downloaded CXF DOSGi « Multi Bundle Distribution » and created the fr.opensagres.remoting.exporter.dosgi.jaxrs bundle
to export on server side the UserService#findAll() with JAX-RS:
@Path("/user") public interface UserService { @GET @Path("/findAll") @Produces(MediaType.APPLICATION_JSON) Collection<User> findAll(); ... }
In this article we will create the importer bundle fr.opensagres.remoting.importer.dosgi.jaxrs which will create a JAX-RS Client with Spring bean :
<jaxrs:client id="jaxrsUserService" address="http://127.0.0.1:9000/fr/opensagres/services/UserService" serviceClass="fr.opensagres.services.UserService" inheritHeaders="true"> </jaxrs:client>
and will register this JAX-RS Client in the OSGi register services as UserService :
<osgi:service ref="jaxrsUserService" interface="fr.opensagres.services.UserService" />
For recall, the Simple Client bundle fr.opensagres.simpleclient consumes a UserService in the Thread FindAllUsersThread from the OSGi registry services and display User list every 5 seconds. In our case the UserService instance will be the JAX-RS Client retrieved from the OSGi registry services :
<osgi:reference id="userService" interface="fr.opensagres.services.UserService" cardinality="0..1" timeout="1000" />
This JAX-RS Client UserService will be setted in the FindAllUsersThread with Dependency Injection :
<bean id="FindAllUsersThread" class="fr.opensagres.simpleclient.FindAllUsersThread" init-method="start" destroy-method="interrupt"> <property name="userService" ref="userService"></property> </bean>
This FindAllUsersThread (client side) consumes the UserService#findAll() every 5 seconds to display user list on the console :
// 1) findAll users = userService.findAll(); displayUsers("findAll", users);
Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step1]
The Eclipse RCP/RAP with Spring DM, Spring Data JPA and Remoting [step11] article explains that we wish to manage remoting for UserService :
package fr.opensagres.services; import java.util.Collection; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import fr.opensagres.domain.User; public interface UserService { Collection<User> findAll(); Page<User> findAll(Pageable pageable); Collection<User> findByFirstNameLikeAndLastNameLike(String firstName, String lastName); Page<User> findByFirstNameLikeAndLastNameLike(String firstName, String lastName, Pageable pageable); User saveUser(User user); }
- the server side will export the UserService which retrieves User from Database with JPA.
- the client side, RCP Application, RAP Application and OSGi Bundle Activator will import the UserService to consume the exported UserService.
In this article I will explain how to manage remoting with CXF DOSGi with JAX-RS. DOSGi Releases provides 2 distributions:
- Multi Bundle Distribution: This distribution is a zip file containing the Distributed OSGi bundles, as well as all their dependencies.
- Single Bundle Distribution: This is a convenience distribution of a single bundle that embeds all the dependencies.
In our case we will use « Multi Bundle Distribution » which is more complex to use it than « Single Bundle Distribution », but we need to do that because we have already Spring DM.
When I have used CXF DOSGi to manage remoting with our UserService, I have met several problems :
- « Multi Bundle Distribution » is very hard to use, because you need install a lot of bundles and set the Auto-Start to true for some bundles.
- Using JAX-RS was not simply with the UserService because it uses Spring Data Page and Pageable interfaces (and not Pojo).
I will explain that in the Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi articles. In this article we will export the UserService#findAll() with JAX-RS:
package fr.opensagres.services; import java.util.Collection; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import fr.opensagres.domain.User; @Path("/user") public interface UserService { @GET @Path("/findAll") @Produces(MediaType.APPLICATION_JSON) Collection<User> findAll(); ... }
On other words, we will
- Install CXF DOSGi by downloading the well JARS bundles.
- Use JAX-RS annotation for UserService and JAXB annotation for User domain.
- Create the exporter services bundle (server side) to export the UserService with JAX-RS.
Eclipse RCP/RAP with Spring DM, Spring Data JPA and Remoting [step11]
In [step10] we have created RAP Application from the RCP Application. At this step we have several Client Layer :
- OSGi Bundle Activator (simpleclient)
- RCP Application
- RAP Application
which consumes Service Layer : UserService (from Mock Dao, JPA Dao) to retrieve list of User :
package fr.opensagres.services; import java.util.Collection; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import fr.opensagres.domain.User; public interface UserService { Collection<User> findAll(); Page<User> findAll(Pageable pageable); Collection<User> findByFirstNameLikeAndLastNameLike(String firstName, String lastName); Page<User> findByFirstNameLikeAndLastNameLike(String firstName, String lastName, Pageable pageable); User saveUser(User user); }
Here, The Client and Service Layer are in the same OSGi container. Now we wish manage Client/Server architecture to have :
- Client Layer in an OSGi container
- Service Layer in an other OSGi container
the Client Layer will consume Service Layer with remoting mean.
JAX-WS with Apache CXF and Eclipse [step2]
In [step1], we have configured CXF Eclipse Plugin to use CXF 2.4.2 and created an empty Dynamic Web project with Tomcat 7.
In this article we will create a sample Java class HelloServiceImpl and publish it as WebService by using CXF Eclipse wizard. Basicly, this wizard will :
- modify the HelloServiceImpl to add well-formed JAX-WS annotation.
- modify or create the Spring beans.xml file which declares the WebServices (in our case HelloServiceImpl) to publish.
- modify the web.xml to declare the Spring CXF Servlet used to dispatch to the proper WebService declared in the beans.xml.