Tuesday, July 10, 2012

JAX-RS server: beyond hello world

Warning: This article is for the legacy JAX-RS 1.x API. Consider using the more up to date JAX-RS 2.x instead. This is not a recommended article to set that up. Take a look at the more recent article Maven JavaEE7 app in Netbeans 8 that demonstrates getting a JAX-RS 2.x based service going.


In my previous article on JAX-RS I dealt with creating a client that consumes a RESTful webservice. In this article I'm going to turn it around and use JAX-RS to expose a webservice. In a way that I myself can easily consume it from a Ruby on Rails client (proper test: make it cross technology boundaries). This service is going to expose some data as JSON, although you could take the next step and expose both JSON and XML.

In the client article I posed a generic interface which allowed a client to search for projects by name. The service would have to produce the data in this format:
[{  
      "active":true,  
      "id":1,  
      "name":"Razorblade supershave mark 2",  
      "company":{  
                 "id":1,  
                 "name":"Razorblade Ent.",  
                 "employees":[{"id":1,"name":"harry"},{"id":2,"name":"sally"}]  
                }  
     },  
     {  
       "active":true,  
       "id":9,  
       "name":"fortune cookie dispenser",  
       "company":{  
                  "id":2,  
                  "name":"Unfortunate Inc.",  
                  "employees":[{"id":5,"name":"gordon f."},{"id":7,"name":"sam stone"}]  
                 }  
     },  
     { "active":true,  
       "id":15,  
       "name":"fortune cookie baking device",  
       "company":{  
                  "id":2,  
                  "name":"Unfortunate Inc.",  
                  "employees":[{"id":5,"name":"gordon f."},{"id":7,"name":"sam stone"}]  
                 }  
     }]  

In this article I'll create a service that produces this. Since its a webservice we'll have no choice but to expose it through a webserver; I'll assume you are going to be deploying this on Tomcat 7 but if you are using a JEE6 capable application server (which has JAX-RS built in) I also provide the slightly alternative setup steps. Also as I explain in the client article there are many JAX-RS implementations, for this article too I'll be using the reference implementation called Jersey for Tomcat; if you use a JEE container it will already provide its own implementation (example: JBoss 7+ comes with RestEasy). If you have no experience with Java web development or deploying an application to something like Tomcat this article is a little out of your league at this point I'm afraid. My JSF 2 and JPA 2 on Tomcat 7 article goes into great detail and might provide you the prerequisite knowledge to be able to do what his article will assume you know how to do. At the very minimum read up about the HTTP protocol as that is basically what makes and drives RESTful webservices.

Remarkably exposing a service is less involved than consuming one; in this article I'll be dealing with:
  • Maven setup / dependencies (separate steps for Tomcat and JEE containers such as JBoss)
  • how to configure Jersey/JAX-RS
  • how to expose a service to GET data
  • how to expose a service to submit (POST) data


As it turns out there are quite a few variations on how to actually expose a service; I'm picking one that to me is the more logical, natural and above all readable way to do it that should cover most needs. In the future I'll add steps to the article to setup some proper security through Tomcat 7.

Tomcat 7 setup

This article will show two ways to setup: for Tomcat and for a JEE server; the latter is far less work. I'll also add JBoss 7.1 specific information, me being a JBoss enthusiast.

Maven
To expose a Jersey based JAX-RS service we need the following Maven dependencies which are all in Maven central. Note that if you have followed the client article also, there is going to be some duplication here. Note also that this is a slightly older article so it targets Jersey 1.x, not the newer Jersey 2.x.
  <dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-server</artifactId>
    <version>1.18.1</version>
  </dependency>
  <dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>jsr311-api</artifactId>
    <version>1.1.1</version>
  </dependency>
  <dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-servlet</artifactId>
    <version>1.18.1</version>
  </dependency>  
  <dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-json</artifactId>
    <version>1.18.1</version>
  </dependency>

That jersey-servlet dependency seems to be a bit of a secret! Apparently it used to be part of the jersey-server dependency, but has been separated at one point in time.

If you deploy to a JEE6+ compliant application server you would mark the above dependencies as provided, since the application server itself does in fact provide a JAX-RS implementation. You will want to check which one because you will want to make the maven dependencies match up.

