By Pedro Hugo do Nascimento Gabriel, Diogo Franqueira Henriques, and Danilo Alexandre Manmohanlal
While developing several user interfaces for Human Tasks in Oracle Business Process Management (BPM) projects, we realized the necessity of creating a mechanism that would allow us to automate the process and reduce development time.
The visual and declarative experience of ADF allows us to create these Human Tasks in two ways: by using the out-of-the-box options to auto-generate the Human Task form, or by manually creating UI pages as each Human Task form is designed. The first approach generates as many separate projects as there are Human Tasks; the latter creates as many user interface pages as there are Human Task forms.
Since the majority of our interfaces for Human Tasks are similar, our approach to this issue was to develop an ADF taskflow library that contains a taskflow that we can drag and drop to our pages and render the Human Task based on input parameters.
We also implemented a Java library that creates an abstraction layer of the BPM interactions. To this library we added some processing of the Human Task payloads, and provide basic caching of services and user contexts. We have found that having a single interface can decrease the development time of new processes by at least 85.7%. This article demonstrates how we implemented our Human Task interface generator for major customers in the Retail, Finance and Energy sectors.
Introduction
The projects that we have developed in Oracle BPM have plenty of interaction because of the use of Human Tasks; the options we have for development can involve massive complexity when we have several projects, or several user interface pages, to manage.
Develop once, reuse often has always been our motto when developing with Oracle ADF. We focused our centralizing efforts on a single interface, Human Task Generator (HTG), on which we can:
- Display whichever Application Development Framework (ADF) Faces that components offer, with dependencies between them
- Process the Human Task with custom or system actions
- Enable or disable user interaction based on permissions
- Display process information and help topics so the user has context
To achieve this goal, we developed our HTG based on a custom library (BpmLib) that abstracts BPM's middleware Java classes. In our BpmLib we expose all the necessary methods for getting Human Task payload, replying actions, etc.
In the next sections of this article, we will detail how we have implemented our HTG as well as the BPM library.
Architecture
Our HTG was engineered based on the convention that it should be reusable and extensible, so that if our client's requirements and the application target change, we could easily adjust to new requests. We divided our implementation in three layers, Data, Business and View, as shown in Figure 1.
**
**
Figure 1: HTG Architecture
The Data Layer encloses two levels of information: BPM's data in which the BPM Suite resides, and the custom-made data model that supports the customers' business logic. In the latter case, the amount and type of information depends on each customer; for example, for some clients we do not need to implement any type of data logic, while for others we have a fairly complex database model.
The Business Layer clusters and abstracts the features and requirements of the HTG. Our BpmLib Library collects the data for each process and its related Human Tasks through BPM's middleware API, exposing it to be consumed by the Model and/or Application Module applications, as shown in Figure 1. In no case do we bypass BPM's middleware API to directly access BPM's data in the data base. By following this rule, we protect our code from any change that may occur in BPM's data structure in future releases of Oracle's BPM product. The Model application exposes data through View Objects. The Application Module application clusters and exposes data. Here, single methods are also made available to the View Layer.
The View Layer consumes the methods and View Objects exposed by the Business Layer and displays data through the HTG application. HTG can be reused by different types of environments (e.g., ADF applications, ADF Mobile Browser applications, and Mobile Application Framework (MAF) applications). Choose the appropriate HTG interface depending on the application target, each of which is an application deployed as a shared library to allow isolation and reusability. This has two advantages: availability to be deployed as a shared library to any WebLogic Server (WLS) and incorporation into any final ADF application.
The HTGs for ADF, Mobile Browser and MAF have much in common-all of them rely on the same layers of data. Nevertheless, each one has its own technology particularities and specific approaches to be followed. Although we haven't yet developed the HTGs for Mobile Browser and MAF, we've designed our HTG architecture and technical choices with their eventual development in mind.
Custom BPM Library - BpmLib
The BpmLib component is a Java library responsible for all calls to the BPM API. It returns our custom data types, creating an abstraction layer over the BPM API, and also caches and pre-processes data for generating the ADF interface.
Creating an abstraction layer has all the normal advantages, and specifically helps us expose custom methods. The API is very powerful, allowing all kinds of interactions with the underlying engines. But since we need only a subset of those functionalities, there are many options in the API that we don't need. We use the BpmLib to encapsulate those options (e.g., when querying processes and tasks, the API allows us to define which columns we want to be returned) and return the same columns. Another example is the use of flex fields on human tasks. Since we return our own custom-made classes, we include in them the business data that's stored in the flex fields so that the upper layers don't have to be concerned with the origin of the data. Using our own classes also allows us to flatten the class structure, simplifying access to the other fields.
Because instantiating the service objects of the API is a heavy operation, we cache those objects. However, this raises a problem: this library is running on a different managed server from the SOA and BPM Suite. When the SOA managed server is restarted, the connection becomes invalid, and all calls to the API throw a javax.ejb.EJBException
. We had to include a mechanism to catch those exceptions and invalidate the service cache.
The security contexts of the users is also cached data. Users are authenticated by the ADF infrastructure, but with that information we then need to create the security contexts that the BPM API uses. Since this creation is also a heavy operation, we cache these contexts. The cache is a simple collection that stores the contexts of already authenticated users, each entry also associated with a timestamp. Whenever there's a cache access, the timestamp is checked, and if a certain amount of time has passed since the creation of the entry, then that entry is deleted and we create a new context. This way we allow new security definitions, like group and role membership changes, to be picked up. In future improvements, we may implement a fuller cache or explore other available caching solutions.
Regarding the automatic generation of the Human Tasks interface, this library is responsible for fetching and pre-processing the payload of the tasks so that ADF layers can generate the interface.
On the BPM side, we define the task payload using the base XSD types (like string and date) as well as other custom types, ranging from generic types-like text (to indicate to the interface generator that the field should be rendered as a text area)-to other, more business-specific types-like account, which the generator must know how to render. This also includes types that have to be rendered as several controls. These type of definitions are deployed to MDS, allowing us to share them across processes. So, as we implement more and more processes, the number of new types for each new process rapidly decreases, allowing us to reduce implementation complexity and time.
On the BpmLib, we created our own Java class representing a payload item, containing the name of the item (a key to a resource file), its value, type, if it is editable or read only, and if it is visible. Lastly, we expose two methods:
getTaskPayload
: returns a list of payload items for a given tasknumber
updateTaskPayload
: updates the task payload of the task for a tasknumber and a Map<String, Object>
GetTaskPayload
Method
The getTaskPayload
method starts by calling the following code:
ITaskMetadataService.getTaskDefinition(context,task).getWorkflowConfiguration().getPayload().getMessageAttribute();
It returns a list of MessageAttributeType
. Each MessageAttributeType
represents one entry of the task payload that we see on the .task
file. While this gives us some information (name, type, and whether it's updatable), we also need to get the payload's actual schema, which is available on the MDS; there are several ways to access it. We used the following URL:
[http://[host]:[port]/soa-infra/services/[partition]/[composite]/[path_to_file]/[name_of_task]Payload.xsd](/[host][port])
We can feed this url to the XSDBuilder...
XMLSchema schema = new XSDBuilder().build(url);
...and get an object representing the payload. From here, it is just a matter of iterating the MessageAttributeType
list, getting for each item the corresponding XMLElement
of the payload, and building our own PayloadItem
object to return.
UpdateTaskPayload
The updateTaskPayload
method receives the values to update in a Map<String, Object> where the String (key) is the name of the payload item, and the Object (value) is its value. The actual class of the Object depends on the type of the payload item. For simple types, it's the closest equivalent in Java to the type (e.g., a String, an Integer, or a Date). For complex types, it's a String for which the XML value needs to be stored. It starts by calling the Task.getPayloadAsElement()
method to get an XMLElement
with the current payload. Then it also gets the XMLSchema
in the same way as the getTaskPayload
. This is needed in order to know the type of each payload item. It then updates the XMLElement
with the values passed in the parameter, and calls the ITaskService().updateTask(context, task)
method to save the changes.
HTG Transformation Model Engine
The core of the engine where Human Task payloads are transformed is located at the Application Module in the Business Layer, as depicted in Figure 1. Here, we expose the getHumanTaskPayload method as a class available for clients.
We established our core engine here for the following reasons:
- The getHumanTaskPayload method is exposed in the application module and therefore can be invoked independently of the targeted platform (ADF, ADF Mobile Browser or MAF)
- Deployed as shared library to any Weblogic Server
- Changes made in this module don’t affect the upper layer and can be deployed as demanded since the contract is not broken
- Proximity with BpmLib
During payload transformation we are able to support two types of fields:
- Simple: Single ADF components such as input text, select one choice, or input date, etc.
- Complex: Custom-created components that encapsulate two or more ADF components--for example, a search field that combines an input text and a button, or a custom address component as depicted in Figure 2, where we have an arrangement of several components into a single custom address component.
**
**
Figure 2 - Address Complex Type
Our HTG transformation model engine is structured on Java classes to represent Human Taks attributes. The Java classes we have created for that purpose are: HumanTaskAttribute
, HumanTaskAttributeFacade
and HumanTaskPayload
. Their relationship can be viewed in Figure 3.
**
**
Figure 3 - HTG transformation model Engine Java Classes
The HumanTaskAttribute
Java class represents each attribute of the Human Task payload regardless of being based on a primitive type, simple type or even complex type. In those cases in which the attribute is a complex type, each XSD element is represented as a HumanTaskAttribute
. The variables we have defined for this Java class are:
name
- internal attribute name. If the attribute is a complex type, the name is the concatenation of Human Task's attribute name plus the name of the element inside the XSD.
value
- the value at which the attribute is to be set.
type
- contains the type of the attribute, i.e. STRING for string attributes, INTEGER for integer attributes, etc. For complex types, the type is similar to XSD name in order to be easily recognized (i.e., ADDRESS for attributes based on address XSD).
isReadOnly
- true if the attribute is not editable, otherwise false.
isRequired
- true if the attribute is mandatory, otherwise false.
isHidden
- true if the attribute must not be displayed on the interface, otherwise false. This type of attributes behaves like auxiliary variables.
isVisible
- true if the attribute should be visible, otherwise false. This variable is useful for setting conditional visibility of the attributes on the interface.
The HumanTaskAttributeFacade
Java class encloses all attributes of each Human Task payload's attribute, regardless of the type (primitive, simple or complex). If the attribute is based on a complex type, this Java class has as many HumanTaskAttributes
as elements in the XSD. If the attribute is based on primitive or simple attributes, this class has only one HumanTaskAttribute
attribute. This situation can be viewed in Figure 3. The variables we have defined for this Java class are:
- type – contains the same value as the attribute type in the
HumanTaskAttribute
Java class. This variable exists only for code simplicity reasons on later data access.
- attributes – represents the list of
HumanTaskAttributes
(List<HumanTaskAttribute>
).
For this same Java class we have defined four methods:
getValue
- get Human Task's attribute value based on attribute's internal name. If internal name doesn't exist, a NULL is returned.
setValue
- set the new value for attribute's internal name.
hasKey
- returns true if Human Task's internal name exists, otherwise returns false.
getValues
- get all Human Task payload attributes. The returning value is a java.util.Map
, where the Key contains the attribute's internal name and the Value the value of the attribute.
Regarding HumanTaskPayload
, this Java class encloses all human task payload attributes regardless of its attribute's complexity (primitive, simple or complex type). It has the following variable:
humanTaskPayloadItems
- gathers all data of HumanTask payload attributes
For this same Java class we have defined three methods, based on HumanTaskAttribute
's methods:
getValues
- gets all human task payload attributes. The returning value is a java.util.Map where the Key contains the attribute's internal name and the Value the value of the attribute.
getValue
- gets HumanTask's attribute value based on attribute's internal name. If internal name doesn't exist, a NULL is returned.
setValue
- sets the new value for attribute's internal name.
In addition to previous Java classes, we have two more with no less importance: HumanTaskUtilities
and HumanTaskGeneratorAMImpl
. The first one is a utility class that helps parse the XSDs' complex types; the second is the implementation class of the "HumanTaskGenerator
" application module where the "getHumanTaskPayload
" method is exposed and returns all HumanTask payload attributes after transformation. The Java class HumanTaskUtilities
has the following methods to perform the transformation:
isComplexType
– true if the attribute is complex (i.e., attribute based on a XSD), otherwise false.
typeHasDropDownValues
– true if the attribute is based on a XSD and one of its elements’ name is prefixed with "ddl," otherwise false.
The current attribute's type is determined during iteration overHumanTask payload attributes returned by BpmLib. Depending on the type, one of the following methods is called:
addSimpleTypeAttribute
– If the attribute is simple or primitive, this method is called. In this method, the variables of HumanTaskAttribute
and HumanTaskAttributeFacade
Java classes are set and added to the top-level class, HumanTaskPayload.
addComplexTypeAttribute
– If the attribute is complex, this method is called. In this method, we strip the HumanTask attribute's XSDs in order to get its elements, set HumanTaskAttribute
and HumanTaskAttributeFacade
Java classes variables, and add them to the top level class, HumanTaskPayload
.
- addDropDownValuesToAttribute – If the attribute is complex and the current XSD element being parsed has the prefix "ddl," this method is called. In this method, an extra
HumanTaskAttribute
entry on the overall structure (see Figure 3, above) is created in order to store the list of items for the control.
HTG Declarative Components
Custom declarative components allow us to build custom components from existing JSF components. They play a major role in showing the same type of data in a desirable layout. Regarding HTG, the declarative components are important for Complex type fields available in human task payloads. By creating this type of component, we can:
- Make use of the primitive ADF Faces components
- Define custom behavior logic for each new declarative component (e.g., during value change events, etc., of the primitive ADF Faces components)
- Create new declarative components as needed, without interfering with the others
- Deploy as shared library to any Weblogic Server
The universe of declarative components already developed for a generic target audience is depicted in Error! Reference source not found, below.
| Declarative Component | Image |
| Address |
|
| BetweenDates |
|
| SearchModal |
|
Our experience tells us that many other declarative components can be developed with different levels of complexity, depending on each customer's data display needs.
HTG Generic Task Attributes Binding
Our HTG is a taskflow that generates, at runtime, a custom interface for HumanTasks. It is organized into three regions:
- global process information regarding the current task
- payload for the HumanTask
- comments or attachments.
Inside our HTG, the payload is encapsulated in another taskflow. To use, we just need to drag and drop to our jsf page,which encapsulates all logic defined for the Human Tasks and is responsible for component generation at runtime. The following diagram represents the execution flow performed in our HTG taskflow.

Our HTG module is defined by a set of input parameters. These input parameters are used to determine which Human Task to show. Pre-processing is then done in order to get the Human Task payload based on our custom made BpmLib library and determine if the user that is trying to acquire the task has appropriate permissions. After this two steps are completed, each Human Task attribute is rendered according to its type.
The generated Human Tasks have system and custom actions. System actions are equal for all tasks and are those available in BPM Workspace. Custom actions are defined for each task during process development.
Regarding the last step of our execution flow, Runtime Component Design, we have developed a task flow for rendering the Human Task attributes. The task flow is implemented as depicted in Figure 4.
**
**
Figure 4 - HTG Payload Task Flow
It all starts by getting Human Task payload attributes with previous transformations already done. Then it's time to set attribute visibility, which is related to an attribute's conditional visibility-that is, if the visibility of any attribute is based on another attribute's value, then it is defined at this time. The next step is about getting the values to populate the selectOneChoice
ADF Faces component. Last but not least, the HTGPayload
fragment is responsible for rendering all Human Task attributes from any possible Human Task, as shown in the following snippet of code.
<?xml version='1.0' encoding='UTF-8'?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:af="http://xmlns.oracle.com/adf/faces/rich" xmlns:f="http://java.sun.com/jsf/core" xmlns:htgDC="/htgDC"> <af:panelGroupLayout id="pgl1"> <af:forEach items="#{pageFlowScope.HTGPayload.humanTaskPayloadItems}" var="item"> <af:panelLabelAndMessage id="plm_Text" label="#{item['attributes'][0]['label']}:" visible="#{item['attributes'][0]['isVisible']}" rendered=" #{item['type'] == pageFlowScope.HumanTaskUtilities.textType and item['attributes'][0]['isHidden'] == false and item['attributes'][0]['isReadOnly'] == false and pageFlowScope.HTGPayload.payloadReadonly == false}" showRequired="#{item['attributes'][0]['isRequired']}" labelStyle="width:210px !important; vertical-align:top"> <af:inputText id="it_Text" simple="true" autoSubmit="true" value="#{item['attributes'][0]['value']}" label="#{item['attributes'][0]['label']}:" readOnly="#{item['attributes'][0]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadOnly}" required="#{item['attributes'][0]['isRequired']}" visible="#{item['attributes'][0]['isVisible']}"> <f:attribute name="humanTaskAttributeName" value="#{item['attributes'][0]['name']}"/> </af:inputText> </af:panelLabelAndMessage> <af:panelLabelAndMessage id="plm_Decimal" label="#{item['attributes'][0]['label']}:" visible="#{item['attributes'][0]['isVisible']}" rendered=" #{item['type'] == pageFlowScope.HumanTaskUtilities.decimalType and item['attributes'][0]['isHidden'] == false}" showRequired="#{item['attributes'][0]['isRequired']}" labelStyle="width:100px !important"> <af:inputText id="it_Decimal" simple="true" autoSubmit="true" value="#{item['attributes'][0]['value']}" label="#{item['attributes'][0]['label']}" readOnly="#{item['attributes'][0]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadonly}" required="#{item['attributes'][0]['isRequired']}" converter="ConverterBigDecimal" validator="#{ValidatorsHTG.validateDecimalField}"> <f:attribute name="humanTaskAttributeName" value="#{item['attributes'][0]['name']}"/> </af:inputText> </af:panelLabelAndMessage> <af:panelLabelAndMessage id="plm_Date" label="#{item['attributes'][0]['label']}:" visible="#{item['attributes'][0]['isVisible']}" rendered=" #{item['type'] == pageFlowScope.HumanTaskUtilities.dateType and item['attributes'][0]['isHidden'] == false}" showRequired="#{item['attributes'][0]['isRequired']}" labelStyle="width:210px !important"> <af:inputDate id="id_Date" simple="true" autoSubmit="true" columns="10" value="#{item['attributes'][0]['value']}" label="#{item['attributes'][0]['label']}" readOnly="#{item['attributes'][0]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadonly}" required="#{item['attributes'][0]['isRequired']}" validator="#{ValidatorsHTG.validateDateField}" converter="javax.faces.DateTime"> <af:convertDateTime hintDate="DD-MM-AAAA"/> <f:attribute name="humanTaskAttributeName" value="#{item['attributes'][0]['name']}"/> </af:inputDate> </af:panelLabelAndMessage> <af:panelLabelAndMessage id="plm_Address" visible="#{item['attributes'][0]['isVisible']}" rendered=" #{item['type'] == pageFlowScope.HumanTaskUtilities.address and item['attributes'][0]['isHidden'] == false}" showRequired="#{item['attributes'][0]['isRequired']}"> <htgDC:Address id="adress_Address" countryLabel="Country:" countryValue="#{item['attributes'][0]['value']}" countryReadOnly="#{item['attributes'][0]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadonly}" countryRequired="#{item['attributes'][0]['isRequired']}" countrySelectItems="" addressLabel="Address:" addressValue="#{item['attributes'][1]['value']}" addressReadOnly="#{item['attributes'][1]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadonly}" addressRequired="#{item['attributes'][1]['isRequired']}" stateLabel="State:" stateValue="#{item['attributes'][2]['value']}" stateReadOnly="#{item['attributes'][2]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadonly}" stateRequired="#{item['attributes'][2]['isRequired']}" zipCodeLabel="Zip Code:" zipCodeValue="#{item['attributes'][3]['value']}" zipCodeReadOnly="#{item['attributes'][3]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadonly}" zipCodeRequired="#{item['attributes'][3]['isRequired']}" postalCodeLabel="Postal Code:" postalCodeValue="#{item['attributes'][4]['value']}" postalCodeReadOnly="#{item['attributes'][4]['isReadOnly'] || pageFlowScope.HTGPayload.payloadReadonly}" postalCodeRequired="#{item['attributes'][4]['isRequired']}" humanTaskAttributeName="#{item['attributes'][0]['name']}" panelLabelStyle="width:100px !important; vertical-align: top !important;"/> </af:panelLabelAndMessage> </af:forEach> </af:panelGroupLayout> </jsp:root>
Note: This task flow can be enhanced when you have an ADF Faces control and need to search information in modal in order to set Human Task attribute values.
In the HTGPayload
fragment, an iteration over all Human Task payload attributes is performed; depending on its type, it will be rendered by one of the underlying components available from the set of components inside the "af:foreach" ADF Faces component. Any new component needed should be put here.
Each component is populated based on the data structure previously presented to represent all Human Task payload attributes, regardless of complexity. For more details about how to assign component properties, you can find an example here.
HTG Generic Task Attribute Validation
There are three types of task validations:
- Simple Component level (e.g., input fields for numeric types only)
- Custom Component level (e.g., dependent fields in a custom component)
- Task submission level (e.g., submission of the task form with validation rules for individual components that cannot be filled)
Simple Component validation is defined in the "validator" component's property, as shown below:
<af:panelLabelAndMessage id="plm_Date"
label="#{item\['attributes'\]\[0\]\['label'\]}:"
visible="#{item\['attributes'\]\[0\]\['isVisible'\]}"
rendered="#{item\['type'\] == pageFlowScope.HumanTaskUtilities.dateType and
item['attributes'][0]['isHidden'] == false}"
showRequired="#{item\['attributes'\]\[0\]\['isRequired'\]}"
labelStyle="width:210px !important">
<af:inputDate id="id_Date"
simple="true"
autoSubmit="true"
columns="10"
value="#{item\['attributes'\]\[0\]\['value'\]}"
label="#{item\['attributes'\]\[0\]\['label'\]}"
readOnly="#{item\['attributes'\]\[0\]\['isReadOnly'\] ||
pageFlowScope.HTGPayload.payloadReadonly}"
required="#{item\['attributes'\]\[0\]\['isRequired'\]}"
validator="#{ValidatorsHTG.validateDateField}"
converter="javax.faces.DateTime">
\<af:convertDateTime hintDate="DD-MM-AAAA"/>
\<f:attribute name="humanTaskAttributeName" value="#{item\['attributes'\]\[0\]\['name'\]}"/>
</af:inputDate>
</af:panelLabelAndMessage>
To centralize all validations of this type, we have followed a recommended best practice in ADF by creating the "ValidatorsHTG" Java class. This class is instantiated with session scope in the "Managed Beans" tab of the faces-config.xml file. The code snippet below shows two component validations of two different types in the "ValidatorsHTG" Java class.
public class ValidatorsHTG {
public ValidatorsHTG() {
}
public void validateDecimalField(FacesContext facesContext,
UIComponent uIComponent,
Object object) {
if(object != null){
String tempStr = object.toString();
if(!Utils.isLong(tempStr))
throw new ValidatorException(
new FacesMessage("Validation Error", "Required a numeric value"));
else {
Long number = Long.valueOf(tempStr);
if(number \< 0)
throw new ValidatorException(
new FacesMessage("Validation Error", "Required a positive value"));
}
}
}
public void validateDateField(FacesContext facesContext,
UIComponent uIComponent,
Object object) {
if(object != null) {
long currentTime = System.currentTimeMillis();
java.util.Date tmp1 = (java.util.Date)object;
long inputTime = tmp1.getTime();
Object\[\] formatArgs = new Object\[1\];
formatArgs\[0\] = ((RichInputDate)uIComponent).getLabel();
if(inputTime > currentTime)
throw new ValidatorException(
new FacesMessage("Validation Error", "The date must be greater than the current date"));
}
}
}
Custom Component validation can be done in two ways: in the "validator" component property of the declarative component, basic validation, or in custom classes for more complex validation. For example, imagine that the Address declarative component is valid only if the "Country" field is filled as well as "Address" and "Postal Code"; if not, an error message is raised to the front end. For this kind of behavior, we have developed the "HTGComplexTypesValidation" Java class, where all generic validations will be available for future reuse. The next code snippet shows the Address complex-type validation:
public class HTGComplexTypesValidation extends HTGValidations {
public HTGComplexTypesValidation(HumanTaskPayload payloadValues,
String processName,
String humanTaskName,
String humanTaskOutcome) {
super(payloadValues, processName, humanTaskName, humanTaskOutcome);
}
public boolean validateAddressComplexType(List messages) {
boolean valid = true;
//address\_country - internal human task payload attribute
//address\_address - internal human task payload attribute
//address\_postalCode - internal human task payload attribute
Object country = payloadValues.getValue("address\_country");
Object address = payloadValues.getValue("address\_address");
Object address\_state = payloadValues.getValue("address\_postalCode");
if(Utils.isNullOrEmpty(country))
{
boolean validAttribute =
displayErrorMessage(messages, "The Country is required");
valid = valid && validAttribute;
}
else if (!Utils.isNullOrEmpty(country))
{
if (Utils.isNullOrEmpty(address))
{
boolean validAttribute =
displayErrorMessage(messages, "The Address is required");
valid = valid && validAttribute;
}
if (Utils.isNullOrEmpty(address\_state))
{
boolean validAttribute =
displayErrorMessage(messages, "The Postal Code is required");
valid = valid && validAttribute;
}
}
return valid;
}
}
This approach encloses component behavior and complexity in one place and thus promotes more and better reuse.
Finally, validation at task submission reuses complex-type validations as well as any other validation. The developer has full freedom and control to do any validation for any Human Task for any BPM process. The validations engine is structured as depicted in Figure 5, below, where "ProcessToValidateA" and "ProcessToValidateB" are Java classes of real BPM processes.
**
**
Figure 5 - HTG Validation Java Classes Architecture
HTGValidations is the top-level Java class called by the user just before replying to the human task. The Human Task is replied only if all attributes (simple or complex attributes) and overall dependencies between attributes are valid. The method "validateHumanTask" is the main method called to perform the validation of the Human Tasks process.
package htg.payload;
import htg.common.HumanTaskPayload;
import htg.view.ADFUtils;
import java.util.ArrayList;
import java.util.List;
public class HTGValidations {
protected HumanTaskPayload payloadValues;
protected String processName;
protected String humanTaskName;
protected String humanTaskOutcome;
public HTGValidations(HumanTaskPayload payloadValues,
String processName,
String humanTaskName,
String humanTaskOutcome) {
this.payloadValues = payloadValues;
this.processName = processName;
this.humanTaskName = humanTaskName;
this.humanTaskOutcome = humanTaskOutcome;
}
public boolean validateHumanTask() {
List messages = new ArrayList();
boolean result = true;
if (processName.equalsIgnoreCase("PROCESS\_NAME\_A"))
result = new ProcessToValidateA(payloadValues, processName, humanTaskName,
humanTaskOutcome).validateHumanTask(messages);
else if (processName.equalsIgnoreCase("PROCESS\_NAME\_B"))
result = new ProcessToValidateB( payloadValues, processName, humanTaskName,
humanTaskOutcome).validateHumanTask(messages);
//classes for new processes wih new human tasks should be introduced here
displayValidationErrorMessages(messages);
return result;
}
private void displayValidationErrorMessages(List messages) {
for (String msg : messages)
ADFUtils.displayInfoMessage(msg);
}
protected boolean displayErrorMessage(List messages,
String errorMessage) {
messages.add(errorMessage);
return false;
}
}
Human Task validation is made inside of each process Java class created for the purpose, as shown in the next code snippet. For example, inside "ProcessToValidateA" Java class, the Human Tasks for this process are validated.
public class ProcessToValidateA extends HTGValidations {
public ProcessToValidateA(HumanTaskPayload payloadValues,
String processName,
String humanTaskName,
String humanTaskOutcome) {
super(payloadValues, processName, humanTaskName, humanTaskOutcome);
}
public boolean validateHumanTask(List messages) {
boolean result = true;
if (humanTaskName.equalsIgnoreCase("HUMAN\_TASK\_A"))
result = validateHumanTaskA(messages);
else if (humanTaskName.equalsIgnoreCase("HUMAN\_TASK\_B"))
result = validateHumanTaskB(messages);
return result;
}
private boolean validateHumanTaskA(List messages) {
boolean valid = true;
HTGComplexTypesValidation complexValidation =
new HTGComplexTypesValidation(payloadValues,
processName,
humanTaskName,
humanTaskOutcome);
valid = complexValidation.validateAddressComplexType(messages);
//etc..
return valid;
}
private boolean validateHumanTaskB(List messages) {
boolean valid = true;
HTGComplexTypesValidation complexValidation =
new HTGComplexTypesValidation(payloadValues,
processName,
humanTaskName,
humanTaskOutcome);
valid = complexValidation.validateAddressComplexType(messages);
//etc..
return valid;
}
}
HTG Generic Task Attributes Conditional Visibility
There may be cases in which the visibility of certain Human Task attributes is based on another attribute's value. Conditional visibility has two setting points: one on entering the "htg-payload-flow.xml" task flow (Figure 4, above), and another in any value change listener of any ADF Faces component or declarative component.
Regarding the first setting point, before the Human Task is displayed, the attribute's conditional visibility must be checked. Why? Imagine the Human Task has already been edited and there is a dependency between attributes. In this case, when re-entering the Human Task it is necessary to gather the value of the attribute that causes the other attribute to change its visibility.
Human task conditional visibility is developed so that each Human Task of each process has its own conditional rules of visibility. This approach promotes flexibility to define attribute visibility.
Real-World Benefits
In this section, we try to answer some basic questions:
- How useful can our approach be in the development process of a new application?
- How reusable can it be?
- Is there a real-world benefit to spending more time at the beginning implementing a generic methodology as opposed to following the way Oracle developed its product?
(1) How useful can our approach be in the development process of a new application?
Since we developed our HTG in well-defined building layers (Data Layer, Business Layer and View Layer) where each one communicates with the nearest layer, they can be developed independently of the others.
Each project inside of each layer is available as a “Shared Library” and is therefore deployed independently to the WLS. Apart from the advantage described earlier, it is not necessary to deploy all the projects to the server if only one has really changed.
When a new application is ready to be developed independently of the business area, and needs to provide Human Tasks interfaces, the developer simply imports the library into the application and easily drags-and-drops the HTG wherever is needed. It behaves as a regular ADF Faces component.
The user will face a standardized interface for all Human Tasks independently of the process. The developer will have fewer interventions regarding layouts, forgotten fields, etc., and therefore is less likely to commit an error.
(2) How reusable can it be?
Implementing the HTG for three different customers in different business areas provided us with insight into the level of reusability of our engineered solution. Table 2, below, agglomerates the data by customer's business area, as well as the total number of processes and human tasks created using HTG versus Human Tasks created from scratch.
| | Retail | Finance | Energy | Total |
| # Processes | 36 | 9 | 3 | 48 |
| # Human Tasks | 96 | 34 | 5 | 135 |
| Human Task Generator (HTG) | 81 | 34 | 5 | 120 |
| Custom Human Tasks | 15 | 0 | 0 | 15 |
| Percentage of Reusability | 84.4% | 100% | 100% | 88.9% |
Table 2 - HTG Percentage of Reusability
This table depicts the success of using our solution. In the Finance industry, the HTG satisfies all customer needs. In the Retail sector, there is a high percentage of satisfaction, and in Energy customer needs are satisfied (but only a few small processes were implemented).
One of the leading reasons for these results is our ability to evolve Complex Component Types as the customer needs them, with the underlying validations for each component and the overall human task. The modularization of our HTG in different layers was another advantage, since we never mixed business logic inside viewing data, and vice versa.
(3) Is there a real-world benefit to spending more time at the beginning implementing a generic methodology as opposed to following the way Oracle developed its product.
All our customers wanted their own custom-made workspace, integrated within a single application, with a customized look-and-feel, and custom business rules associated in the process and interface. In some cases, we started using the available reusable task flows of the workspace, but after a while we weren't able to fulfil customer requirements. They wanted to get the list of tasks and of processes with a specific look-and-feel and with search fields that would make sense for business. Since they had a lot of h Human Tasks, they also wanted an interface that could generate any Human Task interface in order to achieve higher productivity.
Our HTG and all related libraries were made possible by the extensibility of the ADF framework and BPM's middleware, as well as its productivity features (like declarative components).
From this starting point, we needed to develop a library to interact with the BPM's middleware, the BpmLib. Once we developed our own library, and after some experiments in developing Human Task interfaces, we saw that developing a custom, hand-made interface to generate Human Tasks was not so far away.
After understanding all these facts, we conducted some tests to compare our HTG with generating Human Task interfaces by using Oracle's out-of-the-box approach. The results are based on our customers' requirements and the level of customization needed to achieve the same final results in both cases:
| | Time spent setting a human task interface without using HTG
(In Hours) | Time spent setting a human task interface using HTG
(In Hours) |
| Generate Interface (ADF Taskflow based on human task) | 0 | 0 |
| Customize Interface (Create a new taskflow for each human task with clients customizations) | 3 | 0 |
| Implement Validations | 2 | 1 |
| Integration of the human task with the main application | 1 | 0 |
| Data submission | 1 | 0 |
| Total | 7 | 1 |
Table 3 - Spent time setting a human task interface using HTG versus without using
As Table 3 depicts, it takes 6 fewer hours to develop each new Human Task interface, independently of the process, by using HTG.
We also achieved cost-effectiveness by using HTG instead of the regular approach: