Oracle BPM 12c just got Groovy – A Webcenter Content Transformation Example

Introduction

On the 27th June 2014 we released Oracle BPM 12c which included some exciting new features.
One of the less talked about of new features is the support of BPM Scripting which incorporates the Groovy 2.1 compiler and runtime.

So what is Groovy anyway?

Wikipedia describes Groovy as an object-oriented programming language for the Java platform and you can read the definition here.

In short though it is a Java like scripting language, which is simple to use. If you can code a bit of Java then you can write a bit of Groovy and most of the time only a bit is required.

If you can’t code in groovy yet don’t worry, you can just code in Java and that work most of the time too.

With great power comes great responsibility?

The benefits and possibilities of being able to execute snippets of groovy code in a BPM process execution are almost limitless. Therefore we must be responsible in its use and decide whether it makes sense from a BPM perspective in each case and always implement best practices which leverage the best of the BPM execution engine infrastructure.

If you can easily code, then it is easy to write code to do everything. But this goes against what BPM is all about. We must always first look to leverage the powerful middleware infrastructure that the Oracle BPM execution engine sits on, before we look to solve our implementation challenges with low level code.

One benefit of modelled BPM over scripting is Visibility. We know that ideally BPM processes should be modelled by the Business Analysts and Implemented by the IT department.

Business Process Logic should therefore be modelled into the business process directly and not implemented as low level code that the business will not understand nor be aware of at runtime. In this manner the logic always stays easily visible and understood by the Business. Overuse of logic in scripting will quickly transcend into a solution that will be hard to debug or understand in problem resolution scenarios.

If one argues that the business logic from your business process cannot be modelled directly in the BPM  process, then one should revisit the business process analysis and review whether the design actually makes really makes sense and can be improved.

 

What could could be a valid usecase for groovy in BPM?

One valid usecase of groovy scripting can be complex and dynamic data transformations. In Oracle BPM 12c we have the option to use the following mechanisms for transformations:

Data Association

Good for:

  • Top level transformations of the same or similar types
  • Simple transformations of a few elements
  • Lists and arrays
  • Performance

XSL transformation

Good for:

  • Large XML schema elements
  • Assignment of optional XML schema elements and attributes
  • Lists and arrays
  • Reuse

Groovy Scripting

Good for:

  • Generic XML schema types like xsd:any
  • Dynamic data structures
  • Complex logic
  • Error handling
  • Reuse

Java callouts using a mediator or Spring component

Good for:

  • Pure Java implementation requirements
  • Large batch processing

Each method have their own benefits and downsides, but in combination you can transform any payload. What to use is largely a case of:

  • Best practice within your organization
  • Best practice for BPM
  • The level of organized structure of your schemas

In practice, an efficiently implemented BPM process will be a combination of associations, xslt & bpm scripts.

 

tip3Tip: Always try to solve transformation tasks using using a data association first before turning to xslt or groovy. Use the right tool in your toolkit for the right job.

 

 Upgrading from BPM 10g

The inclusion of BPM scripting will also aid in the upgrade from BPM 10g processes. This should be seen as an opportunity to review and improve the implementation as opposed to blindly copying the existing functionality. This is a process that is beyond the scope of this post.

 

A Complex and Dynamic Webcenter Content SOAP Example

Invoking very generic SOAP services can be one instance where groovy can save the day. When a SOAP service is well defined it’s very easy to create a mapping using the xsl or data association mappers. But what if the element definition is very wide open with the use of schemas elements like xsd:any, xsd:anyType or xsd:anyAttribute.

To solve this transformation in XSLT could potentially be complex with lots of hand written, harder to read code.

The GenericRequest of the Webcenter Content SOAP service is an example of such a generic SOAP service. The flexibility of its use means that the payload required is very dynamic.

The actual schema element looks like this.

 

content.xsd

 

Now consider the situation where this payload for the GenericRequest needs to look like this and could potentially have lots of required logic.

 

