Resources-Rest
What are Resources?
Resources are anything that can be accessed by our clients.
In a VRaptor-based Web application, a resource must be annotated with @Resource.
If you annotate a class with @Resource, all its public methods become accessible
through GET requests to specific URIs.
The following example shows a resource called ClienteController, which provides
several operations over clients.
Creating the class below with all its methods instantly make the URIs /client/add,
/client/list, /client/show, /client/remove and /client/update
available, each one invoking the respective method.
@Resource
public class ClientController {
public void add(Client client) {
...
}
public List<Client> list() {
return ...
}
public Client show(Client profile) {
return ...
}
public void remove(Client client) {
...
}
public void update(Client client) {
...
}
}
Parâmetros dos métodos
You can receive parameters on your controller methods, and if those parameters
follow the java beans convention (getters and setters for class fields),
you can use dots for browsing through the fields. For instance, on method:
public void update(Client client) {
//...
}
you can receive on the request parameters:
client.id=3
client.name=John Doe
client.user.login=johndoe
and the respective fields will be set, browsing through getters and setters starting
from client.
If an object field or a method parameter is a list (List<> or array), you can receive
several request parameters, using square brackets and indexes:
client.phones[0]=+55 11 5571-2751
client.relatives[0].id=1
client.relatives[3].id=1
client.relatives[0].name=Mary Doe
clients[1].id=23
Reflection on parameter names
Unfortunately Java can't reflect parameters names, these data don't stay in bytecode
(unless you compile your code in debug mode, but that is optional). It causes that most
frameworks that need this kind of information end up creating annotations, which makes
a very ugly code (like JAX-WS, where its very common to find methods with signature like:
void add(@WebParam(name="client") Client client)).VRaptor uses the Paranamer framework (http://paranamer.codehaus.org), which can get parameter
names information through pre compilation or debug data, avoiding the creation of annotations
for this purpose. Some VRaptor developers also participate in Paranamer development.
Scopes
Sometimes you want to share a component among all users, or through all requests from
the same user or one instance for each user request.
To specify in which scope your component will live, use the annotations
@ApplicationScoped, @SessionScoped and @RequestScoped.
If you don't specify a scope for your component, VRaptor assumes the request scope,
meaning a fresh instance will be created for each request.
Http Methods
The best practice when using HTTP Methods is to specify a different URI for each method,
like GET, POST, PUT etc.
In order to accomplish that, we use annotations @Get, @Post, @Delete etc,
in conjunction with the @Path annotation, which allows us to configure a custom URI.
The following example changes the default URIs for ClienteController.
Now we specify two different URIs for different HTTP methods:
@Resource
public class ClientController {
@Path("/client")
@Post
public void add(Client client) {
}
@Path("/")
public List<Client> list() {
return ...
}
@Get
@Path("/client")
public Client show(Client client) {
return ...
}
@Delete
@Path("/client")
public void remove(Client client) {
...
}
@Put
@Path("/client")
public void update(Client client) {
...
}
}
As you can see, we used HTTP methods + a specific URI to identify each method
in our Java class.
We must be very careful when creating hyperlinks and HTML forms, because web browsers
currently support only POST and GET methods.
For that reason, requests for methods DELETE, PUT etc should be created
through JavaScript, or by adding an extra parameter called _method. The latter
one only works on POST requests.
This parameter will overwrite the real HTTP method being invoked.
The following example creates a link to show one client's data:
<a href="/client?client.id=5">show client 5</a>
Now an example on how to invoke the method to add a client:
<form action="/client" method="post">
<input name="client.name" />
<input type="submit" />
</form>
Notice that if we want to remove a cliente using the DELETE HTTP method, we have
to use the _method parameter, since browsers still don't support that kind
of requests:
<form action="/client" method="POST">
<input name="client.id" value="5" type="hidden" />
<button type="submit" name="_method" value="DELETE">remove client 5</button>
</form>
@Path
The @Path annotation allows you to specify custom access URIs to your controller methods.
The basic usage of the annotation is to specify a fixed URI. The following example shows
how to customize the access URI for a method that accepts POST requests only. The URI
we want to specify is /client:
@Resource
public class ClientController {
@Path("/client")
@Post
public void add(Client client) {
}
}
If you place the @Path on ClientController, the specified value will be used as prefix for all
URIs from this controller.
@Resource
@Path("/clients")
public class ClientController {
//URI: /clients/list
public void list() {...}
//URI: /clients/save
@Path("save")
public void add() {...}
//URI: /clients/allClients
@Path("/allClients")
public void listAll() {...}
}
Path with variable injection
Sometimes we want the uri to include, for example, the unique identifier of
my resource.
Suppose a client controller application where the client's unique identifier (primary key)
is a number. We can map our URIs as /client/{client.id}, so we can visualize each client.
That is, if we access the URI /client/2, the show method will be invoked
and the client.id parameter will be set to 2. If the URI /client/1717 is
accessed, the same method will be invoked with the 1717 value.
That way we can create unique URIs to identify different resources in our application.
See the mentioned example:
@Resource
public class ClientController {
@Get
@Path("/client/{client.id}")
public Cliente show(Client client) {
return ...
}
}
You can go further and set several parameters through the URI:
@Resource
public class ClientController {
@Get
@Path("/client/{client.id}/show/{section}")
public Client show(Client client, String section) {
return ...
}
}
Multiple paths for the same logic method
You can set more than one path for the same logic method:
public class ClientController {
@Get
@Path({"/client/{client.id}/show/{section}", "/client/{client.id}/show/"})
public Client show(Client client, String section) {
return ...
}
}
Paths with regular expressions
You can limit your parameter values using regular expressions using this idiom:
@Path("/color/{color:[0-9A-Fa-f]{6}}")
public void setColor(String color) {
//...
}
Everything that is after the colon is treated as a regex, and the URI will only match
if the parameter matches the regex:
/color/a0b3c4 => matches
/color/AABBCC => matches
/color/white => doesn't match
Paths with wildcards
You can also use the * wildcard as a selection method for your URI. The following
example ignores anything that comes after the word photo/ :
@Resource
public class ClientController {
@Get
@Path("/client/{client.id}/photo/*")
public File photo(Client client) {
return ...
}
}
And now a similar code, but used to download a specific photo from a client:
@Resource
public class ClientController {
@Get
@Path("/client/{client.id}/photo/{photo.id}")
public File photo(Client client, Photo photo) {
return ...
}
}
Sometimes you want the parameter to include the / character. In that case,
you should use the pattern {...*}:
@Resource
public class ClientController {
@Get
@Path("/client/{client.id}/download/{path*}")
public File download(Client client, String path) {
return ...
}
}
Specifying priorities for your paths
It is possible for some URIs to be handled by more than one method in our class:
@Resource
public class PostController {
@Get
@Path("/post/{post.author}")
public void show(Post post) { ... }
@Get
@Path("/post/current")
public void current() { ... }
}
The URI /post/current can be handled by both show and current methods.
But I don't want to invoke the show method with that URI,
what I want is VRaptor to test the current
path first, avoiding the invocation of the show method.
In order to do that, we can define priorities for @Paths, so VRaptor will first
test paths with higher priority, in other words, paths with lower priority values.
@Resource
public class PostController {
@Get
@Path(priority = 2, value = "/post/{post.author}")
public void show(Post post) { ... }
@Get
@Path(priority = 1, value = "/post/current")
public void current() { ... }
}
This way, the "/post/current" path will be tested before "/post/{post.author}" by VRaptor,
solving our problem.
RoutesConfiguration
Finally, the most advanced way to configure access routes for your resources is
using a RoutesConfiguration.
This component must be configured as application scoped and must implement the
config method:
@Component
@ApplicationScoped
public class CustomRoutes implements RoutesConfiguration {
public void config(Router router) {
}
}
Having access to a Router, you can define access routes to methods. And the best
part is that the configuration is refactor-friendly, that is, if you change a method's name,
the configuration reflects the change, but the uri stays the same:
@Component
@ApplicationScoped
public class CustomRoutes implements RoutesConfiguration {
public void config(Router router) {
new Rules(router) {
public void routes() {
routeFor("/").is(ClientController.class).list();
routeFor("/client/random").is(ClientController.class).random();
}
};
}
}
You can also put parameters on the uri and they will be set directly on the method
parameters. You can also add restrictions to these parameters:
// show method receives a Client that has an id
routeFor("/client/{client.id}").is(ClientController.class).show(null);
// If I want to ensure that the parameter is a number:
routeFor("/client/{client.id}").withParameter("client.id").matching("\\d+")
.is(ClientController.class).show(null);
At last, you can choose the class and the method names in runtime, allowing us to
create extremely generic routes:
routeFor("/{webResource}/doSomething/{webMethod}").is(
type("br.com.caelum.projectname.{webResource}"),
method("{webMethod}"));