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 ]
Using Liferay’s Service Builder, part 1
If you’re an experienced developer, it’s likely that you’ve dealt with a data-driven application before. Liferay ships with a tool called Service Builder, which makes the creation of data-driven applications very easy. I highly recommend that you use Service Builder when writing applications on Liferay’s platform because it will get you going very quickly. How? By generating a lot of the database plumbing code for you so you can concentrate on your application’s functionality.
A database-persistence code generator
With Service Builder, you can simply define a database table in an XML file. Based on that definition, Service Builder then generates the entire Hibernate configuration, the entire Spring configuration, finder methods, your model layer, the SQL to create the table on all leading databases, and your entire Data Access Object layer at one fell swoop. Figure 1 illustrates this.
Did you know?
Service Builder is a proven tool that produces code suitable for enterprise deployments. In fact, all of Liferay’s internal database persistence is generated using Service Builder.
If you’re like me, I know what popped into your head as soon as I said “code generator.” It was, “Oh, no. Code generators are bad.” And then you began justifying that statement with many sound, accurate, and excellent arguments. Believe me, I agree with you. But Service Builder is different. From one code generator “hater” to another: Service Builder is designed to enable you to write custom code, not prevent it. It just takes care of the mundane stuff you hate writing, anyway. That is a code generator I’m on board with. I think you’ll like it, too.
We’re going to create a product registration application for a fictional company named Inkwell. This application is designed to replace those warranty registration cards that ship with electronic equipment. You’ll use Service Builder to generate the database tables as well as database persistence. To start, you’ll create that one XML file that is the key to generating the rest of the code.
Creating the service.xml file
Create a file called service.xml in the WEB-INF folder of your project. This file will contain your table definitions. You’ll populate this file with all the information Service Builder needs to generate the SQL for the table—for all the databases Liferay supports as well as database persistence objects you can use in your Java code.
We’ll use the simplest of the tables as our example, which is the Product table. The following listing defines the table for Service Builder.
Listing 1 Defining the Product table using Service Builder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd"> <service-builder package-path="com.inkwell.internet.productregistration"> 1 <author>Rich Sezov</author> 2 <namespace>PR</namespace> 3 <entity name="PRProduct" local-service="true" remote-service="false"> 4 <column name="productId" type="long" primary="true" /> 5 <column name="productName" type="String" /> 6 <column name="serialNumber" type="String" /> <column name="companyId" type="long" /> 7 <column name="groupId" type="long" /> <order by="asc"> 8 <order-column name="productName" /> </order> <finder name="G_PN" return-type="Collection"> 9 <finder-column name="groupId" /> <finder-column name="productName" /> </finder> <finder name="GroupId" return-type="Collection"> <finder-column name="groupId" /> </finder> <finder name="CompanyId" return-type="Collection"> <finder-column name="companyId" /> </finder> </entity> </service-builder> #1 Java package for code #2 Author for JavaDoc #3 Namespace for tables #4 Name of entity #5 Primary key #6 Additional fields #7 Foreign Keys #8 Order data is returned #9 Finder methods
This file is pretty easy to read, so we’ll attack it section by section instead of line by line.
DEFINE JAVA PACKAGE
You can define the Java package (#1) into which Service Builder generates the code that powers this database table. Service Builder also generates JavaDoc, and the name you place in the Author tags (#2) will wind up in the JavaDoc as the author of the code. By default, tables you define with Service Builder will go in the Liferay database. To set them apart from the rest of Liferay’s tables, you can prefix them with a namespace (#3). This table, when created, will actually be called PR_PRProducts in the database.
Now we get to the cool stuff. The database entity—which, for us, is a Product—is defined using the Entity tag (#4). The two parameters in this tag define how you want Service Builder to generate the service that retrieves and saves these entities. At minimum, you need to have a local service, but you can also have a remote service. This is not an EJB. It is instead a web service, complete with a WSDL document describing it so that your service may participate as part of a Services Oriented Architecture (SOA).
Next, we define the columns and finder methods. Our first column (#5) is defined as a primary key in the database. Other fields we want to store come next (#6). We are also defining two foreign keys (#7): a companyId and a groupId. The DBA team did not specify these two foreign key fields in the tables, but we added them anyway. These fields are internal to Liferay and are used for context purposes in non-instanceable portlets. The companyId corresponds to the portal to which the user has navigated, and the groupId corresponds to the community or organization to which the user has navigated. Since we will be using these field values as parameters in all of our queries, our portlet will have different data in different portals, communities, and organizations.
Without reading the next sentence, answer this question: Is our portlet instanceable or non-instanceable? The portlet is non-instanceable because we’ll be using these fields to make sure that the portlet ties all data to the portal and to the community or organization upon which the portlet is placed.
#8 defines a default ordering of the entities when they are retrieved from the database. You can choose to have them returned in ascending or descending order. You aren’t limited to one column here; you can specify multiple columns, and the ordering will happen by priority in order of the columns.
DEFINE FINDER METHODS
The finder methods (#9) actually go and retrieve the objects. Specifying these finders means that Service Builder will automatically generate methods that retrieve objects from the database using the parameters you specify in the finder. For example, the first finder returns Products by groupId and productName.
Now that you’ve defined your table, it’s time to run Service Builder.
Running Service Builder
Save the service.xml file and then run the Ant task called build-service. You’ll see several files get generated and the task will complete with a BUILD SUCCESSFUL message. If you’re using an IDE that compiles source files automatically, you’ll notice errors suddenly appear in your project. Don’t worry about this; it’s easy to fix.
What has happened is that Service Builder generated several Java source files. Some of them are in your existing src folder. Others are in a new source folder called service that it also generated. This folder will resideinside of WEB-INF like the src folder you already have. To fix the errors in your project, just use your IDE’s facility to add another source folder to your project. You may also need to refresh your project. Once you do that, the errors will go away.
Service Builder divides the source it generates into two layers:
- An interface layer gets generated in the aforementioned service folder. You’ll never change anything in the interface layer manually; Service Builder always generates the code that’s found there.
- An implementation layer gets generated in your src folder and is initially just skeleton code that allows you to implement the functionality you need.
You’ll notice that there’s also a new file in the root of your src folder called service.properties. This file was also generated by Service Builder. It contains properties that Service Builder needs at runtime to perform its functions. The most important of these properties is a list of Spring configuration files that were also generated.
Another new construct that was generated was a META-INF folder in your src folder. This folder contains all of the XML configuration files that Service Builder needs, including Spring configuration files and Hibernate configuration.
What all of this means is that Service Builder has taken care of your database persistence configuration for you. If you’ve ever used Hibernate alone or the combination of Hibernate and Spring, you know that there are multiple configuration files to juggle between the two. Service Builder automatically configures all of that for you and provides you with static classes that you can use to perform your database persistence functions. It provides both a Data Access Object (DAO) layer and a Data Transfer Object (DTO) layer for you automatically.
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 is not much different from what developers would already do manually, with the important exception that it does much of this “grunt work” automatically, freeing time for developers to implement their business logic.
In part 2 of this 2-part series, I’ll show you how to provide the functionality in your DTO to keep your portlet code from being dependent on anything related to SQL databases.