soapui

This might be accomplished using a complex, hand coded xslt transformation.

Alternatively if you don’t have any xslt world champions on the team, anyone on your development team that can code code java can do this easily with groovy scripting.

Building the Transformation Demo

To demonstrate the transformation capabilities of groovy scripting we are going to create a simple synchronous BPM process based on the above usecase.

We send an Incident as a request and as a response will receive the transformed GenericRequest. In this manner it will be easy for us to see the whole transformed payload that we would normally send to Webcenter Content.

The finished process looks like this.

 

FinishedProcess

 

 

 

 

 

 

 

Create a new BPM Application and define Data Objects and Business Objects

We will create a new BPM application and define the:

  • Input arguments as an Incident
  • Output argument as a Webcenter GenericRequest

 

1) Download the schema zipfile called docs and extract to a local location. Then open Studio (JDeveloper) and from the top menu choose Application->New->BPM Application

 

NewApplication

 

 

 

 

 

 

 

 

2) Click OK, use the application name GroovyDemoApp and click Next

 

AppName

 

 

 

 

 

 

3) Use the Project Name GroovyDemo, then click Next

 

ProjectName

 

 

 

 

 

 

 

 

 

 

4) Now choose the Synchronous Service, name the process GroovyDemoProcess and click Next

 

SyncProcess

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Now we need to define and add the input and output arguments. Here we use some predefined schema elements in schema files that I provide. Firstly we define these as Business Objects, then we use these Business Objects as a definition for our arguments and Data Objects in the process itself.

 

5) Click on the green add icon to add a new argument, name the argument incidentARG

 

incidentARG

 

 

 

 

 

6) Choose Browse under Type and then click the Create Business Object Icon

 

CreateBO

 

 

 

 

 

7) Use the name IncidentBO and click the magnify icon choose a Destination Module

 

DestModule2

 

 

 

 

 

 

 

8) Click the Create Module icon and use the name Domain

 

Domain

 

 

 

 

 

 

 

 

 

 

 

 

9) Click OK twice to return back to the Create Business Object window

 

 

 

 

 

 

 

 

10) Select the checkbox Based on External Schema and the magnifying glass icon to choose a Type

 

TypeChooser

 

 

 

 

 

 

 

11) Click the Import Schema File icon, select the incidents.xsd schema file and OK

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

12) Click OK to localize the schema files to your composite project

 

localize

 

 

 

 

 

 

 

 

 

 

 

 

 

 

13) Select the Incident element from the Type Explorer and OK twice to return to Browse Types

 

type_explorer

 

 

 

 

 

 

 

 

 

 

 

 

14) Select the IncidentBO type and OK

 

IncidentBOSelect

 

 

 

 

 

 

 

 

15) To complete the In argument creation click OK

 

InArgumentFinal

 

 

 

 

 

16) Now click the output tab to define the GenericRequest type as a an Output

 

InArgComplete3

 

 

 

 

 

 

 

17) Using the same procedure as before create an output argument using the following values:

 

Output Argument Name GenericRequestARG
Type GenericRequestBO
Schema Filename content.xsd
Module Domain
Element GenericRequest

 

OutArg5

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

18) Click Finish to complete the initial definition of the GroovyDemoProcess BPM process.

 

DefinitionProcess

 

 

 

 

 

 

 

 

We have created a GroovyDemoProcess syncronous BPM process that has an Incident as a request and a GenericRequest as a response.

Next we need to define process variables based on the business objects that we have already created. These will be used to store the payload data in the BPM process.

 

19) Ensure the GroovyDemoProcess is selected in the Application Navigator, then  in the Structure Window right-click the Process Data Objects icon. Use the name incidentDO and select the IncidentBO as the Type.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

20) Similarly create another process data object called genericRequestDO of Type GenericRequestBO

 

GenericRequestDO

 

 

 

 

 

