Starting a project: an e-commerce
A product CRUD
We will start the system with product registration. For that, we will need a class that models a product and we will also use it to save products in a database, using JPA/Hibernate:
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private String description;
private Double price;
// getter and setters and other necessary business methods
}
We also need a class that will manage product registration, dealing with web requests. This class will be the products Controller:
public class ProductsController { ... }
The ProductsController
class will expose URIs that can be accessed through the web, i.e., it will expose your application resources. This type of classes have to annotated with @Controller
:
@Controller
public class ProductsController { ... }
Whenever you annotate a class with @Controller
all its public methods are accessible through the web. Take the list method as an example:
@Controller
public class ProductsController {
public List<Product> list() {
return new ArrayList<Product>();
}
}
VRaptor will automatically redirect all request to /products/list
to this method. That, of course, means the convention for URI’s is /<controller_name>/<method_name>
. After the method is executed, VRaptor will dispatch the request to /WEB-INF/jsp/products/list.jsp
, and that is because VRaptor’s convention is to dispatch it to /WEB-INF/jsp/<controller_name>/<method_name>.jsp
, by default.
The method list
returns a product’s list, and you can access it at the JSP through request attributes. For that example, an attribute called productList
can be found at that JSP:
<ul>
<c:forEach items="${productList}" var="product">
<li>${product.name} - ${product.description}</li>
</c:forEach>
</ul>
The exported attribute name convention is quite intuitive: if it is a Collection, the attribute will have the List
sufix; in case it is a regular class, its name is the same as the class, but in lower case. For example, if you method returns a Product
, you will be able to access it at the JSP through ${product}
. Exporting more than one object to the page is a topic we will cover at another chapter.
Creating ProductDao: Dependency Injection (DI)
VRaptor strongly relies on dependency injection (DI) and Inversion of Control (IoC) concepts. The intention is that you won’t need to create or load your class’ dependencies - instead you will just receive the objects VRaptor creates for you. The previous example returns an empty list on our list
method. It would be much more interesting returning a real list, with all the registered products. Let’s create a Product DAO, responsible for listing products
public class ProductDAO {
public List<Product> listAll() {
return list; //a list retrieved from your database
}
}
And, we can use the DAO on the ProductsController
to list products:
@Controller
public class ProductsController {
@Inject
private ProductDAO dao;
public List<Product> list() {
return dao.listAll();
}
}
We can instantiate ProductDAO
from the controller, but that can be complicated, specially if the DAO itself has its own dependencies. It would be much more interesting to take advantage from VRaptor’s dependency management. By annotating the DAO with @RequestScoped
, and the controller attribute with @Inject
we indicate the DAO should be injected to the Controller.
@RequestScoped
public class ProductDAO {
//...
}
@Controller
public class ProductsController {
@Inject
private ProductDAO dao;
public List<Product> list() {
return dao.listAll();
}
}
Create form: redirecting
We already have the product lists, but we can’t register them yet. We will create a form to add products. In order to avoid accessing the JSP directly, we will create an empty logic that will only redirect to the JSP:
@Controller
public class ProductsController {
//...
public void form() { ... }
}
The form jsp will be placed at /WEB-INF/jsp/products/form.jsp
, and can be accessed through the URI /produtos/form
:
<form action="<c:url value='/products/add'/>">
Name:
<input type="text" name="product.name"/><br/>
Description:
<input type="text" name="product.description"/><br/>
Price:
<input type="text" name="product.price"/><br/>
<input type="submit" value="Save" />
</form>
The form, as we designed it, will save the product through the URI /products/add
, threfore we need to create that method on the controller:
@Controller
public class ProductsController {
//...
public void add() { ... }
}
Note the input names: product.name
, product.description
, and product.price
. If we get a Product
called product
as an add
method parameter, VRaptor will populate its name
, description
and price
fields using values from the inputs on the product’s setters. Even the price
field will be converted to a Double
before being set to the product. Learn more about this on the converters chapter.
Then, by using the correct names on the form inputs, all you need to do is to make the add
method receive a Product
for parameter:
@Controller
public class ProductsController {
//...
public void add(Product product) {
dao.add(product);
}
}
After adding something to the application, it is common to navigate back to the list, or the product registration form. In our example, we want to redirect back to the products list, whenever a new product is added. VRaptor’s Result
component is responsible for adding attributes to a request, and change the redirection view. In case we need a Result
instance, all we need to do is to have a result field annotated with @Inject
:
@Controller
public class ProductsController {
@Inject
private ProductDAO dao;
@Inject
private Result result;
//...
}
And, in order to redirect to the listing, we can use the result
:
result.redirectTo(ProductsController.class).list();
We can read this line of code: as a result, redirect to the list
method of the ProductsController
. The redirect configuration is 100% java, not Strings! The code is very clear in showing that the result is not the default, but the configured one. There is no need to worry about configuration files. And, even better, if you ever change the name of the list
method, a simple IDE rename refactor, will automatically change such call: everything just works!
Our add
method will be:
public void add(Product product) {
dao.add(product);
result.redirectTo(ProductsController.class).list();
}
Learn more about the Result
on the Views and Ajax methods.
Notice that you already know enough to build 90% of your application! The next sections are focused on some of the frequent problems that represent the other 10% of your application construction.
Using JPA/Hibernate to save Products
Now we will focus on the actual implementation of the ProductDAO
, using the JPA to assist us persist products. In order to do that, our ProductDAO
needs an EntityManager
. Using VRaptor DI, all you’ll need to do is to declare an EntityManager
field and annotate it with @Inject
.
public class ProductDAO {
@Inject
private EntityManager manager;
public void add(Product product) {
manager.persist(product);
}
//...
}
VRaptor needs to know how to create that EntityManager
and we cannot annotate an EntityManager
as @RequestScoped
, since that is a JPA class. That is when we need to create a Producer.
Check out the Components chapter. Alternatively, you can simply use the JPA plugin. It already does all that work for you and also helps you make the transaction control. You can learn more about that plugin on the plugins page.
Transaction control: Interceptors
We often want to intercept all requests (or some of them) and execute some logic, such as transaction management. That is where VRaptor interceptors jump in to help you out. Get to know how to use them at the Interceptors chapter.
Shopping Cart: session components
When we need to create a shopping cart on our application, we need to keep cart items somehow. In order to do so, we can create a session scoped component, i.e., an unique component that will last through the user session. For that, annotate your component with @SessionScoped
instead:
@SessionScoped
public class ShoppingCart implements Serializable{
private List<Product> items = new ArrayList<Product>();
public List<Product> getAllItems() {
return items;
}
public void addItem(Product item) {
itens.add(item);
}
}
This class needs to implement Serializable
. That happens because, since it is @SessionScoped
, objects created from it may need to be serialized in order to keep the session state. That need is described at the CDI specification and must be respected, otherwise the container will throw an exception during initialization.
The shopping cart is a component, therefore it can be injected in the controller:
@Controller
public class CartController {
@Inject
private ShoppingCart cart;
public void add(Product product) {
cart.addItem(product);
}
public List<Product> listItems() {
return cart.getAllItems();
}
}
Besides the session scope, there is also de application scope. Components annotated with @ApplicationScoped
are created a single time during the application run.
A bit of REST
The REST approach is to have URIs identify resources on the web, so we can profit from the many structural advantages the HTTP protocol provides us. If we could map various HTTP method calls to one URI while having different controller methods respond to them, we could have the following calls for out product’s registration:
GET | /products | list all products |
POST | /products | add a product |
GET | /products/{id} | view the product with that id |
PUT | /products/{id} | update information from product with that id |
DELETE | /products/{id} | remove product with that id |
In order to create that REST behaviour on VRaptor, the method name convention won’t suffice, so we need to change them. The @Path
annotation can change the URI pointing to a method without limiting the HTTP verb that can access it. For example, annotating the list method with @Path("/products")
will have all requests to “/products” call the list
method:
@Path("/products")
public List<Product> list() {...}
However, if we want to restrict it to a specific HTTP method, we can use @Get
, @Post
, @Delete
, and @Put
annotations, instead, with the same attribute syntax:
public class ProductsController {
//...
@Get("/products")
public List<Product> list() {...}
@Post("/products")
public void add(Product product) {...}
@Get("/products/{product.id}")
public void view(Product product) {...}
@Put("/products/{product.id}")
public void update(Product product) {...}
@Delete("/products/{product.id}")
public void remove(Product product) {...}
}
Also, note that we can receive parameters on the URIs. For example, if we call a GET /products/5
URI, the view
method will be invoked, and the product
parameter id will be populated with 5
.
More information about that is available on the REST Controller chapter.
Message files
Internationalization (i18n) is a powerful resource, present in almost every current web frameworks. VRaptor is not an exception. I18n helps us have our application running in various languages with little effort. All we will need is the messages translation.
In order to have that running on your app, all you have to do is have a messages.properties
file on our application classpath (WEB-INF/classes
). That file will contain many key/value entries in the application primary language, such as:
userName.field = Username
password.field = Password
Now, if we wanted to translate our application to Portuguese, all we need to do is to create a new file, much like the previous one, but with the language suffix: messages_pt.properties
. The _pt
suffix indicates that a user that accesses the application from a machine configured to a locale of Portuguese will see that message values, instead:
userName.field = User name
password.field = Password
The keys are the same, of course, but the values change accordingly to the language. Then, you can use those messages on your JSP by using the formatting JSTL:
<!%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
<body>
<fmt:message key="userName.field"/>
<input name="user.username"/> <br/>
<fmt:message key="password.field"/>
<input type="password" name="user.password"/>
<input type="submit"/>
</body>
</html>
Next steps
Continue exploring the other sections of this documentation and Cookbooks, with solutions to common problems.
While studying VRaptor, should you have any doubts please refer to questions.vraptor.org, or visit our discussion list at discussion list.
You can see all this and many other features in a practical way in our sample project, the vraptor-music-jungle