
Service Builder
The official documentation for Service Builder is at the Liferay Developer Network: https://dev.liferay.com/develop/tutorials/-/knowledge_base/6-2/service-builder
Introduction #
The Service Builder is a tool built by Liferay to automate the creation of interfaces and classes that are used by a given portal or portlet. This includes code for EJBs, Spring, Persistence, and Model.
The input to the Service Builder is an XML file, typically /ext/ext-ejb/service.xml. For a complete description of the service.xml syntax refer to the well documented DTD at http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd
Value Object Model Classes #
Since 4.2, the Service Builder has changed the way value object model classes are generated. Now, interfaces for all model classes are placed into the portal-service.jar. Why would you want to do this? So that the portal-service.jar can be made accessible by WARs that are deployed outside of the classpath of the main code base.
Take for instance the Contact family of classes:
All the classes are generated by Service Builder and should not be modified, with the exception of ContactImpl. Let's look at what each of these classes does.
com.liferay.portal.model.BaseModel #
This interface is the base interface for all model classes found in Liferay. The only methods defined in this interface identify whether or not this value object model is new. Ultimately, the persistent layer uses this method to determine whether to update or add a new entry in the persistence.
com.liferay.portal.model.impl.BaseModelImpl #
This abstract class simply implements BaseModel.
com.liferay.portal.model.ContactModel #
This interface provides a representation of a particular entry in the persistence. For example, there can be one ContactModel representing Joe Bloggs and another representing John Smith.
com.liferay.portal.model.impl.ContactModelImpl #
This class has the implementation of the methods defined in ContactModel. Note that there is no direct connection to one another.
com.liferay.portal.model.Contact #
This interface describes extra helper methods that a developer may find useful when dealing with a given contact. Hence, the developer may want to get the first name or last name of the contact. Note that these entries are not persisted in the Contact_ table but are simply helper methods. This is the value object model that most developers will see -- both returned by the various services and accessible in a separate WAR.
com.liferay.portal.model.impl.ContactImpl #
It is here that the developer actually implements the helper methods found in the Contact interface. What is really happening when a helper method is called?
public static String getFullName( String firstName, String middleName, String lastName) {
if (Validator.isNull(middleName)) { return firstName + StringPool.SPACE + lastName; } else { return firstName + StringPool.SPACE + middleName + StringPool.SPACE + lastName; } }
public String getFullName() { return getFullName(getFirstName(), getMiddleName(), getLastName()); }
The getFullName() helper method is actually combining the first name, middle name, and last name of a given contact to form a full name.
So, how do you actually get these method signatures defined in the Contact interface if you are never to edit that file? Service Builder. You simply add the new method to the ContactImpl class, run the build-service target for the corresponding service.xml file and the Service Builder will propagate all the needed signatures.
Service Classes #
The class diagram of the MODEL, PERSISTENCE & SERVICE
Persistence Classes #
Create your own service.xml #
The input to the Service Builder is an XML file, typically /ext/ext-ejb/service.xml. For a complete description of the service.xml syntax refer to the well documented DTD at http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd
Create a column with no default datatype #
If you look at portal/portal-ejb/classes/com/liferay/portal/service.xml you'll find many examples like this one
<column name="groups" type="Collection" entity="Group" mapping-table="Groups_Orgs" />
from the entity "Organization".
If you'd like to create your own service.xml (e.g. ext/ext-ejb/service.xml) don't reuse this line as it is within your own entity. You'll get this output
[java] java.lang.ArrayIndexOutOfBoundsException: -1 [java] at java.util.ArrayList.get(ArrayList.java:323) [java] at com.liferay.portal.tools.ServiceBuilder.getEntity(ServiceBuilder.java:713) [java] at com.liferay.portal.tools.ServiceBuilder._createPersistence(ServiceBuilder.java:2283) [java] at com.liferay.portal.tools.ServiceBuilder.<init>(ServiceBuilder.java:646) [java] at com.liferay.portal.tools.ServiceBuilder.<init>(ServiceBuilder.java:330) [java] at com.liferay.portal.tools.ServiceBuilder.main(ServiceBuilder.java:86)
while running 'ant service-build' and your entity won't be created. The reason is, that the Service-Builder will look for each entity, that does not contain a '.' within it's name, within your 'service.xml'. The entity Group is specified within 'portal/portal-ejb/classes/com/liferay/portal/service.xml', so you have to set up the full qualified name of 'Group' within the entity attribute.
<column name="groups" type="Collection" entity="com.liferay.portal.Group" mapping-table="Groups_Orgs" />
The full qualified name is not the full qualified name of the generated interface
com.liferay.portal.model.Group
It is the path, where the Service-Builder will look after the service.xml.
com.liferay.portal.Group
will be resolved to relativ path
src/com/liferay/portal/service.xml
The Service-Builder assumes, that within this service.xml, the entity Group is declared. If you get an output like this
[java] java.lang.NullPointerException [java] at java.io.Reader.<init>(Reader.java:61) [java] at java.io.InputStreamReader.<init>(InputStreamReader.java:55) [java] at com.liferay.util.StringUtil.read(StringUtil.java:320) [java] at com.liferay.util.StringUtil.read(StringUtil.java:316) [java] at com.liferay.portal.tools.ServiceBuilder.getEntity(ServiceBuilder.java:731) [java] at com.liferay.portal.tools.ServiceBuilder._createPersistence(ServiceBuilder.java:2283) [java] at com.liferay.portal.tools.ServiceBuilder.<init>(ServiceBuilder.java:646) [java] at com.liferay.portal.tools.ServiceBuilder.<init>(ServiceBuilder.java:330) [java] at com.liferay.portal.tools.ServiceBuilder.main(ServiceBuilder.java:86)
while running ant build-service, the service.xml could not be found in the path you specified.
Permissions Filtering #
Finder methods are created automatically but these will return all the results. FilterFindBy method can also be generated under certain conditions and they will return the results filtered by permissions. The conditions are:
- the entity has a simple primitive pk
- the entity has permission checke registered in resource-actions/....xml
- the entity has user id an group id fields
- the finder method has group id in it