Performing Data Associations of the Data Objects

Now we have to assign the payload of the incidentARG argument to the data object we have just created. We do this in the Catch activity.

 

21) Right-click the Start catch activity and select Properties. Select the Implementation tab and click the Data Associations link.

 

DataAssociations

 

Now we need to assign the incidentARG argument to the incidentDO data object.

Since we have defined these to be the same type it is easy. All we need to do is a top level assignment and not even worry about optional sub-elements.

21) Drag from the incidentARG to the incidentDO nodes and click OK twice to complete and close the Start node property definition.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Now we need to associate the GenericRequestDO data object to the response.

This is in the Properties of the Throw End node.

22) Create a Copy association from the genericRequestDO to the GenericRequestARG nodes.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Defining the Groovy Expression in the BPM Script

Now at last we are ready to start defining the groovy code that will be responsible for the transformation.

Drag a Script Activity and place it just after the Start node. Re-name this to Transform Request

 

transform

 

 

 

 

 

 

 

 

 

 

 

 

 

Transform2

 

 

 

 

 

 

 

 

 

23) Right-click the Transform Request Script Activity and select Go To Script 

 

 

GoToScript

 

 

 

 

 

 

 

 

 

tip3Tip: The Script Activity must not have any implementation defined when it is being used for Groovy scripting. It functions as a container for the groovy script

 

Before we can start scripting we have to define the imports for the script, similar to what we would do in Java. First lets take a look at the Scripting Catalog to see what is already there. This will help us understand what we need to import.

 

24) In the Scripting Catalog expand the oracle–>scripting nodes to see what is already available to us.

 

Here we can see the Business Objects we have already created and all the elements that are included in the schema files that we imported.

 

ScriptingCatalog

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Now we need to recall what is the format of the GenericRequest that is the target data structure of our transformation. We need to know this so we can choose the correct imports for our Groovy script.

 

soapui

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Above we can see that a GenericRequest contains the following elements:

 

  • Service–>Document–>Field
  • Service–>Document–>File–>Contents

 

25) Now return back to the Scripting tab and enter the following in the Script Editor window. This as you can see are comments and printing output to the console. This will be seen directly in the weblogic server diagnostic log.

 

//You can add comments like this
//You can print to the console like during your development/testing procedures 
println("Starting transformation of Incident to Generic Request")

 

tip3Tip: printing to the console log like this should only be used in development scenarios and should be removed for production. Alternatively we could add some logic to conditionally log messages only by specifying a payload value or composite mbean.

 

Selecting the Scripting Imports

Now we need to add in the imports for the elements that we will be using.

26) Click the Select Imports button on the top right of the editor to open the Select Imports window

SelectImports

 

 

 

 

 

 

27) Click the green Add icon and click with the mouse cursor in the new import row that appears

 

SelectImports2

 

 

 

 

 

 

 

 

28) Type oracle. (oracle and a dot)

 

OracleDot

 

 

 

 

 

 

 

 

The context menu will now open up to help you find the correct package path.

 

ConextMenu

 

 

 

 

 

 

 

 

 

 

tip3Tip: Do not use the cursor keys until you have clicked inside the context menu with your mouse since this will cause the context menu to disappear.

 

29) Now use the cursor keys to choose the following oracle.scripting.xml.com.oracle.ucm.type.Service, or type it in directly and click the Add icon to add another import.

 

Imports

 

 

 

 

 

 

 

 

30) Add the following imports and click OK

 

oracle.scripting.xml.com.oracle.ucm.type.Service
oracle.scripting.xml.com.oracle.ucm.type.File
oracle.scripting.xml.com.oracle.ucm.elem.Field
oracle.scripting.xml.com.oracle.ucm.type.Service.Document

 

Writing the Groovy Expression

31) Return back to the Groovy Script editor window.

 

