This article is based on Liferay in Action, to be published on August, 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]
Introduction
Liferay’s code generator for database persistence, which is called Service Builder, jump-starts portlet development. This utility (which ships as part of Liferay) creates code and SQL for accessing a database from within portlets. Since it uses Spring and Hibernate to implement this, it’s not much different from what you would already do manually, with the important exception that it does much of this “grunt work” automatically, freeing time for you to implement your business logic.
In this article, we’ll continue working with the data layer of a product registration portlet for a fictional company named Inkwell, which we started in part 1 of this series. In part 1, you used Service Builder to generate your persistence objects. Now we’ll see how to use that persistence layer to store and retrieve objects.
How Service Builder works
As shown in figure 1, Service Builder automatically generates several files for you. Among them are both the Data Access Object and the Data Transfer Object layers.
Figure 1 Service Builder generates for you everything from the service layer down. You might need to modify/customize some of this code, but you won’t have to touch most of it. This significantly increases developer productivity.The layer you’ll work with for the most part is the Data Transfer Objects (DTO) layer. This layer talks to the Service Layer, which is automatically generated and allows you to work with objects that need to be persisted or have been retrieved from the database. These are the -Impl classes that you generated in part 1 of this series, and this is the reason why they were placed in your src folder with the rest of your portlet code. You’ll call methods from the Data Access Objects in the persistence layer to do the actual persisting. These methods are generated from the <finder> tags you placed in your service.xml file for Inkwell’s product registration portlet.
Let’s see how this actually works.
Implementing the DTO layer
Though not created yet, Inkwell’s design calls for a form in Edit mode of the portlet. This form allows users to enter products that appear in a dropdown selection box in the actual registration form that makes up the portlet’s View mode. Only two fields will be coming from this form because the third is the primary key, which will be generated:
- Product name
- Product’s serial number mask, which is set by the manufacturing department and can be used for field validation
So, how do we get the data the user entered into the database? Remember, we’re after the idea of loose coupling. This means that we don’t want to have any database insertion code in our business logic. So our business logic (when we get to it) will need to call a generic method that has nothing to do with the database. This is where the DTO layer comes in. It acts as a buffer between your business logic and your underlying database code. Service Builder generates both layers, leaving the DTO layer as a stub for you to implement as you wish.
Create a generic method
Presumably, we’ll want a method that has two parameters in the method signature for our two values—the product name and the product’s serial number mask. So let’s create it.
Open the file ProductLocalServiceImpl.java. You’ll find this in a newly generated package called com.inkwell.internet.productregistration.service.impl. This class extends a generated class called ProductLocalServiceBaseImpl (see figure 2).
If you’re using an IDE (such as Eclipse) that does not automatically recognize when another tool adds files, you may need to click on the project name and press F5 to refresh the project in order to see this package.
Figure 2 Service Builder generates both your DAO and DTO layer. Shown above is the DTO layer, which has an instance of the DAO layer (the productPersistence attribute) injected into it by Spring.The first thing we’ll implement in ProductLocalServiceImpl is adding a product. Once you see how that’s done, implementing the other methods will be very simple.
Adding a product
Another great thing about the design of Service Builder is that you never have to touch or modify any of the classes it generates. All of the Spring dependency injection, the Hibernate session management, and the query logic stay in classes you never have to touch. You add code in a class that extends the generated class and, if you add something that changes the interface/implementation contract, those changes are propagated up the chain so the contract is never broken. We’ll see an example of this a bit later. For now, you’ll see that this file is just an empty stub. This is because you have yet to implement anything. So we’ll implement our first method, which will take the values passed to it and call the underlying database code to persist the data to the database (listing 1).
Listing 1 Adding a product to the database
public Product addProduct(PRProduct newProduct, long userId) throws SystemException, PortalException { PRProduct product = prProductPersistence.create(counterLocalService.increment(PRProduct.class. GetName())); 1 resourceLocalService.addResources(newProduct.getCompanyId(), newProduct.getGroupId(), userId, Product.class.getName(), product.getPrimaryKey(), false, true, true); 2 product.setProductName(newProduct.getProductName()); product.setSerialNumber(newProduct.getSerialNumber()); product.setCompanyId(newProduct.getCompanyId()); product.setGroupId(newProduct.getGroupId()); return prProductPersistence.update(product, false); } #1 Create empty object #2 Create permissions resources
The first thing we do (#1) in this method is create a new Product object. Product is the Interface (inherited from PRProductModel), and PRProductImpl (which extends PRProductModelImpl) is the implementation. Note that all we have to do is specify the Interface here since a Factory design pattern is used to create the object for us using the prProductPersistence object that has been injected into this class by Spring. The interface and implementation of this object were both generated automatically by Service Builder, and the fields within the object map directly to the fields in the PRProduct table that we defined. To obtain a new instance of this object, we call a create method, which was also generated by Service Builder.
Because Liferay is database agnostic, it does not use any database-specific means of generating primary keys for its database. Instead, it provides its own utility for generating primary keys. Since the create method that was generated requires a primary key, we need to call Liferay’s Counter utility to generate this. Here’s the cool thing about how this is done: the Counter is automatically injected into the class we’re working on. Why? Because, if you’re doing work with databases and Service Builder, you’re 100 percent likely to need the Counter to create new objects that will be persisted.
Notice that in #2, we next make a call to resourceLocalService to persist resources. Resources are used to define permissions on the objects that are persisted. We added CompanyId and GroupId fields to our tables in order to make our portlet a non-instanceable portlet and to enable us to later implement Liferay’s permissions system.
These two variables track the portal instance and the community/organization, respectively. For example, say a user adds our portlet to a page in the Guest community of the default portal instance. Users then begin adding Products to the database. Because we’ve added the Company ID and the Group ID to the product entities, users can add our portlet to another community, organization, or portal instance and the set of records returned will be different based on the Company ID and Group ID. Our Finder methods filter the records being returned by Company ID and Group ID.
This is how Liferay allows you to place, for example, a Message Boards portlet in two different communities, and have completely different content in each. We’ll do the same thing as we write our portlet.
Once we have our object, it’s a simple matter to set the proper values in the object and then persist it to the database. We then return the object that we’ve created back to the layer that called this method in the first place.
But we’re not quite done yet.
Propagating your changes up the chain
Recall that changes to the interface/implementation contract get propagated up the chain automatically by Service Builder. We were just working in an implementation class, which extends another class, which we call a -BaseImpl class. All of the methods generated by Service Builder have entries in their Interfaces and actual implementations in their BaseImpl class. If developers wish to add more, they’re added in the -LocalServiceImpl class.
Because we’ve just added a new method to a class that implements an Interface, there is no method stub in the Interface for the method we just created. To continue without errors, we need to run Service Builder again. This can be done by running the build-service Ant task. When you run the task, Service Builder will regenerate the interface, which is called PRProductLocalService, to include a stub for the method you created in the implementation class.
This is generally the point where people scratch their heads and say, “Isn’t this backwards? Aren’t you supposed to define the Interface first and then write the implementation?”
In a sense, yes, that’s correct. But, remember that we’re working with a code generator, and it’s that code generator’s job to make things easier for us as developers. By using Service Builder, we are defining the Interface first, as much as we can do that up front. We did this in one step, when we defined our database table in service.xml. Service Builder generated our Interface as well as our default implementation—as best as it could guess at what we needed. That’s what went in PRProductLocalServiceBaseImpl.java. If you need further customization—and you generally do—you can add your own methods. These need to be added to a class that is free of the code generator, so that your changes don’t get overwritten. So PRProductLocalServiceImpl.java is provided as a DTO layer, which allows you to add any methods you may need.
You’ll generally only need to make customizations in one class: the -LocalServiceImpl class. In some cases, you may need to make customizations in -Impl classes in the model.impl package. The only thing we need to worry about right now is the -LocalServiceImpl class.
Now that we’re done with adding products, let’s try deleting products.
Deleting Products
If you think about how you might want to delete products, there are two ways:
- You already have a Product object, and you just want to delete it.
- You have a Product’s primary key, and you want to delete it that way without having to retrieve the whole Product.
To make things easier for us in the Controller layer, we’ll overload the delete method by creating versions of it that can handle both cases:
public void deleteProduct(long productId, long companyId) throws NoSuchProductException, SystemException, PortalException { PRProduct product = prProductPersistence.findByPrimaryKey(productId); deleteProduct(product, companyId); } public void deleteProduct(Product product, long companyId) throws PortalException, SystemException { resourceLocalService.deleteResource( companyId, Product.class.getName(), ResourceConstants.SCOPE_INDIVIDUAL, product.getPrimaryKey()); productPersistence.remove(product); }
What we’ve done here is enable us to delete a PRProduct using its primary key or the object itself. If we’re using the primary key, we’ll first retrieve the PRProduct object and then call deleteProduct on it. Note that we delete the resource as well as deleting the product. This keeps Liferay’s permissions tables free of “dead” data that might otherwise be left there simply taking up space. When you’re finished modifying your -Impl class, run Service Builder again using ant build-service. As before, this adds your new methods to the interface.
The only thing remaining to do is to query the database for the products we’ve entered because, presumably, users will want to view them and/or edit them.
Querying the database
Just like adding and deleting, getting products out of the database is really easy. You’ll probably remember that we had Service Builder generate a finder for this, which queries the database for products by the GroupId. We always want to query by GroupId because our portlet is non-instanceable. So we’ll implement this in our DTO layer like this:
public List<PRProduct> getAllProducts(long groupId) throws SystemException { List<Product> products = prProductPersistence.findByGroupId(groupId); return products; }
This will return a List of PRProducts, which can be used in the UI layer to display products for viewing or editing purposes. And, as you can see, whatever calls this method (it will be our portlet class) does not need to know anything about JDBC or databases. It’s simply requesting a List. This frees you to do something like swap out Service Builder later, if you find that it doesn’t meet your needs.
Summary
Service Builder makes it easy to generate a whole persistence layer of an application. Using well-known design patterns such as Data Access Objects and Data Transfer Objects, it not only implements a consistent design but also helps the developer to do so. Finders are automatically generated, which allow developers to access their data as Java objects.