If you're not using Maven, the following dependencies are actually going to end up in the webapp (most of them transitive dependencies):
  • jersey-core 1.18.1
  • asm 3.1
  • jersey-server 1.18.1
  • jersey-servlet 1.18.1
  • jersey-json 1.18.1
  • jettison 1.1
  • activation 1.1
  • stax-api 1.0.2
  • jaxb-api 2.2.2
  • jaxb-impl 2.2.3-1
  • jackson-core-asl 1.9.2
  • jackson-jaxrs 1.9.2
  • jackson-mapper 1.9.2
  • jackson-xc 1.9.2
So compared to the jersey-client article, the asm, jersey-server and jersey-servlet dependencies have been added.

Web
The client is install, fire, go and can even work on the commandline. The webservice part of it requires additional setup however and to my amazement there are multiple ways to actually do that. I present here the one I found to most understandable version. Jersey exposes its services as a (clever) servlet, which we need to plug in the web.xml as follows:
  <servlet>
    <servlet-name>Jersey</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>jaxrstest.server.service;org.codehaus.jackson.jaxrs</param-value>
    </init-param>
    <init-param>
      <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
      <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    
  </servlet>
  
  <servlet-mapping>
    <servlet-name>Jersey</servlet-name>
    <url-pattern>/REST/*</url-pattern>
  </servlet-mapping>  

Note the servlet-class; without the jersey-servlet dependency that class would be missing. This piece of configuration data contains two application specific elements you will want to know about.

First of all there is the config.property.packages init-param. This property tells the Jersey servlet in which packages we'll be putting our restful resources (or RESTful root resources as the JEE 6 tutorial likes to call it). You can put more packages than the one by separating them with a semi-colon like this:
<param-value>jaxrstest.server.service;package.some.other</param-value>

Be sure to put a package (or packages) of your own choosing here. In the above example I've also added a package that is part of the Jackson JSON mapping library; by doing so Jersey will find the Jackson mapper classes and use them. The Jackson POJO mapping features are better (read: produces more compatible JSON layouts) than what you get from Jersey itself.

Secondly there is the url-pattern that the servlet is mapped too; you should pick a pattern that does not clash with the rest of the web application. The pattern you put here is going to be used by Jersey to build up the actual URL under which services are going to be exposed.

Also note the POJOMappingFeature init parameter - this basically tells Jersey that we'll be exposing data as JSON (and thus the jersey-json dependency is needed). If you leave this parameter out you'll find that things still work... but generally not as expected.

JEE setup


Maven
Since JEE6 and up already provides JAX-RS out of the box, we don't need much; only the API to compile against.
  <dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>jsr311-api</artifactId>
    <version>1.1.1</version>
    <scope>provided</scope>
  </dependency>

Web
No configuration is needed. We only need to add a class to our application which indicates we want to expose a JAX-RS service, and where:
@ApplicationPath("/REST")
public class JaxrsApplication extends Application {
}

This is enough for a compliant JEE6/JEE7 container to make the magic happen. JAX-RS services will be exposed under the url WEB_URL/REST, so for example http://localhost:8080/jaxrstest/REST. Put whatever application path you like.

JBoss 7.x setup

If like me you're not rich, you use the community version of JBoss and you'll be stuck with outdated libraries. Luckily you can easily upgrade the built-in RestEasy module - and you should do that because the default version has some issues, such as not being able to use adapters. To upgrade RestEasy, go to the download page to get the latest version. Then:
  • unpack the file resteasy-jboss-modules-VERSION.Final.zip from it
  • unpack that resteasy-jboss-modules-VERSION.Final.zip file into your JBoss 7.1 modules subdirectory
  • If you do it correctly, you'll be asked to overwrite files: yes to all
Done! Note: If you are using JBoss 7.2 / EAP 6.1, you need to copy the unpacked files to the modules/system/layers/base subdirectory. See my JBoss 7 getting started article to learn how to build JBoss 7.2 yourself.

Creating a model

Since I'm aiming to produce exactly the same JSON data that the client article is set to consume, I'll re-use the model classes verbatim. Again, getters and setters left out to save space.
@XmlRootElement
public class Project {

  private boolean active;
  private long id;
  private String name;

  private Company company;

  public Project(){
  }
  
  public Project(long id, String name, boolean active, long companyId, String companyName, Employee[] emps){
    this.id = id;
    this.name = name;
    this.active = active;
    this.company = new Company(companyId, companyName, emps);
  }

  // getters and setters here
}


@XmlRootElement
public class Company {
  private long id;
  private String name;
  private Employee[] employees;

  public Company(){
  }

  public Company(long id, String name, Employee[] emps){
    this.id = id;
    this.name = name;
    this.employees = emps;
  }

  // getters and setters here
}

@XmlRootElement
public Employee {
  private long id;
  private String name;

  public Employee(){
  }

  public Employee(long id, String name){
    this.id = id;
    this.name = name;
  }
  // getters and setters here
}

Only a few things to note:
  • the XmlRootElement annotation; that is a JAXB annotation that basically marks the pojo class as a complex type to be used in mapping the data; either JSON or XML.
  • the properties of the beans match the labels as you'll find them in the JSON data
  • array of employees in the JSON data, array of employees in the Java class. You can however replace the array type with a List<Employee> type if you so please, it magically works.
Convention over configuration: you're best off just using the exact same property field names as you find them in the JSON data, or else you'll have no choice but to add extra annotations to provide mapping names.

Creating a service class

Now that we have our model, its time to create a class which is going to serve as the "project" service; the service through which we are going to expose the RESTful functions that operate on our projects; the project search to be more precise. But we'll likely want to define more functions that operate on projects so from the many different ways the API can be used to expose a service, here is again the one I find the most logical. Put this class in the package you configured in that servlet init param (in my case the jaxrstest.server.service package)!
@Path("/project")
public class ProjectResource {
  // things and stuff here
}

Lets take it one step at a time. This minor piece of code maps our service to the /project path. At this point the question arises: but what is going to be the full HTTP URL? Well assuming that:
  • We're running the webserver on the localhost on port 8080
  • Our application's web context is 'jaxrstest'
The full URL of our project service is going to be:

http://localhost:8080/jaxrstest/REST/project/

If we define that in tokens:

HOST/CONTEXT/JERSEY-URL-PATTERN/PATH

But we're not there yet - we have yet to expose our project search function. What I want to do is match the pattern as I documented it in the client article, which was along the lines of:

/REST/project/search/:NAME

Where :NAME is to be replaced with a (partial) project name to search for. Given that requirement we can lay down the service declaration as follows:
  @GET @Path("/search/{name}")
  @Produces("application/json")  
  public Project[] search(@PathParam("name") String name) {
   ...
  }

Whoa, Four annotations for one service call. Of course I picked a rather involved example, so let's examine it.

First of all the @GET annotation should not leave anything up to the imagination; we expose this service as a GET request, which should be used to fetch information. The @Path annotation binds our search function to a very specific path under /project: namely /search followed by something that should be mapped to a variable parameter; the {name} part is what tells JAX-RS that it is in fact variable. The @PathParam annotation is then used to in fact bind the variable in the URL to the parameter of the method.

And then there is the @Produces annotation which again does not leave anything up to the imagination; it defines the content-type that the method will be producing. In this case we'll be exposing the data as JSON.

Lets define an actual body for the service call. Generally at this point you would of course fire off a query to a database and actually return a filtered list of results through for example JPA; in our case lets keep it simple and just create some objects:
    Project[] ret = new Project[3];
    Employee[] emps1 = {new Employee(1, "Donald"), new Employee(2,"Hewey")};
    Employee[] emps2 = {new Employee(3, "Scrooge"), new Employee(4,"321-123")};

    ret[0] = new Project(1, name + " 1", true, 1, "Super duper company", emps1);
    ret[1] = new Project(2, name + " 2", true, 1, "Super duper company", emps1);
    ret[2] = new Project(3, name + " 3", true, 2, "Super duper company 2", emps2);
    
    return ret;
This will just echo back whatever search query you pass in the project names. A useful way to mock some test data and still see that it does "something".

At this point you're ready to deploy your webapp, fire up Tomcat and invoke the URL: http://localhost:8080/jaxrstest/REST/project/search/test. During startup you can already spy some interesting logs that indicate that things are going well for us.
9-jul-2012 17:05:13 com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Scanning for root resource and provider classes in the packages:
  jaxrstest.server.service
9-jul-2012 17:05:13 com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
  class jaxrstest.server.service.ProjectResource

Be sure to alter that URL to your personal environment using the description I gave you earlier. If everything is correct, you'll be greeted with the JSON data as posted at the top of this article, only as one long line. If not: did you by any chance forget to set that POJOMappingFeature init parameter?

JResponse - our list friend

To receive an array and use it is easy; to produce an array (in a statically typed language like Java) is cumbersome. It would be nicer if we could just return a generic list of projects in stead of an array. This is one of those things that has vastly improved during the time it took for JAX-RS to mature into the API it is today. To make our life incredibly easy we can use the JResponse class (as apposed to the old Response class) which preserves the generic type information for us.

Translating the code is as simple as this:
  @GET @Path("/search/{name}")
  @Produces("application/json")  
  public JResponse<List<Project>> search(@PathParam("name") String name) {
    
    // normally this would do a database search
    List<Project> ret = new ArrayList<Project>();

    Employee[] emps1 = {new Employee(1, "Donald"), new Employee(2,"Hewey")};
    Employee[] emps2 = {new Employee(3, "Scrooge"), new Employee(4,"321-123")};

    ret.add(new Project(1, name + " 1", true, 1, "Super duper company", emps1));
    ret.add(new Project(2, name + " 2", true, 1, "Super duper company", emps1));
    ret.add(new Project(3, name + " 3", true, 2, "Super duper company 2", emps2));
    
    return JResponse.ok(ret).build();
  }

This should produce exactly the same JSON output as the array example. A general use case would be: fetch list of Project instances from the database using JPA, expose it through JAX-RS service. Easy and only a few lines of code involved.

What about a client?

The client article should be able to help you there, but I'm a firm believer of crossing technology boundaries to really put something to the test. If you're familiar with Ruby on Rails, I created this very simplistic (rails 3) client class using the HTTparty gem (don't forget to add it to your gems file) to be able to consume the project search service.
require 'rubygems'
require 'httparty'

class ProjectResource
  include HTTParty
  base_uri 'http://localhost:8080/jaxrstest/REST/project'
  format :plain
  headers 'Accept' => 'application/json'

  
  def self.search_projects(name)
    
    #this performs a GET request to the project search, returning the JSON data as plain text. That way we can easily use ActiveSupport to deserialize the data into model class objects.
    data = get("/search/#{name}")
    projects = ActiveSupport::JSON.decode(data)
    return projects
  end  
end
Where 'projects' is then an array of Project objects, which are simple Rails model classes that match the Java ones above. The fact that they're ActiveRecord model classes is what makes it possible for ActiveSupport to magically translate the JSON data into the appropriate model types without needing any hint at all. As an example, here is a stripped version of the Project model class:
class Project < ActiveRecord::Base
  
  attr_accessible :id, :name, :company, :company_id, :active 
  belongs_to  :company

  ...
end
How to actually setup a Rails application and use the above code is way beyond this article, but I'd just want to give an idea of how such a thing translates to another language/platform. Of course Rails being Rails there are likely dozens of alternatives to be able to consume a web service; I picked this route because it is basic and lightweight.

You don't have to write any client to test this out, you could also install the excellent HttpRequester Firefox plugin, which allows you to fire very specific HTTP requests to an url. That way you can fire a GET request with the accept header set to application/json. If you're not a Firefox user I'm sure there is a similar plugin for your browser of choice, or you could create a simple java command line thing using the Apache HttpClient API.

Producing multiple content-types

So far we've locked our service down to JSON, but what if some clients must remain in the stone age and demand XML? Easy enough, if you look in the Jersey reference manual you'll quickly spot that the content-type for XML is "application/xml". But what if we wanted to support both json AND xml output? It would really stink if we had to duplicate each service call. Luckily you don't have to, we can augment our service call as follows:
  @GET @Path("/search/{name}")
  @Produces({"application/json", "application/xml"})  
  public Project[] search(@PathParam("name") String name) {

This one method can now service both JSON and XML data. But which one is going to be the default? Jersey at least, it seems, favors XML. If you navigate to the service call in a browser you'll now get XML output as a result. Our Ruby client as defined above however, provides an accept HTTP header of 'application/json'. That accept header makes it so a call to the same URL will in fact produce JSON data as a result!

Reversing the flow: pushing data

Until now the article has been dealing with one way to provide data to a client. Now lets examine the other way around: how does a client push data to our service? Since this is fully HTTP based, the answer lies in the request method. Until now we've been dealing with GET requests, but there is also HTTP POST, PUT and DELETE. Delete won't need any explanation, but POST and PUT are subject to a certain convention you'll find common in RESTful services - POST is used to add something, PUT is used to update something.

If you've ever built any kind of web application you've likely built a web form which is submitted to the server through a POST request; that same web form can likely be used to either add or update a record. Now there is nothing stopping you from creating a RESTful service which reacts to a POST request and both inserts and updates data; there is nothing stopping you from using PUT in stead. But when it comes to providing a service, you want to lay down a very precise and clearly defined interface or contract - it is not a bad idea at all to actually separate insertion and modification into two separate methods and following the convention for one very simple reason: the ability to make safe assumptions which is the biggest step towards self-documenting code.

Okay, lets create a method to add a company to our system. Since I've been pushing JSON until now, let's in fact make our client offer the data in JSON format.
  @POST @Path("/company/add")
  public void createCompany(Company comp) {
    // normally you'd do something like use JPA to persist the company to the database.
    System.out.println("CREATING COMPANY WITH NAME: " + comp.getName());
  }

This method is actually more flexible than you'd think as the client can transmit the data both in JSON and XML format and both will work; if you'd want to lock the service down to a specific type then you could add a @Consumes annotation to the code.

Now this will certainly work, but for our client it lacks critical feedback. The company is now stored, but how does one uniquely identify this company to for example be able to fetch it later or bind an employee to it? Much like a GET method, we can return something from our POST method. You could echo back the entire Company object but for example with an ID property added, but a more clever solution is to return some sort of result object which can also communicate success or failure.
@XmlRootElement
public class IdResult {

  private long id;
  private boolean ok;
  private String msg;
  
  public IdResult(){
  }
  
  public IdResult(long id){
    this.id = id;
    this.ok = true;
  }
  
  public IdResult(String msg){
    this.ok = false;
    this.id = -1;
    this.msg = msg;
  }

  // getters and setters here
}

With that simple object we can now give proper feedback to the client:
  @POST @Path("/company/add")
  @Produces("application/json")
  public IdResult createCompany(Company comp) {
    // normally you'd do something like use JPA to persist the company to the database. We need an ID to return, so just generate something.
    System.out.println("CREATING COMPANY WITH NAME: " + comp.getName());
    return new IdResult(System.currentTimeMillis());
  }

Time to try that out. With the path set the full url would be http://localhost:8080/jaxrstest/REST/project/company/add; if you put that in a browser you should get a "method not allowed" error message (since we're sending a GET request to something which expects a POST request). To do a proper test you could create a simple client method or by using that HttpRequester firefox plugin I mentioned earlier. Use the following POST request body, which is our company in JSON format sans employees:
{"id":0,"name":"My super duper company"}

To be able to actually send this POST request you have to make sure that you are setting the content-type of the request to application/json! As an accept header you have to set application/json as well since that is what the service call @Produces. the output of such a request would now be like this:
{"id":"1341924925250","ok":"true"}

Lets add another Ruby on Rails example here just to keep the article consequent. Since we have to set an explicit content-type, its best to put update code in a separate resource class specifically aimed at doing update actions. To get the result you could define another IdResult model class and deserialize the JSON response like in the previous client example, but to be original lets skip that and simply let HTTparty do some work for us. It can actually deal with JSON response data and return the individual fields as a hash.
class ProjectUpdateResource
  include HTTParty
  base_uri 'http://localhost:8080/jaxrstest/REST/project'
  format :json
  headers 'Accept' => 'application/json'
  headers 'Content-Type' => 'application/json'

  def self.add_company(name)
    
    #construct a company model object and serialize that to JSON (limiting to specific fields)
    company = Company.new(:id => 0, :name => name)
    data = company.to_json(:only => [:id, :name])

    #post the JSON data as the body of the request and get the response
    res = post("/company/add", :body => data)
    
    #get the individual fields from the response
    ok = res['ok'] == 'true'
    id = res['id']
    msg = res['msg']
    
    #should do some error checking here and throw an exception where necessary.
    puts "----------------------------- OK: #{ok}, ID: #{id}, MSG: #{msg}"
    return id
  end
end
Again a very basic and lightweight way to do it and there are probably many alternatives to do it in a more RESTful way. It doesn't matter, what matters is that we get the data to our service in a way that is expected and we can read out the response in an easy way. At this point you may run into many HTTP errors, all of them caused by the content-type and the accept headers of the client not matching with what the services produces and consumes. Its a question of fiddling until something works.

Working with dates in your models

Dates are a difficult thing, because they come in so many shapes, sizes and orders. Therefore marshalling/unmarshalling a Date is not something that can just happen; likely you will want to impose a little control over this. The easiest way of all is to just use dates in textual format, but if someone sends you an invalid date you can't let the API deal with that, you have to parse and validate yourself. My choice to attack this problem is to use a JAXB adapter.

Watch out though!: older versions of Jackson (the JSON mapping library often used in JAX-RS implementations) did not handle the adapters at all. Make sure you are using an up to date version of Jackson / your JAX-RS implementation. If you're a JBoss 7.1 user, check the start of this article for upgrade steps.

First of all we need our adapter, which is as simple as this:
public class DateAdapter extends XmlAdapter<String, Date> {

    @Override
    public Date unmarshal(String v) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
        return sdf.parse(v);
    }

    @Override
    public String marshal(Date v) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
        return sdf.format(v);
    }
}

This class will work with dates in Dutch format, adapt the pattern to what you need. Now we need to tell our JAX-RS implementation that we want to use this adapter to marshall/unmarshall dates. Lets take a more extreme example and you are using JPA entities as your model classes (just to show that you can), it could turn into something like this:
@Entity // JPA
@Table(name="person") // JPA
@XmlRootElement // JAX-RS
public class Person {

  @Id // JPA
  @GeneratedValue // JPA
  private int id;

  private String name;

  @Temporal(TemporalType.DATE) // JPA
  @XmlJavaTypeAdapter(DateAdapter.class) // JAX-RS
  private Date birthdate;

  // getters and setters
}
Its a bit annoying; I have not found a way to set an adapter globally yet but I'm sure there is a way to achieve this result in some way...

Dealing with CORS

CORS, or "Cross Origin Resource Sharing" is a protection mechanism built into modern browsers that basically prohibits websites from making ajax requests to just any other server; by default javascript is only allowed to make requests to the host (and port) from which the resource was serviced. That's a bit limited especially when you want to make ajax requests to a RESTful service which will almost never be the host which services the web application.

To be able to get around that you need to setup CORS on your server and it is surprisingly simple on Tomcat. Simply make sure that your Tomcat version is up to date (IE. if you run Tomcat 7, make sure you are running the latest one) and then simply configure this filter in your web.xml:
  
    CorsFilter
    org.apache.catalina.filters.CorsFilter
  
  
    CorsFilter
    /REST/*
  
Be sure to match the URL pattern to whatever you URL pattern you have mapped your RESTful service too. If you are not using Tomcat, you'll have to dig into your server's manual to figure out how to properly setup CORS, or possibly manually add the headers to the response with your own filter. For further reading when using Tomcat: the documentation page.

Is it really a good idea to mix JPA and JAX-RS?

Int this article I've shown a few examples where JPA entities are exposed in the webservice calls. That is certainly very useful as it prevents duplicate code, but is it really a good idea?

The answer is unfortunately: absolutely no. There are a myriad of problems attached to exposing the database layer directly in the webservice interface, as that is what you're doing. Lets list a few:
  • Any field you add, change or remove in an entity directly affects and changes the webservice contract!
  • You need to actively exclude properties from being serialized; all are exposed to the outside world by default.
  • JAX-RS serializes entities, so all their lazy properties will be hit

What it boils down to is that you have absolutely no freedom and you severely lock down your capabilities to make any changes to the data model of the application. Basically if an entity is exposed to the outside world, it better stay exactly the way it is. If you change it, any client depending on its old state will likely break unless it is modified to match the new serialized state.

You do not want that head ache. You should use POJO value objects which expose the minimum amount of information. If you then change an entity you don't necessarily have to change the VOs that match it - you only change how the data in the VOs is fetched from the entity. Separation of concerns - it won't lead to less code, but it will prevent burn out.

Securing our web service

One day I'll provide exact steps on how to configure Tomcat 7 to do such things as utilizing HTTPS and how to setup HTTP digest authentication. But that will require further experimentation on my part as well.

Time to call it a day

Not an entire manual on setting up JAX-RS services but not hello world either. I hope with this article I've given you the leg up that makes you productive with JAX-RS as quickly as possible. For further reading I suggest you get a good book on the API, but you may also want to check out the JEE 6 tutorial chapter on JAX-RS. For Jersey specific documentation there is the Jersey reference manual. And I'd like to repeat once more that if you were looking for documentation on creating a client using JAX-RS, I have a whole article dedicated to that topic.

Good luck with your project!

3 comments:

  1. - split setup into Tomcat/JEE setup
    - added JBoss 7.1 (community) upgrade advice
    - added Date adapter paragraph

    ReplyDelete
  2. Updated versions to latest (in Jersey 1.x branch anyway); added addition of Jackson POJO mapper configuration in Tomcat/Jersey setup section to produce better/more standardized formatted JSON data. Added section on CORS.

    ReplyDelete
  3. Added a section that discusses using JPA entities as JAX-RS mapped objects (and why not)

    ReplyDelete