Reference Manual (updated for SerfJ 0.4.0)

Table of Contents

1. Architecture

SerfJ provides an MVC architecture, but it doesn't do anything with models, its main characters are controllers, those controllers are managed by REST requests.

Controllers represent application's resources, so the way to send messages to those resources is through REST-like requests. When a request is attended by the main servlet (net.sf.serfj.RestServlet), a resource (controller) is searched and is asked if it's able to answer the request. In that case, an action is executed (a controller's method), and a response is served to the client. The response can be a page, a serialized object, or an HTTP status code 204 that means no content.

1.1 REST-like URLs

Since REST requests control the flow of SerfJ's applications, it's very important to read this section. However, concepts explained below are simple, so everything will be easy to understand.

The HTTP methods supported by SerfJ are:

For example, if you want to present a page that has a form for updating or creating resources, you need to send a GET request, not a PUT request. But the submit button (whose intention is to update some information) will have to send a PUT request.

The patterns for REST requests are:

Notice that resource's name must be plural, and that identifiers must start with a number. But SerfJ is able to parse URLs with nested resources:

If you finish your URL with an extension, then the result won't be a page but a serialized object. Thus, depending on the extension used, you can receive an object serialized as JSON, XML or so:

1.2 Standard URLs

Depending on the HTTP's method used the same URL will call different controller's methods:

HTTP MethodURLController's methodViewMeaning
GET/accountsindex()indexShow every resource
POST/accountscreate()createCreate a new resource
GET/accounts/1show()showShow a resource with ID 1
PUT/accounts/1update()updateUpdate a resource with ID 1
DELETE/accounts/1delete()deleteDelete a resource with ID 1
GET/accounts/1/newResourcenewResource()newShow a form to create a new resource
GET/accounts/1/editedit()editShow a form to update a resource with ID 1

2. Controllers

Controllers are the main character in SerfJ, REST requests are dispatched to them, and they answer those requests. Answers could be a page, a serialized object, or nothing (an HTTP code).

There are two ways to write a controller (there will be more in next SerfJ versions), extending net.sf.serfj.RestController class, or even writing a JavaBean. Last case is weird because the controller will not be able to do some actions like getting parameters from the request, or redirect to another page, or send objects to JSP pages.

For now, extending RestController class is the best way to write a controller. Methods that attend requests musn't have parameters, but can return objects and throw exceptions. For example, if we need a controller to attend request for /accounts, we have to write a class like this:

            public class Account extends RestController {
            }
        

2.1 Annotations

There are several annotations that tell the framework which HTTP method is accepted by a controller's method.

Also, there is another one, @DoNotRenderPage that tells the framework that after executing a controller's method, no page will be rendered, but a HTTP 204 code will be answered. Since a method that returns an object doesn't render a page as a result, it doesn't need to be annotated with @DoNotRenderPage. However, a method that doesn't return anything (a void method) but developer doesn't want to render a page as a result, does have to be annotated.

2.2 Getting parameters from the request

Obiously, the class we wrote before won't do anything, it won't answer any request. Let's write more stuff. If we need a method for updating some information from an account (PUT /accounts/1), we could write a method like this:

            public class Account extends RestController {
			    @PUT
			    public void updateAccount() {
				    String accountId = this.getId();
				    // Do something to update the account
				}
            }
        

We see that the method can recover account's identifier from the request with getId(String) method. Let's see how we get others identifiers if requests have nested resources (PUT /banks/1/accounts/2).

            public class Account extends RestController {
                @PUT
                public void updateAccount() {
                    String accountId = this.getId();
                    String bankId = this.getId("bank");
                    // Do something to update the account from the bank received
                }
            }
        

If you put some parameters in the request, the method can get them too. Params can travel in the query string, or within the request.

            public class Account extends RestController {
                @PUT
                public void updateAccount() {
                    String accountId = this.getId();
                    String someInfo = this.getStringParam("some_info_param_name");
                    // Do something to update the account
                }
            }
        

But what about receiveing objects that are not strings?.

            public class Account extends RestController {
                @PUT
                public void updateAccount() {
                    String accountId = this.getId();
                    String someInfo = this.getStringParam("some_info_param_name");
					Balance balance = (Balance) this.getParam("balance"));
                }
            }
        

