Using SerfJ is the easiest way of developing Java REST web applications. It helps you to develop your application over an elegant MVC arquitecture, giving more importance to convention than configuration, so for example, you will not have to have configuration files or annotations in order to specify which view serves a controller's method. However, SerfJ is very flexible library, so if you want to jump over those conventions, you can configure the behaviour of your applications as you like.
The framework tries to meet JSR 311 specification, but it doesn't follow every point of that, because the purpose is to have a very intuitive library, and some some aspects of the specification are out of the scope of SerfJ.
SerfJ is opensource and is released under the Apache License, Version 2.0.
Last stable release 0.4.1 was launched on 14th of September, 2012.
You can read what's going on at our blog: http://serfj.wordpress.com. It isn't a blog with too much activity, but there you can follow what will be in next version, or when a version is launched, etc.
If you want to know how to use SerfJ, you must read this little guide. First of all, you have to set up your web.xml file to map your REST-like URLs against net.sf.serfj.RestServlet servlet. In the next example, we map two resources banks and accounts.
RestServlet net.sf.serfj.RestServlet 5 RestServlet /banks/* RestServlet /accounts/*
The next step is to put within the classpath a config file /config/serfj.properties with at least one line:
# Main package where looking for classes (controllers, serializers) main.package=net.sf.serfj.test
Now, our application is able to attend requests like:
Notice that some-action is any method that developers want to write on their controllers. But for now, nobody is attending those requests, so we have to write some classes in order to get it working right. We need a controller to attend /banks/* requests, and another to attend /accounts/* requests.
Bank's controller:
public class Bank extends RestController { @GET public void index() { // By default, this action redirects to index.jsp (or index.html or index.htm) } @GET public void show() { // Gets ID from URL /banks/1 String id = this.getId(); // By default, this action redirects to show.jsp (or show.html or show.htm) } @GET public void newResource() { // By default, this action redirects to new.jsp (or new.html or new.htm) } @GET public void edit() { // By default, this action redirects to edit.jsp (or edit.html or edit.htm) } @POST public void create() { // By default, this action redirects to create.jsp (or create.html or create.htm) } @PUT public void update() { // Gets bank's ID String id = this.getId("bank"); // ... or another way to get the main Id String bankId = this.getId(); Bank bank = // Code that gets the bank object // Gets new name for the bank String name = this.getStringParam("name"); // Updating the bank // ... Code that updates the bank's information // By default, this action redirects to update.jsp (or update.html or update.htm) } @DELETE public void delete() { // By default, this action redirects to delete.jsp (or delete.html or delete.htm) } @GET public void someAction() { // By default, this action redirects to someAction.jsp (or someAction.html or someAction.htm) } }
Account's controller:
public class Account extends RestController { @GET public void index() { // By default, this action redirects to index.jsp (or index.html or index.htm) } @GET public void show() { // Gets account's ID from URL /banks/1/accounts/2 String accountId = this.getId("account"); // Gets account's ID from URL /banks/1/accounts/2 String theSameAccountId = this.getId(); // Gets bank's ID from URL /banks/1/accounts/2 String bankId = this.getId("bank"); // Gets the account Account account = // Code that gets the account 2 from bank 1 // Put account into the request so the page will be able to use it this.addObject2Request("account", account); // By default, this action redirects to show.jsp (or show.html or show.htm) } @GET public void newResource() { // By default, this action redirects to new.jsp (or new.html or new.htm) } @GET public void edit() { // By default, this action redirects to edit.jsp (or edit.html or edit.htm) } @POST public void create() { // By default, this action redirects to create.jsp (or create.html or create.htm) // But I want to render another page!... easy this.renderPage("mypage.jsp"); } @PUT public void update() { // By default, this action redirects to update.jsp (or update.html or update.htm) // But I want to render another page... from another controller!... easy this.renderPage("bank", "another_page.jsp"); } @DELETE public void delete() { // By default, this action redirects to delete.jsp (or delete.html or delete.htm) // Well, if something happens, I want to redirect to mypage.jsp if (somethingHappens) { this.renderPage("mypage.jsp"); } else { // Default page this.renderPage(); } } /** * If this method is called as /accounts/1/accountBalance.xml, then the balance object will * be serialized as an XML, whereas if it's called as /accounts/1/accountBalance.json, the * object will be serialized as a JSON object. */ @POST public Balance accountBalance() { // Gets account's Id String id = this.getId("account"); // Calculate balance BalanceManager manager = new BalanceManager(); Balance balance = manager.getBalance(id); this.serialize(balance); } }
I think you're wondering where do you have to put those classes. Having in mind that we set our main.package (remember serfj.properties) to net.sf.serfj.test, you have three different possibilities, and you can combine them as you want for every controller:
If you don't want, you don't have to configure anything, SerfJ will find your controllers if they follow those rules. It's a good thing that every controller follows the same rule, so you can set up the framework to find them in the same way (it's an option within configuration file). Also you can put a prefix or suffix to your controllers, but this is also a configuration feature. For now I don't have the all documentation in the site, but it'll be very soon.
By default, the framework will answer your request presenting a web page. First, it will look for a .jsp page, then for a .html and in the end for a .htm page. If it doesn't find any of those kind of pages, a exception will be throw. But... where must be the pages?. Well, by convention SerfJ will look for them at views directory. So if we have to have this directory structure:
Within those directories we have to have an index.jsp, new.jsp (for newResource method), edit.html, etc.
Following JSR 311 specification, SerfJ provides some annotations:
But there is a fifth annotation DoNotRenderPage.
With this runtime annotation, a developer is able to mark a controller's method in order to not
render a page after its execution. It's only valid for methods that return void, because methods
that return an object will always serialize that object in the response.
Methods that don't return anything have to be annotated too, so the framework is able to know
what to do after the execution. If it's annotated with DoNotRenderPage, the framework
will return a HTTP status code 204 (No content), if it isn't annotated, the framework will search
for a page to render.
Our application is now able to respond to our requests with some beautiful pages, but what if we want some request to answer with a serialized object?, or with a serialized object as JSON, XML, PDF or so?... No problem!, first of all you have to declare your method with a return type of a serializable class.
public class Account extends RestController { @POST public MyObject some-action() { // Some clever code :) MyObject myObject = // clever method that returns MyObject return myObject; } }
Then you are able to call this method in different ways:
Magic?... No, SerfJ provides some serializers that help you to have this stuff.
There are serializers for XML, JSON, Base 64, Files, but you can have your own
serializers for those formats, even you can implement serializers for other formats like PDF, CSV, or
whatever you want. You only need to write an implementation for
net.sf.serfj.serializers.ObjectSerializer
interface for object serialization or extending the class
net.sf.serfj.serializers.FileSerializer:
for serving files.
Please read the Reference for more information about serialization and the different possibilities.
But, where do you have to put your serializers?. Well, it follows the controller's logic:
When you are sure about your application architecture, you can configure SerfJ so it will look for your serializers in the right path, but for now, it will search for them using several algorithms.
Yes, I know it, in a browser you can only do GET or POST requests, but there is a trick to convince SerfJ that requests meet HTTP protocol. You must make a POST request, sending the parameter http_method whose value will be the HTTP method the request needs (GET, POST, PUT or DELETE). So if you want to call Bank.delete() to delete Bank with ID = 1, you must make a POST request like this:
POST /banks/1?http_method=DELETE
Of course, and SerfJ provides a client!: net.sf.serfj.client.Client. With this client you can do REST request to any method of SerfJ controllers you implement. If controller's methods return values, the client will manage them and will return you the correct object. In order to be close to the JSR 311 specification, if the controller's method fails, a WebServiceException will be thrown. Also, if the method doesn't return values (a void method), an HTTP 204 code will be answered.
No problem, SerfJ is within Maven Central repository, here you have the POM you need to add the dependencies:
net.sf.serfj ;serfj 0.4.1
No problem too :), these are the dependencies:
And for testing:
However you can download a .zip file with all dependencies needed at runtime from our Download section.
Copyright © 2010-2012 Eduardo Yáñez Parareda, Licensed under the Apache License, Version 2.0. Apache and the Apache feather logo are trademarks of The Apache Software Foundation.