Now we need to define the classes we need to use to build our GenericRequest. We define a Service, Document, Field, File and two arrays for the lists of files & fields.

 

tip3Tip: In essence here we are just instantiating POGO (plain old groovy objects) objects that are a Groovy representation of our GenericRequest element

 

32) Now enter in the following code after the debug code you entered earlier

 

//Define the message element types for data population

//The Service element
Service service = new Service()
//The Document element
Document document = new Document()
//The File element (base64 message embedded attachment)
File file = new File()
//The filed element
Field field = new Field()
//An array of type Field
List<Object> fields = new ArrayList()
//An array of type File
List<Object> files = new ArrayList()

 

Now we have created our POGO objects. Now we need to populate them with real data. Since we are transforming from an Incident to a GenericRequest, most of our data comes from the data object incidentDO, which we have populated from the argument.

We will start by creating each of the individual Field elements and assigning them to the array, since these constitute the bulk of our message.

Our first field looks like this.

 

FirstField

 

It contains an XML Schema attribute called name and a value which is the Internal BPM process ID of the in flight process.

Type field.set (field dot set) in the expression editor to show the context list of the available methods for the field object. We can see that the methods to set and get data from the field POGO already exist.

 

FieldDot

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

32) Type in the following expression to populate the first Field element and add it to the array at position 0 (appears first in the message)

 

//sDocName element containing BPM process instance ID
field.setName("dDocName")
field.setInner_content(predef.instanceId)
fields.add(field)

 

tip3Tip: We could get the BPM process instance ID by executing an xpath expression in a data association. However BPM 12c conveniently provides several pre-defined variables which are available from predef, of which some can be also updated in a groovy expression.  See the full list here.

 

The next field that we need to populate in the GenericRequest is the dDocTitle, which comes from the incident subject.

The transformed element looks like this.

 

SecondField

 

This time we get the value from the process data object incidentDO by directly calling the get method.

 

33) Add the following expression to the end of the script.

 

//dDocTitle from the incident subject
field = new Field()
field.setName("dDocTitle")
field.setInner_content(this.incidentDO.subject)
fields.add(field)

 

Now this is really straight forward right? Actually, with the power of groovy expressions it really is.

Now imagine that you wanted to implement some complicated if/then logic to only conditionally display some elements. All you need to do is write some simple logic into the script. Perhaps you need to format some dates or concatenate some strings values or convert some data types, again easy as pie.

Consider the xincidentDate field below. Here we get a date and convert it into a Webcenter Content required format in a few lines.

 

ConvertDate

 

 

 

 

 

 

 

 

34) Now add the remaining field definitions to the expression.

 

field = new Field()
field.setName("dDocAuthor")
field.setInner_content(this.incidentDO.reporter)
fields.add(field)
   
field = new Field()
field.setName("dDocAccount")
field.setInner_content("incident");
fields.add(field)
  
field = new Field()
field.setName("dSecurityGroup")
field.setInner_content("webcenter")
fields.add(field)
  
field = new Field()
field.setName("dDocType")
field.setInner_content("Incident")
fields.add(field)
  
field = new Field()
field.setName("xClbraRoleList");
field.setInner_content(":CaseMgr(RW),:CaseWorker(RW),:ActionOfficer(RW)");
fields.add(field)
  
field = new Field()
field.setName("xClbraUserList");
field.setInner_content("&${this.incidentDO.getReporter()}(RW)");
fields.add(field)
  
field = new Field()
field.setName("xIdcProfile")
field.setInner_content("IncidentRecord")
fields.add(field)
  
field = new Field()
field.setName("xComments")
fields.add(field)
  
field = new Field()
field.setName("xCitizenName")
field.setInner_content(this.incidentDO.name);
fields.add(field)
  
field = new Field()
field.setName("xEMail")
field.setInner_content(this.incidentDO.email);
fields.add(field)
  
field = new Field()
field.setName("xCity")
field.setInner_content(this.incidentDO.city)
fields.add(field)
  