2.3 Sending parameters to the response

Well, now we know how to get parameters from requests, but sometimes we'll need to send objects to a JSP, for example. Obviously those objects must implement java.io.Serializable.

            public class Account extends RestController {
                @PUT
                public void updateAccount() {
				    Account account = // some code to get an account
				    this.putParam("my_object_param_name", account);
                }
            }
        

It will be very common that methods return objects. As we have seen in section 1.1, those methods must be called with REST URLs that end with an extension. The extension will point the framework what serializer must be used to make the response. SerfJ provides three different serializers (XML, JSON or Base64) for three different extensions (.xml, .json, .base64), but developers can write their own serializers(see section 3).

For example, a method that responds to URLs like /accounts/1/balance.xml could be written in two ways. Let's see the first way to do it:

            public class Account extends RestController {
                @GET
                public Balance balance() {
				    Balance balance = new Balance();
					return balance;
                }
            }
        

This method will always try to return an object, how the object is serialized depends on the extension used. But may be we need a method that returns an object when an extension is received, or render a page in other cases:

            public class Account extends RestController {
                @GET
                public void balance() {
                    Balance balance = new Balance();
                    if (this.getSerializer() != null) {
                        this.serialize(balance);
                    } else {
					    // This is optional, we need it only if we want to send the object to the page
                        this.putParam("balance", balance);
                        this.renderPage("balance");
                    }
				}
            }
        

In this case, if there is any object serialized, the framework will write it in the response.

2.4 Rendering pages

