Blogs Entries, Journal Articles, and Forms Entries are just a few Assets supporting workflow. There’s nothing stopping you from likewise enabling workflow for your custom Assets. Discover here how the workflow framework works, and find the steps and code samples for enabling your custom entities to use the workflow capabilities in subsequent articles.
A workflow process is a set of steps that an Asset must proceed through before it’s marked with the workflow status Approved. The steps are defined in an XML file called a workflow definition. Each Asset is configured to run through a specific workflow definition via the Control Panel.
The workflow status is a database field that must be present for an entity to support workflow. If a database has the status field, but no workflow code has been written, it’s auto-marked Approved by Liferay’s Service Builder infrastructure, to assure that everything works smoothly by default.
There are several database fields that must be present for an Asset to support workflow:
int status represents the workflow status of each Asset.
long statusByUserId is the ID of the user that set the status (for example,
the initial User that hit the Submit for Publication button to add a new
String statusByUserName is the User Name of the User that set the status of
Date statusDate is the date/time when the status was set.
For Service Builder applications, add these as entity columns in the
service.xml file, run Service Builder, and you’re good to go.
Once the database table has the proper status fields, set them in your Entity’s
addEntity service method. Initially, set the status as a DRAFT. It’s what
the workflow framework expects of an entity as it enters the workflow process.
The status is an
int, but you don’t have to remember which number corresponds
to the DRAFT status. Instead, use the
portal-kernel. For a draft, pass in
If you’re curious, the
int represented by this constant is
important status, APPROVED, is represented by the
0 and the constant
The User fields (
statusByUserName) are easy, since the
userId of the User making the
addEntity request is part of the request
itself, and passed into the
addEntity method for most assets. Use the ID
directly as the
statusByUserId, and get the full name associated with the User
by using the ID to retrieve the
statusDate is usually best set as the date the entity was modified, and
is part of the Service Context in the request:
Once the status dates are set, the entity is ready to be sent into the workflow framework.
When an entity is added to the database, the application must detect whether
workflow is enabled. If not, it automatically marks the entity as approved so it
appears in the UI. Otherwise, it’s left in draft status and the workflow
back-end handles it. Thankfully, this whole process is easily done with a call
WorkflowHandlerRegistryUtil.startWorkflowInstance in your persistence code.
Once the entity is sent to the Workflow Framework, much of the process is
automated, and you need not worry about the details. Write one class that gives
the framework some information on how to process the entity. It’s called a
workflow handler (
WorkflowHandler<T>), and you can create it by extending the
handy abstract implementation,
The workflow handler usually goes in the module containing service implementations. It’s nice to keep your back-end code separate from your view layer and controller (ala the MVC pattern).
Make your workflow handler a Component class so it can be registered properly
with OSGi runtime. It requires one Component property,
is the fully qualified class name for class you pass as the type parameter in
the class declaration.
In addition to the property, declare the type of service you’re providing in the
Workflow handlers extending the
BaseWorkflowHandler must override three methods:
getClassName returns the model class’s fully qualified class name
com.my.app.package.model.FooEntity, for example).
getType returns the model resource name
model.resource.com.my.app.package.model.FooEntity, for example).
updateStatus does most of the heavy lifting here. It returns a call to a local
service method of the same name (for example,
FooEntityLocalService.updateStatus), so the status returned from the workflow
back-end can be persisted to the entity table in the database. The
updateStatus method needs a user ID, the primary key for the class (for
fooEntityId), the workflow status, the service context, and the
workflow context. The status and the workflow context can be obtained from the
workflow back-end. The other parameters can be obtained from the workflow
The service layer must update the status of the entity when it returns from the
Workflow Framework. Make an
updateStatus method for this purpose, and make
sure, at a minimum, to set the status fields again as the Asset comes out of the
Workflow Framework, and call the persistence layer’s
After that, provide any additional logic you might want, like checking the
status and updating the Asset’s visibility (using the
based on the condition (visible if Approved, not visible is any other status).
Return the entry once you’re through here.
When you send an entity to the workflow framework via the
startWorkflowInstance call, it creates an entry in the
database table. In your service layer’s deletion logic, you must delete the
workflow instance links. This
delete call ensures there are no orphaned
entries in the
To get the
WorkflowInstanceLocalService injected into your
*LocalServiceBaseImpl so you can call its methods in the
reference entity to your entity declaration in
After you finish all the backend work, update your UI. Some common tasks here include:
In any public facing portion of the application (accessible to guest Users), don’t display the entity if the status is anything except Approved. This task requires the creation of an additional
findermethod that accounts for workflow status, and a corresponding
getterto expose it in the service layer.
In administrative portions of the application, display the entities, but also display their workflow status. There’s a tag library for this.
See the next article for more concrete steps and code snippets.