field = new Field()
field.setName("xGeoLatitude")
field.setInner_content(this.incidentDO.geoLatitude)
fields.add(field)
  
field = new Field();
field.setName("xGeoLongitude");
field.setInner_content(this.incidentDO.geoLongitude);
fields.add(field);

field = new Field()
field.setName("xIncidentDate")
Calendar nowCal = this.incidentDO.getDate().toGregorianCalendar()
Date now = nowCal.time
String nowDate = now.format('M/d/yy HH:mm aa')
field.setInner_content(nowDate)
fields.add(field)
  
field = new Field()
field.setName("xIncidentDescription")
field.setInner_content(this.incidentDO.description)
fields.add(field)
  
field = new Field()
field.setName("xIncidentStatus")
field.setInner_content(this.incidentDO.incidentStatus)
fields.add(field);
  
field = new Field()
field.setName("xIncidentType")
field.setInner_content(this.incidentDO.incidentType)
fields.add(field)
  
field = new Field();
field.setName("xLocationDetails")
field.setInner_content(this.incidentDO.locationDetails)
fields.add(field)
  
field = new Field()
field.setName("xPhoneNumber")
field.setInner_content(this.incidentDO.phoneNumber.toString())
fields.add(field)
  
field = new Field()
field.setName("xStreet")
field.setInner_content(this.incidentDO.street)
fields.add(field)
  
field = new Field();
field.setName("xStreetNumber");
field.setInner_content(this.incidentDO.streetNumber);
fields.add(field);
  
field = new Field()
field.setName("xPostalCode")
field.setInner_content(this.incidentDO.getPostalCode());
fields.add(field)
  
field = new Field()
field.setName("xTaskNumber")
field.setInner_content(this.incidentDO.taskNumber)
fields.add(field)

 

The next element to add is the embedded base64 attachment. We add this in a similar fashion.

 

34) Add the following expression.

 

file.setContents(this.incidentDO.attachment.file)
file.setName("primaryFile")
file.setHref(this.incidentDO.attachment.name)
files.add(file)

 

Now we are nearly finished our groovy script. All we need to do is:

 

  • Add the arrays to the Document element
  • Add the Document element to the Service element
  • Add the Service to the process data object genericRequestDO

 

35) Add the following expression for the Document, Service and gerericRequestDO

//Add Field and Files
document.setField(fields)
document.setFile(files)

//Add Document to Service
service.setDocument(document)
service.setIdcService("CHECKIN_UNIVERSAL")

//Add the Service element to data object genericRequestDO
genericRequestDO.setWebKey("cs")
genericRequestDO.setService(service)

 

The BPM script is now complete and your Studio Application should look similar to this.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Deploying the Process

Now we need to deploy the BPM process to our BPM server so we can test it. We are going to deploy to the new BPM 12c Integrated Weblogic Server that comes with studio, but another server can be used if preferred.

 

tip3If this is the first time deployment to the Integrated Weblogic Server then Studio will ask for parameters and then create the domain first before deployment.

 

36) In the Application Explorer Right-click the GroovyDemo project and select deploy–>GroovyDemo–>Deploy to Application Server–>Next–>Next–>IntegratedWeblogicServer–>Next–>Next–>Finish

 

deploy1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

deploy2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The deployment log should should complete successfully.

 

 

 

 

 

 

 

 

 

 

 

Testing the Deployed Process

Now it is time to test the process. We will invoke our BPM process through the web service test page.

37) Open a browser window and go to the Web Services Test Client page http://localhost:7101/soa-infra/ and login with the weblogic user.

Click on the Test GroovyDemoProcess.service link .

 

 

 

 

 

 

 

 

38) Click on the start operation

 

teststartopp

 

 

 

 

 

 

 

 

 

39) Click on the Raw Message button to enter a raw XML SOAP payload.

 

raw

 

