Sunday, July 31, 2011

Sample JAXB Application Converting POJO to XML Element and Vice-versa

In developing SOA applications, it is necessary that you have tools in converting Java objects to XML and vice-versa. In Oracle Human Task Services for example, the task payloads are represented as XML elements, and if there's a need to manipulate those payload then, I believe that its better to do the manipulation on a Java instance equivalent, rather than navigating to the complex tree structure of the XML payload. With the Java instance, you could take advantage of Java's vast frameworks and utilities.


In this post, I will demonstrate how to convert POJO's to XML and vice-versa using Java Architecture for XML Binding (JAXB), and will give some tips in annotating our entities to avoid some common gotchas.

To be able to convert your POJO to XML, it is mandatory that you put an "@XmlRootElement" annotation to your target classes.
package soadev.jaxbsample.model;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "job")
@XmlAccessorType(XmlAccessType.FIELD)
public class Job{
    private String jobId;
    private String jobTitle;
    private Long maxSalary;
    private Long minSalary;
    //getters and setters
The root name "job" above will be the root of the generated XML as follows:
<job>
   <jobId></jobId>
   <jobTitle></jobTitle>
   <maxSalary></maxSalary>
   <minSalary></minSalary>
</job>

The "@XmlAccessorType(XmlAccessType.FIELD)" annotation tells JAXB that only the fields will be marshalled to XML. There are other types which you can read from the links near the end of this post. I am more inclined to use this annotation particularly because our entities persistence annotations are also on the fields, and that we have a lot of transient methods like "computeTotal" which value I don't need to be marshalled.

I will also recommend that you removed any unnecessary associations (some auto-generated by JDeveloper when creating entities from database tables)like for example departmentList in the Employee class (mapped by the manager in Departments). Those associations will add too much complexity especially if you are already starting to expose your EJB session beans as Web Services or as EJB Service. When you cannot remove it due to some usage requirement then put "@XmlTransient" annotation if those associations are not necessary to be marshalled to XML.

If you have fields of type "java.sql.Timestamp", create a TimeStampAdapter class as follows:

package soadev.jaxb.adapters;

import java.sql.Timestamp;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class TimestampAdapter extends XmlAdapter<Date, Timestamp> {

    public Date marshal(Timestamp value) {
        if(value == null){
            return null;
        }
        return new Date(value.getTime());
    }

    public Timestamp unmarshal(Date value) {
        if (value == null){
            return null;
        }
        return new Timestamp(value.getTime());
    }
}
...and annotate your approriate fields as follows:
    @XmlJavaTypeAdapter(value = TimestampAdapter.class)
    private Timestamp hireDate;
This annotation can also be put inside a "package-info.java". Please see the links for details.

The runner class

The class below shows how to convert POJO to XML. It also contains a sample on how to convert to POJO an XML element with a different root name.

package soadev.jaxbsample.clients;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import oracle.bpel.services.common.util.XMLUtil;

import org.w3c.dom.Document;

import org.w3c.dom.Element;

import soadev.jaxbsample.model.Department;
import soadev.jaxbsample.model.Employee;
import soadev.jaxbsample.model.Job;

public class SimpleJaxbClient {
    public static void main(String[] args) {
        SimpleJaxbClient client = new SimpleJaxbClient();
        try {
            client.testMarshallUnmarshallSimplePojoWithSameRootQName();
            client.testMarshallUnmarshallComplexPojoWithSameRootQName();
            client.testUnmarshallComplexXmlWithDifferentRootQName();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void testMarshallUnmarshallSimplePojoWithSameRootQName() throws Exception {
        System.out.println("----Start testMarshallUnmarshallSimplePojoWithSameRootQName----");
        JAXBContext context = JAXBContext.newInstance(Job.class);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        System.out.println("Instantiating simple pojo...");
        Job job = new Job();
        job.setJobId("SOADEV");
        job.setJobTitle("SOA Developer");
        job.setMaxSalary(10000L);
        job.setMinSalary(8000L);
        System.out.println("Simple pojo created: " + job);
        System.out.println("Marshalling pojo to a DOM Node...");
        DocumentBuilder docBuilder =
            DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = docBuilder.newDocument();
        marshaller.marshal(job, document);
        Element jobElement = document.getDocumentElement();
        System.out.println("Job element created: " + jobElement);
        System.out.println(XMLUtil.toString(jobElement));
        System.out.println("Unmarshalling an xml element to a POJO");
        Unmarshaller unmarshaller = context.createUnmarshaller();
        Job jobPojoFromXml = (Job)unmarshaller.unmarshal(jobElement);
        System.out.println("Simple pojo from xml created :" + jobPojoFromXml);
        System.out.println(jobPojoFromXml);
        System.out.println(jobPojoFromXml.getJobTitle());
        System.out.println(jobPojoFromXml.getJobId());
        System.out.println("----End testMarshallUnmarshallSimplePojoWithSameRootQName----");
    }
    
    public void testMarshallUnmarshallComplexPojoWithSameRootQName()throws Exception{
        System.out.println("----Start testMarshallUnmarshallComplexPojoWithSameRootQName----");
        JAXBContext context = JAXBContext.newInstance(Employee.class);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        System.out.println("Instantiating complex pojo...");
        Employee employee = new Employee();
        employee.setEmployeeId(1L);
        employee.setFirstName("Rommel");
        employee.setLastName("Pino");
        Job job = new Job();
        job.setJobId("SOADEV");
        job.setJobTitle("SOA Developer");
        job.setMaxSalary(10000L);
        job.setMinSalary(5000L);
        employee.setJob(job);
        Department department = new Department();
        department.setDepartmentName("Java Team");
        employee.setDepartment(department);
        System.out.println("Complex pojo created: " + employee);
        System.out.println("Marshalling complex pojo to a DOM Node...");
        DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = docBuilder.newDocument();
        marshaller.marshal(employee, document);
        Element employeeElement = document.getDocumentElement();
        System.out.println("Employee element created: "+ employeeElement);
        System.out.println(XMLUtil.toString(employeeElement));
        System.out.println("Unmarshalling an xml element to a POJO");
        Unmarshaller unmarshaller = context.createUnmarshaller();
        Employee employeePojoFromXml = (Employee)unmarshaller.unmarshal(employeeElement);
        System.out.println("Complex pojo from xml created :" + employeePojoFromXml);
        System.out.println(employeePojoFromXml);
        System.out.println(employeePojoFromXml.getEmployeeId());
        System.out.println(employeePojoFromXml.getFirstName());
        System.out.println(employeePojoFromXml.getLastName());
        System.out.println(employeePojoFromXml.getJob());
        System.out.println(employeePojoFromXml.getJob().getJobTitle());
        System.out.println(employeePojoFromXml.getDepartment());
        System.out.println(employeePojoFromXml.getDepartment().getDepartmentName());
        System.out.println("----End testMarshallUnmarshallComplexPojoWithSameRootQName----");
    }
    
    public void testUnmarshallComplexXmlWithDifferentRootQName()throws Exception{
        System.out.println("----Start testUnmarshallComplexXmlWithDifferentRootQName----");
        //root is 'employeeVariable' instead of 'employee'
        String xmlString = 
            "<employeeVariable>" + 
            "  <employeeId>1</employeeId>" + 
            "  <firstName>Rommel</firstName>" + 
            "  <lastName>Pino</lastName>" + 
            "  <job>" + 
            "    <jobId>SOADEV</jobId>" + 
            "    <jobTitle>SOA Developer</jobTitle>" + 
            "    <maxSalary>10000</maxSalary>" + 
            "    <minSalary>5000</minSalary>" + 
            "  </job>" + 
            "  <department>" +
            "    <departmentName>Java Team</departmentName>" +
            "  </department>" +         
            "</employeeVariable>";
        InputStream is = new ByteArrayInputStream(xmlString.getBytes());
        DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = docBuilder.parse(is);
        Element varElement = document.getDocumentElement();
        JAXBContext context = JAXBContext.newInstance(Employee.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        JAXBElement<Employee> employeeJaxbElement = unmarshaller.unmarshal(varElement, Employee.class);
        System.out.println("Element variable is :" + employeeJaxbElement.getName());
        Employee employeePojoFromXml = employeeJaxbElement.getValue();
        System.out.println("Complex pojo from xml created :" + employeePojoFromXml);
        System.out.println(employeePojoFromXml);
        System.out.println(employeePojoFromXml.getEmployeeId());
        System.out.println(employeePojoFromXml.getFirstName());
        System.out.println(employeePojoFromXml.getLastName());
        System.out.println(employeePojoFromXml.getJob());
        System.out.println(employeePojoFromXml.getJob().getJobTitle());
        System.out.println(employeePojoFromXml.getDepartment());
        System.out.println(employeePojoFromXml.getDepartment().getDepartmentName());
        System.out.println("----End testUnmarshallComplexXmlWithDifferentRootQName----");
    }    
}
Run Outpout
C:\Oracle\11g\jdk160_24\bin\javaw.exe -client -classpath ...
----Start testMarshallUnmarshallSimplePojoWithSameRootQName----
Instantiating simple pojo...
Simple pojo created: soadev.jaxbsample.model.Job@a6d51e
Marshalling pojo to a DOM Node...
Job element created: oracle.xml.parser.v2.XMLElement@9f5011
<job>
   <jobId>SOADEV</jobId>
   <jobTitle>SOA Developer</jobTitle>
   <maxSalary>10000</maxSalary>
   <minSalary>8000</minSalary>
</job>

Unmarshalling an xml element to a POJO
Simple pojo from xml created :soadev.jaxbsample.model.Job@c62c8
soadev.jaxbsample.model.Job@c62c8
SOA Developer
SOADEV
----End testMarshallUnmarshallSimplePojoWithSameRootQName----
----Start testMarshallUnmarshallComplexPojoWithSameRootQName----
Instantiating complex pojo...
Complex pojo created: soadev.jaxbsample.model.Employee@76fba0
Marshalling complex pojo to a DOM Node...
Employee element created: oracle.xml.parser.v2.XMLElement@949f69
<employee>
   <department>
      <departmentName>Java Team</departmentName>
   </department>
   <employeeId>1</employeeId>
   <firstName>Rommel</firstName>
   <lastName>Pino</lastName>
   <job>
      <jobId>SOADEV</jobId>
      <jobTitle>SOA Developer</jobTitle>
      <maxSalary>10000</maxSalary>
      <minSalary>5000</minSalary>
   </job>
</employee>

Unmarshalling an xml element to a POJO
Complex pojo from xml created :soadev.jaxbsample.model.Employee@82764b
soadev.jaxbsample.model.Employee@82764b
1
Rommel
Pino
soadev.jaxbsample.model.Job@12452e8
SOA Developer
soadev.jaxbsample.model.Department@1bf3d87
Java Team
----End testMarshallUnmarshallComplexPojoWithSameRootQName----
----Start testUnmarshallComplexXmlWithDifferentRootQName----
Element variable is :employeeVariable
Complex pojo from xml created :soadev.jaxbsample.model.Employee@42552c
soadev.jaxbsample.model.Employee@42552c
1
Rommel
Pino
soadev.jaxbsample.model.Job@e5bbd6
SOA Developer
soadev.jaxbsample.model.Department@8ee016
Java Team
----End testUnmarshallComplexXmlWithDifferentRootQName----
Process exited with exit code 0.

Sample Application

You can get the sample application from here. To run, find the SimpleJaxbClient.java and run.

Relevant links

  • JAXB Architecture
  • Thursday, July 28, 2011

    Custom BPM Workspace Deployed in a Non-SOA Server in a Different Domain

    In my previous post, I demonstrated through a sample application how to utilize the reusable task flows from BPM Workspace. In this post, I will share the steps and configuration to deploy the custom application to a non-soa server located in a different domain.

    The soa server in my case is a stand alone installation of Oracle SOA Suite with configured domain "base_domain" that supports SOA, BPM, and BAM.
    The remote non-soa server is the default integrated weblogic server of JDeveloper using port "7101". The integrated weblogic server "DefaultServer" is in domain "DefaultDomain".

    Steps and Configuration

    There are four steps to successfully run the custom application:
    1. Set the "federatedMode" input parameter of the taskList task flow to true.
    2. Deploy all the referred libraries in the weblogic-application.xml and weblogic.xml to the non-soa server as follows:
      • oracle.soa.bpel
      • oracle.soa.workflow
      • oracle.bpm.runtime
      • oracle.bpm.client
      • oracle.bpm.projectlib
      • oracle.bpm.workspace
      • oracle.bpm.webapp.common
      • oracle.soa.worklist.webapp
      Note: If you are not using in your custom application the BPM related task flows but the task list, then you just need to deploy the oracle.soa.workflow.jar and oracle.soa.worklist.webapp.jar. These libraries are present in the sub directories in "$MIDDLEWARE_HOME$\jdeveloper\soa\modules".

    3. Add a properly configured wf_client_config.xml in the classpath. You can put the file inside "MyUIShellBPMApp\UIShellBPMWeb\src" directory. A sample wf_client_config.xml is as follows:
      <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
      <workflowServicesClientConfiguration  xmlns="http://xmlns.oracle.com/bpel/services/client" clientType="REMOTE">
         <server default="true" name="default">
            <localClient>
               <participateInClientTransaction>false</participateInClientTransaction>
            </localClient>
            <remoteClient>
               <serverURL>t3://hoitpino:8001</serverURL>
               <initialContextFactory>weblogic.jndi.WLInitialContextFactory</initialContextFactory>
               <participateInClientTransaction>false</participateInClientTransaction>
            </remoteClient>
            <soapClient>
               <rootEndPointURL>http://hoitpino:8001</rootEndPointURL>
               <identityPropagation mode="dynamic" type="saml">
                  <policy-references>
                     <policy-reference enabled="true" category="security" 
                      uri="oracle/wss10_saml_token_client_policy"/>
                  </policy-references>
               </identityPropagation>
            </soapClient>
         </server>
      </workflowServicesClientConfiguration>
      
      
    4. Establish global trust between the domains.
      1. Login to the Oracle WebLogic Server console.
      2. Under Domain Structures, select the domain name like "base_domain".
      3. Select the Security tab.
      4. Click the Advanced link (near the bottom Save button.)
      5. Give some password in the "Credential" field. The same password should be used for the other domains.
      6. Click Save.
      7. Restart the server.

    Running the application

    Access the sample application from my previous post and follow the steps described above. Run the application by right clicking the main.jspx in JDeveloper, then run.

    Below is a screenshot of my running application in integrated weblogic server:

    Cheers!

    Wednesday, July 27, 2011

    Custom ADF UIShell Application with Oracle BPM Workspace Task Flows

    An Excerpt from Appendix A (Creating Custom ADF Applications with Oracle Business Process Management Workspace Task Flows) of the Oracle Fusion Middleware User's Guide for Oracle Business Process Management 11g Release 1 (11.1.1.5.0)
    Different features available in Process Workspace are exposed as standalone reusable components, called task flows. You can embed task flows in any Oracle Application Development Framework (ADF) application. These standalone task flows provide many parameters that enable you to build customized applications.
    All the task flows are bundled in an ADF library that you can include in the application in which you are embedding.

    Below are the notable steps that I have done to incorporate the BPM Workspace task flows into this custom ADF UI Shell application:
    1. I Added the BPM Worklist component in the class path. This library includes the adflibTaskListTaskFlow.jar that contains the reusable taskflows related to the standard worklist application.
    2. I want to display the "Process Tracking" and "Standard Dashboards" tabs, so I acquired the oracle.bpm.workspace-adflib.jar from a process portal installation and added it into the libraries and classpath.
    3. Modified the weblogic-application.xml to add some soa and bpm related library references. Please see the weblogic-application.xml in the artifacts section below.
    4. Added weblogic.xml descriptor that includes a library ref to the "oracle.soa.worklist.webapp" library. Please see artifacts section for the complete weblogic.xml
    5. With the jars configures from the steps above, the application is already deploying fine, but I encountered ClassNotFoundExceptions during runtime. I exploded the OracleBPMWorkspace application that ships with the standard Oracle Suite 11g R1 installation and noticed 4 additonal jars as part of the WEB-INF/lib directory, namely: oracle.bpm.security.jar; oracle.bpm.jsfcomponents.jar; oracle.bpm.workspace.adf.jar; oracle.bpm.workspace.model.jar; and oracle.bpm.web-resources.jar. I added them all to the libraries and class path.
    6. I configured ADF Security and added resource grants to the corresponding task flows. Ensure to mark check the "Show task flows imported from ADF libraries" checkbox located in the jaz-data.xml Overview tab.
    7. Tested the application by deploying it to a soa enabled weblogic server.

    User Experience

    Figure 1: Workspace Tasks Tab

    To display the BPM Applications/ Initiable tasks in the left panel above, you also need to set the "taskFlowMode" parameter of the "taskList-task-flow-definition" to "workspace" and add the taskFlow binding "processApplicationsTaskflow1" on the same page definition. Please the see the complete taskListPageDef.xml page definition on the artifacts section below.


    Figure 2: Workspace Process Tracking Tab

    The process instances table is displaying, except for ONE BIG FAIL, it does not expose anything, like sort of contextual events- for example, so that the processInstanceDetailTaskflow can sync with the current selection on the processInstancesTaskflow. I hope I'm just wrong but the documentation is empty about this.


    Figure 3: Workspace Standard Dashboards Tab


    Artifacts

    weblogic-application.xml
    <?xml version = '1.0' encoding = 'windows-1252'?>
    <weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                          xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-application http://www.bea.com/ns/weblogic/weblogic-application/1.0/weblogic-application.xsd"
                          xmlns="http://www.bea.com/ns/weblogic/weblogic-application">
      <listener>
        <listener-class>oracle.adf.share.weblogic.listeners.ADFApplicationLifecycleListener</listener-class>
      </listener>
      <listener>
        <listener-class>oracle.mds.lcm.weblogic.WLLifecycleListener</listener-class>
      </listener>
      <library-ref>
        <library-name>adf.oracle.domain</library-name>
      </library-ref>
      <library-ref>
        <library-name>oracle.soa.bpel</library-name>
      </library-ref>
      <library-ref>
        <library-name>oracle.soa.workflow</library-name>
      </library-ref>
      <library-ref>
        <library-name>oracle.bpm.runtime</library-name>
      </library-ref>
      <library-ref>
        <library-name>oracle.bpm.client</library-name>
      </library-ref>
      <library-ref>
        <library-name>oracle.bpm.projectlib</library-name>
      </library-ref>
      <library-ref>
        <library-name>oracle.bpm.workspace</library-name>
      </library-ref>
      <library-ref>
        <library-name>oracle.bpm.webapp.common</library-name>
      </library-ref>
    </weblogic-application>
    
    

    weblogic.xml
    <?xml version = '1.0' encoding = 'windows-1252'?>
    <weblogic-web-app xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app">
    
      <security-role-assignment>
        <role-name>valid-users</role-name>
        <principal-name>users</principal-name>
      </security-role-assignment>
      <session-descriptor>
        <persistent-store-type>replicated_if_clustered</persistent-store-type>
      </session-descriptor>
      <library-ref>
        <library-name>oracle.soa.worklist.webapp</library-name>
        <specification-version>11.1.1</specification-version>
      </library-ref>
    </weblogic-web-app>
    

    taskListPageDef.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                    version="11.1.1.60.13" id="taskListPageDef"
                    Package="fragments.workspace">
      <parameters/>
      <executables>
        <variableIterator id="variables"/>
        <taskFlow id="taskListtaskflowdefinition1"
                  taskFlowId="/WEB-INF/taskList-task-flow-definition.xml#taskList-task-flow-definition"
                  activation="deferred"
                  xmlns="http://xmlns.oracle.com/adf/controller/binding">
          <parameters>
            <parameter id="taskFlowMode" value="workspace"/>
            <parameter id="showViewsPanel" value="true"/>
            <parameter id="showTaskDetailsPanel" value="true"/>
          </parameters>
        </taskFlow>
        <taskFlow id="processApplicationsTaskflow1"
                  taskFlowId="/WEB-INF/processApplicationsTaskflow.xml#processApplicationsTaskflow"
                  activation="deferred"
                  xmlns="http://xmlns.oracle.com/adf/controller/binding"/>
      </executables>
      <bindings/>
    </pageDefinition>
    

    Sample Application

    You can download the sample sample application from here.
    To test the sample application after deployment, open in browser "http://host:port/myworkspace/faces/main".

    Saturday, July 23, 2011

    How to Send Notification to the Initiator of a BPM Process Instance Upon Completion of an Activity

    In BPEL, human workflow is almost always expected to be represented by a single human task activity with multiple steps - that is why the Oracle SOA Suite 11g Human Task component has a lot of facility for notification related to the raise of events like the assign, error, expire, withdraw, alert, complete, and etc. on a particular task definition.

    But in the current release of BPM (11.1.1.5), it is different. Each task participant or steps in the workflow are represented by a different task definition, and to notify the initiator of a process instance, upon approval or disapproval of a request or a completion of a certain activity, needs to be configured on a separate service call activity, and definitely, not covered by the facilities of Human Task component.

    And there goes the topic of this blog post - How to send notification to the initiator of a BPM process instance upon completion of an activity?

    First, of course you need to have a properly set-up email driver configuration. If you still don't, then you could take advantage of gmail as the smtp server as describe on my previous post - Using GMail as Mail Server for Oracle SOA Suite 11g Notifications.

    Secondly, you need to obtain the the necessary wsdl and xsd files of the NotificationService available from BPEL. This is describe in one of Niall Commiskey's charming blog post - BPM 11g and Notifications.

    Third, we need to get the email address of the initiator of a bpm process instance so we can set it as the addressee on a NotificationService service call. This is the thing that will be described on this post ;)

    Before we can get the email address on the initiator, we need to know the username of the initiator first. My first bet was to use the BPM Extension Function - "bpmn:getProcessInstanceAttribute('creator')", but this doesn't seem to work as of 11.1.1.5 in which I am getting some internal exceptions that says - "the variable is not defined in the current scope". As an alternative, the initiator username can be acquired through the following xpath expression available from BPEL Xpath Extension Functions:
        ora:getCreator()
    
    To get email address of a specified user we can use the following xpath expression from the Identity Service Functions:
        ids:getUserProperty()
    
    Excerpt from the expression builder:
    Get a user property. The signature of this function is ids:getUserProperty(userName, attributeName, realmName) The arguments to the function: 1) userName - String or element containing the user whose attribute should be retrieved 1) attributeName - String or element containing the name of the user attribute. The attribute name should be one of the following values: givenName middleName sn displayName mail telephoneNumber homephone mobile facsimileTelephoneNumber pager preferredLanguage title manager 3)realmName - The realm name. This is optional and if not specified default realm is assumed.

    And finally, below is the compound expression to access the email address of the initiator of the process instance:
        ids:getUserProperty(ora:getCreator(),'mail')
    
    Cheers!

    Thursday, July 21, 2011

    Custom BPM Applications(InitiableTasks) ADF TaskFlow

    In a previous post, we show how to access the initiable tasks in a main method of a simple Java class. In line with, this post demonstrates how to get the task's URL and launch the corresponding generated BPM task details form in an ADF application.


    The following are the key methods and artifacts:
    • getTaskDisplayURL() - this method retrieves from a task object, the URL defined in the runtime Human Task configuration of a specific deployed composite, which can be found in Enterprise Manager. The URL is automatically set when you deploy a web app that contains BPM task forms with the configuration - "hwtaskflow.xml" that contains our task definition.
    • getTaskFlowDefinitionId() - this method returns a TaskFlowId object from the lengthy URL returned by the getTaskDisplayURL() method above.
    • initiateTask() - this method that returns a string is used as action attribute in the page fragment. It invokes both methods above then returns a "callTaskDetail" which launches a declarative popup.
    • The declarative pop-up, "modal-dialog-task-flow", is a simple task flow that we reused from the BPM Worklist components library. It doesn't do much but invoke a remote task flow. We could create our own if necessary.
    • wf_client_config.xml - We add this config file to remove from our code the connection details. To get a BPMServiceClientFactory instance, we just passed null parameters as follows:
      factory = BPMServiceClientFactory.getInstance(null, null, null);
    • Configure ADF Security - so we don't need to pass username and password when getting a BPMContext.

    Below is the the backing bean that support our page fragment.
    package soadev.bpm.web.backing;
    
    import java.net.URLDecoder;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.StringTokenizer;
    
    import oracle.adf.controller.TaskFlowId;
    
    import oracle.bpel.services.bpm.common.IBPMContext;
    import oracle.bpel.services.workflow.task.model.Task;
    import oracle.bpel.services.workflow.worklist.api.util.WorklistUtil;
    import oracle.bpel.services.workflow.worklist.servlet.Constants;
    
    import oracle.bpm.client.BPMServiceClientFactory;
    import oracle.bpm.services.client.IBPMServiceClient;
    import oracle.bpm.services.processmetadata.IProcessMetadataService;
    import oracle.bpm.services.processmetadata.ProcessMetadataSummary;
    
    import org.apache.myfaces.trinidad.context.RequestContext;
    
    import soadev.view.utils.JSFUtils;
    
    
    public class InitiableTaskListForm {
        private IBPMContext bpmContext;
        private ProcessMetadataSummary process;
        private BPMServiceClientFactory factory;
    
        public InitiableTaskListForm() {
            factory = BPMServiceClientFactory.getInstance(null, null, null);
        }
        public List<ProcessMetadataSummary> getInitiatiableTasks() {
            try {
                // get the process metadata service
                IProcessMetadataService pms =
                    factory.getBPMServiceClient().getProcessMetadataService();
                // get the list of initiatable proceses
                return pms.getInitiatableProcesses(getIBPMContext());
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        
        public String initiateTask() throws Exception {
            Task task = initiateTask(process.getCompositeDN()+ "/" + process.getProcessName());
            String detailsUrl = getTaskDisplayURL(task);
            getPageFlowScope().put("remoteUserTaskUrl", detailsUrl);
            getPageFlowScope().put("remoteTaskDefId", getTaskFlowDefinitionId(detailsUrl));
            return "callTaskDetail";
        }
    
    
        private Map<String, Object> getPageFlowScope() {
            return RequestContext.getCurrentInstance().getPageFlowScope();
        }
    
        private Task initiateTask(String compositeDN) throws Exception {
            IBPMServiceClient client = factory.getBPMServiceClient();
            return client.getInstanceManagementService().createProcessInstanceTask(getIBPMContext(),
                                                                                   compositeDN);
        }
    
        private String getTaskDisplayURL(Task task) throws Exception {
            Map parameters = new HashMap();
            parameters.put(Constants.BPM_WORKLIST_TASK_ID,
                           task.getSystemAttributes().getTaskId());
            parameters.put(Constants.BPM_WORKLIST_CONTEXT,
                           getIBPMContext().getToken());
            String url =
                WorklistUtil.getTaskDisplayURL(factory.getWorkflowServiceClient(),
                                               getIBPMContext(), task, null,
                                               "worklist", parameters);
            return url;
        }
    
    
        //courtesy of http://amulyamishras-tech-blog.blogspot.com/2011/01/opening-remote-http-url-inan-adf-dialog.html
    
        private TaskFlowId getTaskFlowDefinitionId(String detailsURL) {
            //Checks if the url is null.
            if (detailsURL == null)
                return null;
            //gets the parameter list from the url.
            String parameterList =
                detailsURL.substring(detailsURL.indexOf("?") + 1,
                                     detailsURL.length());
            //tokenizes the url.
            StringTokenizer stok = new StringTokenizer(parameterList, "&");
            String taskFlowId = null;
            String documentName = null;
            //gets the taskflowId from the url.
            while (stok.hasMoreTokens()) {
                String parameter = stok.nextToken();
                String[] parts = parameter.split("=", 2);
                if (parts[0].equals("_id"))
                    taskFlowId = parts[1];
                if (parts[0].equals("_document"))
                    documentName = parts[1];
            }
            //decodes the encoded url.
            try {
                documentName = URLDecoder.decode(documentName, "UTF-8");
                taskFlowId = URLDecoder.decode(taskFlowId, "UTF-8");
    
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return new TaskFlowId(documentName, taskFlowId);
        }
    
        public void setProcess(ProcessMetadataSummary process) {
            this.process = process;
        }
    
        public ProcessMetadataSummary getProcess() {
            return process;
        }
    
        private IBPMContext getIBPMContext() throws Exception {
            if (bpmContext == null) {
                bpmContext = (IBPMContext)JSFUtils.getFromSession("BPM_CONTEXT");
            }
            if (bpmContext == null) {
                bpmContext = factory.getBPMUserAuthenticationService().getBPMContextForAuthenticatedUser();
            }
            JSFUtils.storeOnSession("BPM_CONTEXT", bpmContext);
            return bpmContext;
        }
    }
    
    The "initiable_task_list.jsff" page fragment:
    <?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">
      <af:panelBox text="Applications" id="pb1">
        <f:facet name="toolbar"/>
        <af:panelList id="pl1">     
          <af:forEach items="#{viewScope.initiableTaskListForm.initiatiableTasks}"
                      var="process">
                <af:commandLink text="#{process.projectName}: #{process.processName}"
                          action="#{viewScope.initiableTaskListForm.initiateTask}" id="cl1"
                                useWindow="true"
                                windowModalityType="applicationModal">
              <af:setPropertyListener type="action" from="#{process}"
                                      to="#{viewScope.initiableTaskListForm.process}"/>
            </af:commandLink>
          </af:forEach>
        </af:panelList>
      </af:panelBox>
    </jsp:root>
    

    Sample Application

    You can get the sample application from this link. The sample application needs to be deployed on a SOA server. On a subsequent post I will demonstrate how to run this sample on the integrated weblogic server.
    In the sample, the InitiableTasksProject is deployed as an ADF library so it can easily be reused in other projects/ applications.

    Cheers!

    Sunday, July 17, 2011

    BPM 11g Java API Samples

    Please click on the link below to access the sample application that contains the runnable classes related to my posts on BPM 11g Java API. This link will be updated to reflect latest additions to the samples.
    BPM 11g Java API samples
    Cheers!

    Getting BPM InitiableTasks and Initiating a Task

    This is my third post on the series of post related to the Oracle BPM 11g Java API.
    The class below demonstrate how to get the BPM initiable tasks and how to initiate a task. It also contains a testGetProcessMetadata() method which could be useful to initiate a predefined task.
    package soadev.bpmclient;
    
    import java.util.List;
    
    import oracle.bpel.services.bpm.common.IBPMContext;
    
    import oracle.bpel.services.workflow.task.impl.TaskUtil;
    import oracle.bpel.services.workflow.task.model.Task;
    
    import oracle.bpm.services.instancemanagement.IInstanceManagementService;
    import oracle.bpm.services.processmetadata.IProcessMetadataService;
    import oracle.bpm.services.processmetadata.ProcessMetadataSummary;
    
    public class GetInitiableTasks {
        public static void main(String[] args) {
            GetInitiableTasks client = new GetInitiableTasks();
            client.testGetInitiatiableTasks();
            client.testInitiateTask();
            
        }
        public void testGetInitiatiableTasks() {
            System.out.println(">>> getting initiable tasks");
            try {
                IProcessMetadataService service =
                    Fixture.getBPMServiceClient().getProcessMetadataService();
                IBPMContext bpmContext = 
                    Fixture.getIBPMContext("pino", "password1");
                List<ProcessMetadataSummary> initiableTasks =  
                    service.getInitiatableProcesses(bpmContext);
                for(ProcessMetadataSummary pms: initiableTasks){
                    System.out.println(pms.getProjectName()+ "/" + pms.getProcessName());
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public void testGetProcessMetadata(){
            try {
                String processName = "HelloWorldProcess";
                String compositeDN = "default/HelloWorldProject!1.0*soa_a164d265-e961-4afb-b44b-50ea95a06fa1";
                IBPMContext ctx = Fixture.getIBPMContext("pino", "password1");
                IProcessMetadataService service =
                    Fixture.getBPMServiceClient().getProcessMetadataService();
                ProcessMetadataSummary pms = 
                    service.getProcessMetadataSummary(ctx, compositeDN, processName);
                System.out.println("compositeDN: " + pms.getCompositeDN());
                System.out.println("processName: " + pms.getProcessName());
                System.out.println("compositeName: " + pms.getCompositeName());
            } catch (Exception e) {
                // TODO: Add catch code
                e.printStackTrace();
            } 
        }
        public void testInitiateTask(){
            System.out.println(">>> initiating a task");
            try {
                IProcessMetadataService service =
                    Fixture.getBPMServiceClient().getProcessMetadataService();
                IBPMContext bpmContext = 
                    Fixture.getIBPMContext("pino", "password1");
                List<ProcessMetadataSummary> initiableTasks =
                    service.getInitiatableProcesses(bpmContext);
                //get the first initable task
                ProcessMetadataSummary pms = initiableTasks.get(0);
                IInstanceManagementService ims =
                    Fixture.getBPMServiceClient().getInstanceManagementService();
                Task task = ims.createProcessInstanceTask(bpmContext, 
                                        pms.getCompositeDN()+"/"+pms.getProcessName());
                System.out.println(">>> task initiated");
                printTask(task);
     
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        private void printTask(Task task){
            System.out.println(TaskUtil.getInstance().toString(task));
        }
    
    }
    

    Run output...
    C:\Oracle\11g\jdk160_24\bin\javaw.exe -client -classpath ...
    >>> getting initiable tasks
    EmployeeTransfersProject/EmployeeTransferProcess
    HelloWorldProject/HelloWorldProcess
    JobMasterProject/JobMasterCreationProcess
    >>> initiating a task
    >>> task initiated
    <task xmlns="http://xmlns.oracle.com/bpel/workflow/task">
       <title>Request Employee Transfer</title>
       <creator>pino</creator>
       <ownerRole>EmployeeTransfersProject.ProcessOwner</ownerRole>
       <priority>3</priority>
       <processInfo/>
       <systemAttributes>
          <assignedDate>2011-07-17T00:33:15+03:00</assignedDate>
          <createdDate>2011-07-17T00:33:15+03:00</createdDate>
          <digitalSignatureRequired>false</digitalSignatureRequired>
          <displayInfo>
             <applicationName>worklist</applicationName>
             <hostname>hoitpino.al-naghi.com</hostname>
             <httpPort>8001</httpPort>
             <httpsPort>0</httpsPort>
             <uri>/workflow/HumanTaskForms/faces/adf.task-flow?_id=RequestEmployeeTransfer_TaskFlow&amp;_document=WEB-INF/RequestEmployeeTransfer_TaskFlow.xml</uri>
          </displayInfo>
          <fromUser>
             <type>user</type>
          </fromUser>
          <numberOfTimesModified>1</numberOfTimesModified>
          <state>ASSIGNED</state>
          <systemActions>
             <action>VIEW_TASK</action>
             <displayName>VIEW_TASK</displayName>
          </systemActions>
          <taskId>04d4a518-f444-4e04-8a70-b64c58e79b0a</taskId>
          <taskNumber>200381</taskNumber>
          <updatedBy>
             <id>pino</id>
             <type>user</type>
          </updatedBy>
          <updatedDate>2011-07-17T00:33:15+03:00</updatedDate>
          <version>1</version>
          <taskDefinitionId>default/EmployeeTransfersProject!1.0*soa_a8c4f842-6b05-406a-be83-4ceb27588928/RequestEmployeeTransfer</taskDefinitionId>
          <workflowPattern>Participant</workflowPattern>
          <participantName>default.DefaultPerformer</participantName>
          <assignees>
             <id>EmployeeTransfersProject.Requester</id>
             <displayName></displayName>
             <type>application_role</type>
          </assignees>
          <isPartialTask>true</isPartialTask>
       </systemAttributes>
       <systemMessageAttributes/>
       <callback/>
       <category>HR</category>
       <sca/>
       <applicationContext>OracleBPMProcessRolesApp</applicationContext>
       <taskDefinitionId>default/EmployeeTransfersProject!1.0*soa_a8c4f842-6b05-406a-be83-4ceb27588928/RequestEmployeeTransfer</taskDefinitionId>
       <mdsLabel>soa_a8c4f842-6b05-406a-be83-4ceb27588928</mdsLabel>
       <creatorDisplayName>pino</creatorDisplayName>
       <customAttributes/>
    </task>
    Process exited with exit code 0.
    

    Please refer to the earlier post with regards to the Fixture class and the necessary BPM jars. You can access the sample application on this link.


    Cheers!

    Thursday, July 14, 2011

    Querying a BPM Process Instance and Adding Comments

    This is my second post on the series of post related to the Oracle BPM 11g Java API.
    The code below demonstrate how to query a specific BPM process instance and how to add comments to an instance:
    
    package soadev.bpmclient;
    
    import java.util.Calendar;
    import java.util.List;
    
    import oracle.bpel.services.bpm.common.IBPMContext;
    
    import oracle.bpm.client.common.BPMServiceClientException;
    import oracle.bpm.services.common.exception.BPMException;
    import oracle.bpm.services.instancemanagement.IInstanceManagementService;
    import oracle.bpm.services.instancemanagement.model.IIdentityType;
    import oracle.bpm.services.instancemanagement.model.IProcessComment;
    import oracle.bpm.services.instancemanagement.model.IProcessInstance;
    import oracle.bpm.services.instancemanagement.model.impl.IdentityType;
    import oracle.bpm.services.instancemanagement.model.impl.ProcessComment;
    import oracle.bpm.services.instancequery.IInstanceQueryService;
    
    public class GetProcessInstance {
        public static void main(String[] args) {
            GetProcessInstance client = new GetProcessInstance();
            client.testGetProcessInstance();
            client.testAddAttachment();
        }
    
        public void testGetProcessInstance() {
            try {
                String sampleProcessId = "150001"; //replace this with a valid id
                IBPMContext ctx = Fixture.getIBPMContext("pino", "password1");
                IInstanceQueryService queryService =
                    Fixture.getBPMServiceClient().getInstanceQueryService();
                System.out.println("retrieve sample instance");
                IProcessInstance instance =
                    queryService.getProcessInstance(ctx, sampleProcessId);
    
                printProcessInstance(instance);
    
            } catch (Exception e) {
                // TODO: Add catch code
                e.printStackTrace();
            }
        }
    
        private void printProcessInstance(IProcessInstance instance) {
            System.out.println(instance.getSystemAttributes().getProcessInstanceId());
            System.out.println(instance.getSystemAttributes().getProcessNumber());
            System.out.println(instance.getSystemAttributes().getState());
            System.out.println(instance.getProcessDN());
            System.out.println(instance.getCreator());
            System.out.println(instance.getSystemAttributes().getCreatedDate().getTime());
    
        }
        
        public void testAddAttachment(){
            try {
                String sampleProcessId = "150001"; //replace this with a valid id
                IBPMContext ctx = Fixture.getIBPMContext("pino", "password1");
                IInstanceQueryService queryService =
                    Fixture.getBPMServiceClient().getInstanceQueryService();
                IProcessInstance instance =
                    queryService.getProcessInstance(ctx, sampleProcessId);
                IInstanceManagementService service =
                    Fixture.getBPMServiceClient().getInstanceManagementService();
                IProcessComment comment = new ProcessComment();
                IIdentityType identity = new IdentityType();
                identity.setDisplayName(ctx.getUser());
                identity.setId(ctx.getUser());
                identity.setType("user");
                comment.setUpdatedBy(identity);
                comment.setComment("my programmatic comment3");
                comment.setUpdatedDate(Calendar.getInstance());
                service.addComment(ctx, instance, comment);
                //check if persisted
                IProcessInstance updatedInstance =
                    queryService.getProcessInstance(ctx, sampleProcessId);
                List<IProcessComment> comments = updatedInstance.getUserComment();
                for (IProcessComment c : comments) {
                    System.out.println(c.getComment());// the new comment should appear
                }
            } catch (BPMServiceClientException bpmsce) {
                // TODO: Add catch code
                bpmsce.printStackTrace();
            } catch (BPMException bpme) {
                // TODO: Add catch code
                bpme.printStackTrace();
            } catch (Exception e) {
                // TODO: Add catch code
                e.printStackTrace();
            }
        }
    }
    
    Please refer to the earlier post with regards to the Fixture class and the necessary BPM jars. Or click this link to access the sample application.

    Cheers!

    Querying Oracle BPM Process Instances

    This is the first in a series of posts related to the BPM 11g Java API. As far as I know, as of this writing, there is no official Oracle BPM 11g Java API documentation that is being release yet, but the corresponding jars are already present on a standard JDeveloper installation (probably after installing BPM Extension).
    The purpose of this post is to show simple runnable classes so readers can easily have an acquintance with the BPM 11g APIs, without needing to setup a complex web project.

    We can manipulate BPM processes through a BPMServiceClient that we can acquire from a BPMServiceClientFactory. Below is a utility class that I set to easily acquire a client instance for my testing. This class need the following jars in the classpath: Oracle.bpm.client.jar, Oracle.bpm.project.model.jar, and Oracle.bpm.runtime.jar which are present inside the BPM folders in "MIDDLEWARE_HOME\jdeveloper\soa\modules" directory.
    package soadev.bpmclient;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import oracle.bpel.services.bpm.common.IBPMContext;
    import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
    import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
    import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;
    
    import oracle.bpm.client.BPMServiceClientFactory;
    import oracle.bpm.services.client.IBPMServiceClient;
    
    public class Fixture {
        private static String url = "t3://localhost:8001";
        
        public static BPMServiceClientFactory getBPMServiceClientFactory() {
         Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String> properties =
                new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String>();
    
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,
                                 WorkflowServiceClientFactory.REMOTE_CLIENT);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY
                                .EJB_PROVIDER_URL,url);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY
                                          .EJB_INITIAL_CONTEXT_FACTORY,
                           "weblogic.jndi.WLInitialContextFactory");
            return BPMServiceClientFactory.getInstance(properties, null, null);
        }
    
        public static IBPMContext getIBPMContext(String username,
                                                 String password) throws Exception{      
            return getBPMServiceClientFactory().getBPMUserAuthenticationService()
                                          .authenticate(username, 
                                                        password.toCharArray(),
                                                        null);
        }
    
        public static IWorkflowServiceClient getIWorkflowServiceClient() {
            return getBPMServiceClientFactory().getWorkflowServiceClient();
        }
        
        public static IBPMServiceClient getBPMServiceClient(){
            return getBPMServiceClientFactory().getBPMServiceClient();
        }
    }
    


    Below is a sample class that demonstrate querying of BPM process instances. It shows how to compose Predicate, Ordering, and IInstanceQueryInput objects.
    package soadev.bpmclient;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import oracle.bpel.services.bpm.common.IBPMContext;
    import oracle.bpel.services.workflow.repos.Column;
    import oracle.bpel.services.workflow.repos.Ordering;
    
    import oracle.bpel.services.workflow.repos.Predicate;
    import oracle.bpm.services.instancemanagement.model.IProcessInstance;
    import oracle.bpm.services.instancequery.IColumnConstants;
    import oracle.bpm.services.instancequery.IInstanceQueryInput;
    import oracle.bpm.services.instancequery.IInstanceQueryService;
    import oracle.bpm.services.instancequery.impl.InstanceQueryInput;
    
    public class GetProcessInstances {
        
        public static void main(String[] args) {
            GetProcessInstances client = new GetProcessInstances();
            client.testGetProcessInstances();
        }
        
        public void testGetProcessInstances(){
            try {
                IInstanceQueryService queryService =
                    Fixture.getBPMServiceClient().getInstanceQueryService();
                IBPMContext bpmContext = 
                    Fixture.getIBPMContext("pino", "password1");
                List<Column> columns = new ArrayList<Column>();
                columns.add(IColumnConstants.PROCESS_ID_COLUMN);
                columns.add(IColumnConstants.PROCESS_NUMBER_COLUMN);
                columns.add(IColumnConstants.PROCESS_STATE_COLUMN);
                columns.add(IColumnConstants.PROCESS_TITLE_COLUMN);
                columns.add(IColumnConstants.PROCESS_CREATOR_COLUMN);
                columns.add(IColumnConstants.PROCESS_CREATEDDATE_COLUMN);
    
                Ordering ordering = new Ordering(IColumnConstants.PROCESS_NUMBER_COLUMN,
                                                 true,true);  
                Predicate pred = new Predicate(IColumnConstants.PROCESS_STATE_COLUMN,
                       Predicate.OP_EQ,
                       "OPEN");
                IInstanceQueryInput input = new InstanceQueryInput();
                input.setAssignmentFilter(IInstanceQueryInput.AssignmentFilter.MY_AND_GROUP);
                
                List<IProcessInstance> processInstances =
                    queryService.queryInstances(bpmContext, columns, pred, ordering,
                                                       input);
                System.out.println("ProcessId\tProcess#\tState\tTitle\t\t\t\t\tCreator\tCreadedDate");
                for (IProcessInstance instance : processInstances) {
                    System.out.println(instance.getSystemAttributes().getProcessInstanceId()
                                       + "\t" + instance.getSystemAttributes()
                                                                  .getProcessNumber() 
                                       + "\t" + instance.getSystemAttributes().getState() 
                                       + "\t" + instance.getTitle()
                                       + "\t" + instance.getCreator()
                                       + "\t" + instance.getSystemAttributes()
                                                            .getCreatedDate().getTime());
                }
                if (processInstances.isEmpty()){
                    System.out.println("no result");
                }
            } catch (Exception e) {
                // TODO: Add catch code
                e.printStackTrace();
            }
        }
    }
    

    Below is a sample result when I run it on my machine:

    You can access the sample application on this link.

    Kudus

    Kudus to the following blogs and OTN forum posts:

    Cheers!