MessageBodyWriter not found vogella tutorial - java

I am attempting to recreate the most excellent vogella tutorial for create REST with java, JAX-RS and Jersey.
I'm using eclipse Kepler with Java-EE perspective, tomcat 7.0.
I have create the Todo class, the TodoResource class with the appropriate annotations and deployed on tomcat 7. I have imported the jaxrs-ri libs into the WEB-INF/lib folder as instructed.
Todo class:
package com.vogella.jersey.jaxb.model;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Todo {
private String summary;
private String description;
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
TodoResource with annotations:
package com.vogella.jersey.jaxb.model;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/todo")
public class TodoResource {
// This method is called if XMLis request
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Todo getXML() {
Todo todo = new Todo();
todo.setSummary("This is my first todo");
todo.setDescription("This is my first todo");
return todo;
}
// This can be used to test the integration with the browser
#GET
#Produces({ MediaType.TEXT_XML })
public Todo getHTML() {
Todo todo = new Todo();
todo.setSummary("This is my first Todo");
todo.setDescription("This is my first Todo");
return todo;
}
}
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" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>com.vogella.jersey.first</display-name>
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.vogella.jersey.jaxb</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
I have also created the client as instructed.
Test.java:
package com.vogella.jersey.first.client;
import java.net.URI;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientResponse;
import org.glassfish.jersey.message.internal.MediaTypes;
public class Test {
public static void main(String[] args) {
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target(getBaseURI());
System.out.println(target.path("rest").path("todo").request()
.accept(MediaType.APPLICATION_XML ).get(Response.class)
.toString());
System.out.println(target.path("rest").path("todo").request()
.accept(MediaType.APPLICATION_JSON ).get(Response.class)
.toString());
}
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost:8080/com.vogella.jersey.jaxb").build();
}
}
Everything works perfectly for the MediaType.APPLICATION_XML - the server returns:
InboundJaxrsResponse{ClientResponse{method=GET, uri=http://localhost:8080/com.vogella.jersey.jaxb/rest/todo, status=200, reason=OK}}
However, for the MediaType APPLICATION_JSON - which is what I actually need, I get an error:
InboundJaxrsResponse{ClientResponse{method=GET, uri=http://localhost:8080/com.vogella.jersey.jaxb/rest/todo, status=500, reason=Internal Server Error}}
Tomcat clearly shows me the problem - it seems to me it doesn't know how to return a JSON response -
Nov 02, 2014 11:59:19 AM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=application/json, type=class com.vogella.jersey.jaxb.model.Todo, genericType=class com.vogella.jersey.jaxb.model.Todo.
My understanding is that the jaxrs-ri 2.13 bundle includes everything required including dependencies to let me do this - and that I don't need to add any kind of JSON provider. I have done so anyway, I have tried adding gson for example, I have downloaded the moxy jars and attempting to add them to my WEB-INF/lib folder and deploy - all to no avail. I don't know if I'm completely out in the weeds, or if I'm missing something simple?

My understanding is that the jaxrs-ri 2.13 bundle includes everything required including dependencies to let me do this - and that I don't need to add any kind of JSON provider.
That's actually incorrect. As stated at the Jersey User Guide 8.1. JSON
Jersey JSON support comes as a set of extension modules where each of these modules contains an implementation of a Feature that needs to be registered into your Configurable instance (client/server). There are multiple frameworks that provide support for JSON processing and/or JSON-to-Java binding. The modules listed below provide support for JSON representations by integrating the individual JSON frameworks into Jersey. At present, Jersey integrates with the following modules to provide JSON support:
MOXy - JSON binding support via MOXy is a default and preferred way of supporting JSON binding in your Jersey applications since Jersey 2.0. When JSON MOXy module is on the class-path, Jersey will automatically discover the module and seamlessly enable JSON binding support via MOXy in your applications. (See Section 4.3, “Auto-Discoverable Features”.)
Among a few others
So the main Jersey download doesn't come with these extra modules. We need to obtain them separately. That being said, the easiest way to get the required jersey-media-moxy is through Maven.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.13</version>
</dependency>
If you're not using Maven (which looking through the tutorial, it doesn't), you're going to have to do some searching for the dependencies. The jersey-media-moxy artifact has 16 dependencies, but Fortunately, most are contained within the Jersey distribution. So after filtering out what was already included in the Jersey distro, these are the remaining jars you will have to find on your own (I just created a User Library to test out)
Adding these dependencies will get the example up and running. Tested and works as expected after adding these.
Now you have Eclipse, which I assume came with the Maven (m2e) plugin. So maybe the easiest way to get these dependencies is to create a new Maven project, and add the dependency shown above. After you build the project, maven should download all the extra dependencies into your local Maven Repo. Just grab them from there for your main project.
Other Resources/Notes
Jersey User Guide
I would download the Jersey Example package, which is more up to date then the tutorial you are using.
If you don't know Maven, I would strongly suggest learning at least the basic of dependency management, and let the build framework grab all the dependencies for you. Also all the examples in the examples package uses Maven, so it would help to know the basics.

If your are using Jersey-2
Web.xml Configuration
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servletclass>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.oracle.restful</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
List of Libraries required
org.eclipse.persistence.moxy-2.6.3.jar
org.eclipse.persistence.core-2.6.3.jar
org.eclipse.persistence.asm-2.6.3.jar
org.eclipse.persistence.antlr-2.6.3.jar
jersey-media-moxy-2.23.1.jar
jersey-entity-filtering-2.23.1.jar
Run project it will work. Also check your JAXB classed because it will internally use xml annotation to convert pojo object to JAXB

Have you written any custom MessageBodyWriter for your marshalling from Java to JSON. If yes then you need to have the #Produces annotation with 'application/json' in the provider implementation. For more details refer http://h2labz.blogspot.in/2014/12/marshalling-java-to-json-in-jax-rs.html

Related

Deploying a REST API to AWS

I am making a distributed system as a school project and I need to have a REST service. This will be a simple service with a login/register function and some information transfer.
I have made the REST API in Java in NetBeans. It works fine locally, but I am having difficulties to put it on my AWS server. I have no experience with servers, so I don't really know how it works. I thought that it should easy to get the service up and running on a server.
So far I have used this guide for the REST and tried to deploy the war-file with Elastic Beanstalk.
My Java code:
ApplicationConfig.java
package dk.dtu.ds.login;
import java.util.Set;
import javax.ws.rs.core.Application;
#javax.ws.rs.ApplicationPath("CoL")
public class ApplicationConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
addRestResourceClasses(resources);
return resources;
}
/**
* Do not modify addRestResourceClasses() method.
* It is automatically populated with
* all resources defined in the project.
* If required, comment out calling this method in getClasses().
*/
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(dk.dtu.ds.login.Login.class);
}
}
Login.java
package dk.dtu.ds.login;
import cleanoutloudserver.ICleanOutLoud;
import java.net.MalformedURLException;
import java.net.URL;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
#Path("login")
public class Login {
// HTTP Get Method
#GET
#Path("dologin")
// Produces JSON as response
#Produces(MediaType.APPLICATION_JSON)
// Query parameters are parameters: http://localhost/colrest/CoL/login/dologin?username=s150157&password=1234
public String doLogin(#QueryParam("username") String uname, #QueryParam("password") String pwd) throws MalformedURLException, Exception {
URL url = new URL("http://ec2-52-43-233-138.us-west-2.compute.amazonaws.com:3769/col?wsdl");
QName qname = new QName("http://cleanoutloudserver/", "CleanOutLoudImplService");
Service service = Service.create(url, qname);
ICleanOutLoud col = service.getPort(ICleanOutLoud.class);
String token = col.login(uname, pwd);
token = Utility.constructJSON(token);
System.out.println("\nChecking credentials = true\n");
return token;
}
}
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>RESTWebApp</display-name>
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>dk.dtu.ds.login</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
When I then try to open the path for the service, I get a blank page. My Chrome console says "GET (link) 404 (Not Found)"
Since I am not that familiar with HTTP and servers, I don't know what to do.
Isn't there an easy way to deploy a simple REST service with AWS or have I done something wrong?
I have really tried to search google to find help, but there has been no success so far.
It seems like i got too confused by all the guides out there.
I found an easy solution and installed Tomcat on the EC2 instance, so I didn't even need to use Beanstalk.
All I did was following this guide and uploaded the war-file in the Web Application Manager and now it works fine.
Thanks for the comments they helped me on the way to find a solution.

