Reference Manual

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, 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 will control the flow of SerfJ's applications, is 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 to update or create a resource, 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 a REST request 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

There are some URLs that will always call the same controller's method:

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 to update 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("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("account");
                    String bankId = this.getId("bank");
                }
            }
        

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("account");
                    String someInfo = this.getStringParam("some_info_param_name");
                }
            }
        

But what about sending objects that are not strings?. Well, you need to serialize them before sending them, then deserialize them in the controller's method. SerfJ provides a class to serialize/deserialize objects to Base 64 strings. But you can use whatever you want to do it.

            public class Account extends RestController {
			    private Base64Serializer serializer = new Base64Serializer();
				
                @PUT
                public void updateAccount() {
                    String accountId = this.getId("account");
                    String someInfo = this.getStringParam("some_info_param_name");
					Balance balance = (Balance) serializer.deserialize(this.getStringParam("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.addObject2Request("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 it must use to make the response. SerfJ provides three different serializers (XML, JSON or Base64) for three different extensions (.xml, .json, .64), but developers can write their own serializers(see section 3).

For example, a method that respond 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 (response.getSerializer() != null) {
                        response.serialize(balance);
                    } else {
					    // This is optional, we need it only if we want to send the object to the page
                        this.addObject2Request("balance", balance);
                        response.renderPage("balance");
                    }
				}
            }
        

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:

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 and Base 64 (.xml, .json or .base64 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.Serializer 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 Serializer {
			    /**
			     * 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();
			
			    /**
			     * Returns the extension which came in the URL. With that extension the
			     * framework knows which serializer must use for serialization.
			     * 
			     * @return a String with an extension without the dot.
			     */
			    public String getExtension();
			}
		

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

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

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, etc.). For example, having this configuration:

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

net.sf.serfj.tests.serializers.JsonAccountSerializer

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 XML serializer for accounts, the qualified class name will be:

net.sf.serfj.tests.account.serial.XmlAccountSerializer

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.