In the text box paste the following sample Webcenter Content GenericRequest payload.

 

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:gro="http://xmlns.oracle.com/bpmn/bpmnProcess/GroovyDemoProcess" xmlns:v1="http://opengov.com/311/citizen/v1">
   <soapenv:Header/>
   <soapenv:Body>
      <gro:start>
         <v1:Incident>
            <v1:Name>Joe Bloggs</v1:Name>
            <v1:Email>joe.blogs@mail.net</v1:Email>
            <v1:PhoneNumber>12345</v1:PhoneNumber>
            <v1:Reporter>03a7ee8a-ae3f-428b-a525-7b50ac411234</v1:Reporter>
            <v1:IncidentType>Animal</v1:IncidentType>
            <v1:IncidentStatus>OPEN</v1:IncidentStatus>
            <v1:Date>2014-09-17T18:49:45</v1:Date>
            <v1:Subject>There is a cow in the road</v1:Subject>
            <v1:Description>I have seen a big cow in the road. What should I do?</v1:Description>
            <v1:GeoLatitude>37.53</v1:GeoLatitude>
            <v1:GeoLongitude>-122.25</v1:GeoLongitude>
            <v1:Street>500 Oracle parkway</v1:Street>
            <v1:StreetNumber>500</v1:StreetNumber>
            <v1:PostalCode>94065</v1:PostalCode>
            <v1:City>Redwood City</v1:City>
            <v1:LocationDetails>Right in the middle of the road</v1:LocationDetails>
            <v1:Attachment>
               <v1:File>aGVsbG8KCg==</v1:File>
               <v1:Name>hello.txt</v1:Name>
               <v1:Href/>
            </v1:Attachment>
         </v1:Incident>
      </gro:start>
   </soapenv:Body>
</soapenv:Envelope>

 

40) Click the Invoke button in the bottom right hand corner

 

invoke

 

 

 

 

 

 

 

 

41) Scroll down to the bottom to see the Test Results

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Congratulations! We can see that the Incident request we sent to Oracle BPM 12c has been transformed to a Webcenter Content GenericRequest using Groovy Scripting.

 

 tip3Tip: The Web Services Test Client is an lightweight method for testing deployed web services without using Enterprise Manager. For full instance debugging and instance details use Enterprise Manager or the Business Process Workspace

 

If we track this instance in Enterprise Manager we can see what happened at runtime in the Graphical form.

 

graph

 

 

 

 

 

 

 

 

 

 

 

 

 

We can also look at the log from the Integrated Weblogic Server in Studio, which shows the debug expression we included.

 

 

 

 

 

 

 tip3Tip: This process could be easily remodelled to be asynchronous or re-usable and the transformed GenericRequest could be used in the input association for a Service Activity to actually invoke the Webcenter Content SOAP Service.

The actual implemented process where this example comes from in the B2C Scenario looks like this. It is a reusable process that waits for the the upload to Webcenter Content to complete before querying the final documenting details and returning to the main BPM process.

CreateContent

 

 

 

 

Summary

In this blog we introduced Groovy BPM Scripting in BPM 12c. Firstly we learned how to model a synchronous BPM process based on predefined XML schema types.

We learned how to do the following using BPM Scripting:

  • Where and how we should use BPM scripting in a BPM process.
  • How to import classes
  • Instantiate and declare groovy objects
  • Print debug messages to the weblogic log file
  • Use process data objects
  • Use predefined variables
  • Format data
  • Dynamically build data object data structures
  • Programmatically transform data between different XML schemas types
  • Deploy and test using the Web Services Test Client tool

 

In the next blog in this series I will demonstrate how to define and use BPM scripting in Business Objects and Exception handling in BPM scripting.

 

tip3Tip: For more information on BPM Scripting (e.g. the list of predefined variables) see the section Writing BPM Scripts in the official BPM documentation

 

 

 

 

 

 

 

Add Your Comment