Controller's methods will always render a page after their execution unless the method has a returning object or is annotated with @DoNotRenderPage. The page it will try to render must be in a subdirectory, whose name is the resource's name, also must be under the directory defined in views.directory property (by default it's views). Pages must have .jsp, .html or .htm extensions. For example:

ControllerMethodView
Accountvoid index()views.directory/account/index
Accountvoid show()views.directory/account/show
Accountvoid newResource()views.directory/account/new
Bankvoid edit()views.directory/bank/edit
Carvoid update()views.directory/car/update
Accountvoid create()views.directory/account/create
Accountvoid delete()views.directory/account/delete
Accountvoid myMethod()views.directory/account/myMethod
AccountObject myMethod()Returns a serialized object
Account@DoNotRenderPage void myMethod()Returns an HTTP 204 code

That's how the framework renders pages by default, but there are ways to render other pages. There are three methods to render pages:

2.5 Serialization

URLs may end with different extensions. The default extensions are .xml, .json, .base64 and .file. If an URL ends with an extension, the framework will try to serialize the response in the format specified using a Serializer (read section 3). Some examples:

HTTP MethodURLController's methodViewMeaning
GET/accounts.xmlindex()N/ASend every account serialized in XML
GET/accounts/1.xmlshow()N/ASends account 1 in XML
GET/documents/1.fileshow()N/ADownload document with ID 1
GET/songs/1.mp3play()N/ADownload a MP3 song with ID 1 (you'd need to implement a Mp3Serializer extending FileSerializer)
As you see, only GET method has some meaning when an extension is used.

2.6 Serving files

As of version 0.4.0 is possible to serve files using the extension .file in the requests. But you must set the location for the file in order to get SerfJ reading it.

            @GET
            @DoNotRenderPage
            public void download() {
                // Set the file location
                this.getResponseHelper().setFile(new File("path_to_a_file/avatar_sabreman.png"));
                // SerfJ will download the file
            }
        

The default implementation set as content type application/octect-stream so if you need to send a different one using the same .file extension, you must set the content type along with the file:

            @GET
            @DoNotRenderPage
            public void download() {
                // Set the file location
                this.getResponseHelper().setFile(new File("path_to_a_file/avatar_sabreman.png"));
                this.getResponseHelper().setContentType("audio/mpeg3");
                // SerfJ will download the file
            }
        

On the other hand if you want to use a different extension like .mp3, .txt, .pdf or so on, you must implement your own file serializer. For example, an implementation for .mp3 extension would be:

            package net.sf.serfj.serfj_sample.serializers;
            
            import net.sf.serfj.serializers.FileSerializer;
            
            /**
             * Serializer for MP3 audio files.
*/ public class Mp3Serializer extends FileSerializer { /** * Content type that will be used in the response. */ public String getContentType() { return "audio/mpeg3"; } }

2.7 Accessing javax resources

Although SerfJ tries to avoid that controllers have dependencies with javax.servlet package, sometimes the developer could be limited by the framework functionality, so as of version 0.4.0, net.sf.serfj.RestController has a way to access javax.servlet.ServletContext, javax.servlet.http.HttpServletRequest and javax.servlet.http.HttpServletResponse.

            ServletContext context = this.getResponseHelper().getContext();
            HttpServletRequest request = this.getResponseHelper().getRequest();
            HttpServletResponse response = this.getResponseHelper().getResponse();
        

3. Serializers

When a REST request arrives, if it has an extension before the query string, a serializer capable to serialize the response is searched (read section 5 to learn how resources are searched). SerfJ provides serializers for XML, JSON, Base 64 and FILES (.xml, .json ,.base64 or .file extensions), but developers can make their owns, and can make others to treat different extensions.

It's very easy to develop new serializers, you only need to implement net.sf.serfj.serializers.ObjectSerializer interface, that is so simple that it isn't needed more explanations than its own Javadoc:

            package net.sf.serfj.serializers;
            
            /**
             * Interface for Serializers.
             * 
             * @author Eduardo Yáñez
             */
            public interface ObjectSerializer {
                /**
                 * Serialize an object in the format that the implementation requires.
                 * 
                 * @param object
                 *            Object to serialize.
                 * @return a String with the object serialized.
                 */
                public String serialize(Object object);
            
                /**
                 * Deserialize an object from the format that the implementation requires to
                 * Java Object.
                 * 
                 * @param string
                 *            String representation of the object to deserialize.
                 * @return an Object.
                 */
                public Object deserialize(String string);
            
                /**
                 * Content type that will be used in the response.
                 */
                public String getContentType();
            }
        

For downloading files there is a net.serfj.serializers.FileSerializer that serves any file with a content type of "application/octect-stream". Also you can define your own files serializers extending the class and overriding the FileSerializer.getContentType() method. For example, a serializer for serving MP3 files could be:

            package net.sf.serfj.serfj_sample.serializers;
            
            import net.sf.serfj.serializers.FileSerializer;
            
            /**
             * Serializer for MP3 audio files.
*/ public class Mp3Serializer extends FileSerializer { /** * Content type that will be used in the response. */ public String getContentType() { return "audio/mpeg3"; } }

The class' name must start with the extension that the serializer is made for, followed by the resource's name, and must end with Serializer:

Other option is having the serializers within a 'package.hierarchy.serializers' package. Then the class' name must start with the extension that the serializer is made for, and must end with Serializer. This way you can implement generic serializers for every model:

4. Configuration

This framework tries to follow the concept of Convention over Configuration, so to use it's almost unnecessary to configure it. Of course it needs to be configured, but only a little bit. However, if developers want to get it running better, they can set up several configuration properties in order to avoid SerfJ to do predictions to find some resources.

SerfJ has only one configuration file serfj.properties that has to be at /config directory within the classpath. It only needs a main.package property to work. This property must point to the package where SerfJ will look for controllers and serializers, but it doesn't mean that all controllers and serializers must be in that package, the way the framework look for resources is explained in the next section.

4.1 Configuration properties

Here you have an example for a configuration file:

            # Main package where looking for classes (controllers, serializers)
            main.package=net.sf.serfj.test
            
            # Directory with the JSP, HTML, etc...
            # 
            views.directory=src/test/webapp/WEB-INF/views
            
            # Packages style.
            # There are 3 types of styles:
            #
            # FUNCTIONAL/functional
            # All classes in the same package by functionality
            # net.sf.serfj.controllers.Bank
            # net.sf.serfj.controllers.Order
            # net.sf.serfj.serializers.JsonBankSerializer
            # net.sf.serfj.serializers.XmlOrderSerializer
            #
            # FUNCTIONAL_BY_MODEL/functional_by_model
            # Classes by model and functionality
            # net.sf.serfj.bank.controllers.Bank
            # net.sf.serfj.bank.serializers.JsonSerializer
            # net.sf.serfj.order.controllers.Order
            # net.sf.serfj.order.serializers.JsonSerializer
            #
            # MODEL/model
            # All classes in the same package by model
            # net.sf.serfj.bank.Bank
            # net.sf.serfj.bank.JsonSerializer
            # net.sf.serfj.order.Order
            # net.sf.serfj.order.JsonSerializer
            #
            # OFF/off/Leave blank
            # Leave blank or don't define it. The library will try different ways to find
            # the classes in the following order: FUNCTIONAL, FUNCTIONAL_BY_MODEL, MODEL
            # This method is less efficient, so it's better defininig some one.
            #packages.style=
            
            # You can change the name of controllers' package if there is one.
            # Default is 'controllers'.
            #alias.controllers.package=controllers
            #alias.controllers.package=ctrl
            #alias.controllers.package=controllers.main
            
            # You can change the name of serializers' package if there is one.
            # Default is 'serializers'.
            #alias.serializers.package=utils
            #alias.serializers.package=utils.serializers
            
            # Suffixes used
            # If you don't want any suffix set OFF/off as value
            # For controllers default is 'OFF'
            #suffix.controllers=Controller
            # Controller class must named net.sf.serfj.bank.BankController instead of net.sf.serfj.bank.Bank
            # For serializers defaults is 'Serializer'
            #suffix.serializer=
        

5. Looking for resources

There are three ways in which SerfJ look for resources, they are named:

If no one is defined in the configuration file, then the framework uses each one in that order to find resources. So if a developer wants to tell SerfJ how it must search for resources, he must define the packages.style property.

5.1 Functional style

If it is looking for a controller, then the qualified class name will be:

main.package + "." + alias.controllers.package + "." + capitalized(singularized(resource name)) + suffix.controllers.

So having this configuration:

The framework will look for a controller with the next qualified name:

net.sf.serfj.tests.controllers.BankCtrl

If the resource searched is a serializer, then a prefix is used for the class name. The prefix is not configurable, it'll be the extension of the request capitalized (Xml, Pdf, Json, File, etc.). For example, having this configuration:

Looking for a Pdf serializer for accounts, the qualified class name will be:

net.sf.serfj.tests.serializers.PdfAccountSerializer

Looking for a Pdf serializer for whatever model, the qualified class name will be:

net.sf.serfj.tests.serializers.PdfSerializer

Note: Generic serializers only are able to be defined within the Functional style.

5.2 Functional by model style

In this strategy, resource name is singularized and appended to the main.package property. If it is looking for a controller, then the qualified class name will be:

main.package + "." + singularized(resource name) + "." + alias.controllers.package + "." + capitalized(singularized(resource name)) + suffix.controllers.

So having this configuration properties defined:

The framework will look for a controller with the next qualified name:

net.sf.serfj.tests.bank.ctrl.Bank

If the resource searched is a serializer, then a prefix is used for the class name. The prefix is not configurable, it'll be the extension of the request capitalized (Xml, Pdf, Json, Base64, etc.). For example, having this configuration properties defined:

Looking for a Pdf serializer for accounts, the qualified class name will be:

net.sf.serfj.tests.account.serial.PdfAccountSerializer

5.3 By model style

In this strategy, resource name is singularized and appended to the main.package property, but the alias is not used. If it is looking for a controller, then the qualified class name will be:

main.package + "." + singularized(resource name) + "." + capitalized(singularized(resource name)) + suffix.controllers.

So having this configuration properties defined:

The framework will look for a controller with the next qualified name:

net.sf.serfj.tests.bank.Bank

If the resource searched is a serializer, then a prefix is used for the class name. The prefix is not configurable, it'll be the extension of the request capitalized (Xml, Pdf, Json, Base64, etc.). For example, having this configuration:

Looking for a CSV serializer for accounts, the qualified class name will be:

net.sf.serfj.tests.account.CsvAccountSerializer

6. SerfJ's client

SerfJ provides a class net.sf.serfj.client.Client in order to do REST requests. It has four public methods (get, post, put and delete) used to do requests. The interface is very simple, so the Javadoc should be enough to use this class.

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.