ExceptionMapper methods not getting invoked in Jersey Project [duplicate]

I am trying to invoke this exception mapper to return a 404 not found response but it keeps returning 500 internal error. Jersey version is 2.22.1. Code snippet below. Appreciate all help.
Thanks.
Exception mapper class.
package org.learn.rest.messengerdemo.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
#Provider
public class DataNotFoundExceptionMapper implements ExceptionMapper<DataNotFoundException>{
#Override
public Response toResponse(DataNotFoundException ex) {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
Exception Class.
package org.learn.rest.messengerdemo.exception;
public class DataNotFoundException extends RuntimeException{
private static final long serialVersionUID = 2176642539344388961L;
public DataNotFoundException(String message)
{
super(message);
}
}
Service class's method that throws.
public Message getMessage(long messageId) {
Message message = messages.get(messageId);
if(message == null)
{
throw new DataNotFoundException("Message with id " + messageId + " not found");
}
return message;
}
And the resource class.
#GET
#Path("/{messageId}")
public Message getMessage(#PathParam("messageId") long messageId) {
return messageService.getMessage(messageId);
}
Looking at the web.xml from your previous question, you have this
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>org.learn.rest.messengerdemo.resources</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
What this init-parm jersey.config.server.provider.packages says is that Jersey should scan the named package for #Path annotated resource class and #Provider annotated provider classes and register them.
You only have the resources package org.learn.rest.messengerdemo.resources listed, but you ExceptionMapper is in a different package. The default behavior is to scan recursively, meaning sub-packages also. So if you listed org.learn.rest.messengerdemo instead, you would hit both the resources package and the exceptions package. Or you could list both packages, separated by comma or semi-colon. Either way would work
<param-value>org.learn.rest.messengerdemo</param-value>
<!-- OR -->
<param-value>
org.learn.rest.messengerdemo.resources,
org.learn.rest.messengerdemo.exception
</param-value>
Please note that this is not problem of annotation. i have seen people given comments that due to #Provider annotation its not registered, if you have imported correct provider it will work. Please find my solution below
I have encountered the same issue while develop sample REST API. While creating REST API i have given base package name like org.manish.rest.message, I supposed to create every other packages under the base package like this
model - org.manish.rest.message.model
database - org.manish.rest.message.database
resource - org.manish.rest.message.resource
in web.xml init param was given like this
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>org.manish.rest.message</param-value>
</init-param>
It means, i have registered my base package in web.xml, what ever package i will create under this; will be consider by JAX-RS based on my call and requirement. But when i created my exception package by mistake i put package name org.manish.rest.exception. Since this was not registered in web.xml so my complete exception class was not considered to handle exception by JAX-RS. As a correction, i have just modified my exception package name from org.manish.rest.exception to org.manish.rest.message.exception
After that i executed once in post man and i got expected result.
Hope this can solve your query.
Thanks Manish
I'd like to propose another option ...
change the mapper package to match the resource package where the exception could normally be caught.
Assuming the resource classes package is:
package org.learn.rest.messengerdemo.resources;
change the mapper class package from:
package org.learn.rest.messengerdemo.exception;
to:
package org.learn.rest.messengerdemo.resources;
That way you won't be fiddling with framework generated files or uncommon syntax.
I still don't grasp why a try/catch isn't used but I'm rebuilding my Java knowledge so I won't inject an opinion or request any. If its current practice then I'll learn that soon enough. And mappers are an interesting opportunity at that.

Integrating JAX-RS and Java Servlets

I'm a university student, currently developing an Android app for a module. Working on connecting it to a server to perform logins, etc. The department have given us a server to use and instructed us to use Servlets. I would rather do it a Restful manner, seeming as it's an industry standard. Here is the code I have written so far:
import javax.ws.rs.QueryParam;
/**
*
* #author Tom
*/
public class Login {
public boolean doLogin(#QueryParam("email") String email) {
return checkCredentials(email);
}
private boolean checkCredentials(String email){
boolean result = false;
if (email != ""){
try {
result = DBConnection.checkLogin(email);
} catch (Exception e) {
result = false;
}
} else {
result = false;
}
return result;
}
}
I wrote another class, DBConnection, but this seems to work ok (using JDBC to connect to the MySQL database).
The problem I'm having, is that when I run the webserver (using Jetty, and ant is the build tool) and try to access the page on the server through my browser, it just gives me a 503, Servlet not initialised error. I assume this is because I'm not extending the HttpServlet class? Here is some example Servlet code they gave us:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class Product extends HttpServlet
{
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
{
httpServletResponse.setContentType("text/plain");
PrintWriter out = httpServletResponse.getWriter();
out.println("Hello");
out.close();
}
}
They've given us a file called JettyStart.java, which starts the web server when you run ant:
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHttpContext;
public class JettyStart
{
public static void main(String[] args) throws Exception
{
//A server running at http://localhost:8085 is created
Server server = new Server();
server.addListener(":8085");
ServletHttpContext context = (ServletHttpContext) server.getContext("/");
context.addServlet("/path/to/Login", "package.name.Login");
server.start();
}
}
So how would I go about integrating Servlets into my Restful approach to communicating with the server/database? Or am I doing it all wrong?
I'm not sure if this is an answer, but you may have better luck making your project maven-based and using the jetty-maven-plugin. I have, personally. It's easy to set up if you have an IDE which can produce a simple maven archetype. You just drop the plugin into your pom and run mvn jetty:run from the command line. Wiring up the JAX-RS web services isn't too complicated, you just give them the correct annotations like so:
package com.my.project.services;
// imports here
#Path("/login")
public class Login extends HttpServlet {
#GET
#Produces({"text/html", MediaType.TEXT_HTML})
public String getLoginInfo(#QueryParam("email") String email) {
// ...
}
}
And that should be enough to get them picked up by the jetty servlet container as long as your web.xml is set up properly. If you use a maven webapp archetype this may be done for you, otherwise you'll have to poke around a bit, but if it helps this is what (the relevant parts of) my web.xml looks like in one of my projects:
<web-app>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.my.project.services</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>rest</servlet-name>
<url-pattern>/myproject/rest/*</url-pattern>
</servlet-mapping>
</web-app>
The mapping here is saying take the servlet-name "rest" and map it to the package com.my.project.services where all my JAX-RS services live, with the #Path value appended to the end (so the login service above would be located at /myproject/rest/login). You can set the mapping paths up any way you want.
edit: should mention my project is also using Jersey. Here's a good guide to setting up a project like this: http://crunchify.com/how-to-build-restful-service-with-java-using-jax-rs-and-jersey/

Jersey ExceptionMapper not being invoked

I am trying to invoke this exception mapper to return a 404 not found response but it keeps returning 500 internal error. Jersey version is 2.22.1. Code snippet below. Appreciate all help.
Thanks.
Exception mapper class.
package org.learn.rest.messengerdemo.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
#Provider
public class DataNotFoundExceptionMapper implements ExceptionMapper<DataNotFoundException>{
#Override
public Response toResponse(DataNotFoundException ex) {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
Exception Class.
package org.learn.rest.messengerdemo.exception;
public class DataNotFoundException extends RuntimeException{
private static final long serialVersionUID = 2176642539344388961L;
public DataNotFoundException(String message)
{
super(message);
}
}
Service class's method that throws.
public Message getMessage(long messageId) {
Message message = messages.get(messageId);
if(message == null)
{
throw new DataNotFoundException("Message with id " + messageId + " not found");
}
return message;
}
And the resource class.
#GET
#Path("/{messageId}")
public Message getMessage(#PathParam("messageId") long messageId) {
return messageService.getMessage(messageId);
}
Looking at the web.xml from your previous question, you have this
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>org.learn.rest.messengerdemo.resources</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
What this init-parm jersey.config.server.provider.packages says is that Jersey should scan the named package for #Path annotated resource class and #Provider annotated provider classes and register them.
You only have the resources package org.learn.rest.messengerdemo.resources listed, but you ExceptionMapper is in a different package. The default behavior is to scan recursively, meaning sub-packages also. So if you listed org.learn.rest.messengerdemo instead, you would hit both the resources package and the exceptions package. Or you could list both packages, separated by comma or semi-colon. Either way would work
<param-value>org.learn.rest.messengerdemo</param-value>
<!-- OR -->
<param-value>
org.learn.rest.messengerdemo.resources,
org.learn.rest.messengerdemo.exception
</param-value>
Please note that this is not problem of annotation. i have seen people given comments that due to #Provider annotation its not registered, if you have imported correct provider it will work. Please find my solution below
I have encountered the same issue while develop sample REST API. While creating REST API i have given base package name like org.manish.rest.message, I supposed to create every other packages under the base package like this
model - org.manish.rest.message.model
database - org.manish.rest.message.database
resource - org.manish.rest.message.resource
in web.xml init param was given like this
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>org.manish.rest.message</param-value>
</init-param>
It means, i have registered my base package in web.xml, what ever package i will create under this; will be consider by JAX-RS based on my call and requirement. But when i created my exception package by mistake i put package name org.manish.rest.exception. Since this was not registered in web.xml so my complete exception class was not considered to handle exception by JAX-RS. As a correction, i have just modified my exception package name from org.manish.rest.exception to org.manish.rest.message.exception
After that i executed once in post man and i got expected result.
Hope this can solve your query.
Thanks Manish
I'd like to propose another option ...
change the mapper package to match the resource package where the exception could normally be caught.
Assuming the resource classes package is:
package org.learn.rest.messengerdemo.resources;
change the mapper class package from:
package org.learn.rest.messengerdemo.exception;
to:
package org.learn.rest.messengerdemo.resources;
That way you won't be fiddling with framework generated files or uncommon syntax.
I still don't grasp why a try/catch isn't used but I'm rebuilding my Java knowledge so I won't inject an opinion or request any. If its current practice then I'll learn that soon enough. And mappers are an interesting opportunity at that.

Java Webservice REST proper design best pracice

I have developed a webservice in Java that runs as a servlet on a tomcat, parameters for the application are given to the servlet via the get request (e.g. servlet?method=search&query=searchterm123), the servlet recognizes if the method and the query are defined and in the case of an error gives back a string that is manually wrapped in xml code that I hardcoded via this.writer.println(answer);. If the method is right a new class is instantiated which does the search and then gives back an object which XStream converts into XML for me which I then again send back to the client with println wrapped into my xml overhead that is again hard coded String answer ="<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n".
Obviously this works and also very efficiently but this is far from elegant. I have looked into Apache CXF very briefly and also into things like RESTlet or JBOSS RestEasy, the latter two I found to much for my demands. Every tutorial I find on CXF always includes Spring and Maven which then overwhelms me a little. Do you have suggestions how I should turn my dirty hack into a pretty application?
I think using a JAX-RS framework (as CXF and Resteasy, but also Jersey) is the way to go in your case. Regarding serializing to XML, maybe have a look at JAXB (as included in Jersey too for instance). It should help automatically serialize any entity structure.
Regarding the complexity of such application: it should always depend on what infrastructure you use. If its just a simple Java EE server, its possibly best to use the implementation of that vendor (Jersey for Glassfish, Resteasy for JBoss). Otherwise, just use the build system you are familiar and comfortable with. You can easily replace the Maven dependencies with Ant and Ivy for example.
I can recommend CXF; I found it extremely easy to get going with its tutorial, especially once I bit the bullet and used Maven to manage dependencies (though really it's orthogonal to everything that CXF and Spring do).
But in order to make use of CXF I really recommend using Spring. You don't have to use all of Spring; the simple tutorial on the CXF site gives you enough to get going. This is especially true if you've already got the code that actually implements things already done, and separated out from the code to parse incoming URLS, render responses as XML, etc.; that's the part that CXF (and typically JAXB) will handle for you.
To help, here's a mega-simple example (with imports omitted for brevity).
I know it looks complicated, but almost all the second half consists of stuff you write once and then don't really touch again; as you build out to tackle your real code, you can do a lot without paying must attention to the framework code at all.
First, the interface definitions (including the XML type model):
public interface Foo {
#Path("/") #GET #Produces("application/xml");
FooDesc getDescription(#Context UriInfo ui);
#Path("{id}")
FooItem getFoo(#PathParam("id") String id);
}
#Path("/")
public interface FooItem {
#GET #Produces("application/xml")
FooItemContents getContents();
#PUT #Consumes("application/xml")
void setContents(FooItemContents desc);
#DELETE
Response delete();
}
// These classes are purely structural holders; no behavior.
#XmlRootElement #XmlType
public class FooDesc {
#XmlElement
public List<URI> foo;
}
#XmlRootElement #XmlType
public class FooItemContents {
#XmlElement
String bar;
}
Next, the implementation class:
public class FooImpl implements Foo {
public FooDesc getDescription(UriInfo ui) {
FooDesc desc = new FooDesc();
desc.foo = new ArrayList<URI>();
UriBuilder ub = ui.getAbsolutePathBuilder().path("{id}");
for (String id : realGetIdList()) // Hook to your code here!
desc.foo.add(ub.build(id));
return desc;
}
public FooItem getFoo(String id) {
final RealFoo r = realGetById(id); // Hook to your code here!
return new FooItem() {
public FooItemContents getContents() {
FooItemContents contents = new FooItemContents();
contents.bar = r.getBar(); // Hook to your code here!
return contents;
}
public void setContents(FooItemContents desc) {
r.setBar(desc.bar); // Hook to your code here!
}
public Response delete() {
r.close(); // Hook to your code here!
return Response.noContent().build(); // Return a simple HTTP 204
}
};
}
}
Now, plumbing it together at the Spring level with a WEB-INF/beans.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">
<!-- Instantiate and connect the service framework -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<jaxrs:server id="customerService" address="/">
<jaxrs:serviceBeans>
<ref bean="fooBean" />
</jaxrs:serviceBeans>
</jaxrs:server>
<!-- Instantiate your implementation class -->
<bean id="fooBean" class="your.package.FooImpl" />
</beans>
Now, to hook it all up as a webapp, a web.xml:
<web-app>
<!-- Magic to make Spring work and build the rest for us -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Make CXF implement the servlet for us -->
<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>
<!-- You *must* use this servlet mapping or Bad Things Happen. -->
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Now build a WAR including all the libs listed on the CXF site, putting the bits in the right places (you don't need Maven for this, but in the end it makes it easier) and deploy. That ought to work (i.e., you've now got enough to be dangerous!)

Categories

Resources