/ Development  

Playing with BPM-activity ownership

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

BPM, BPM…We use it when we really would like to build more complex features into the AppWorks platform. We create the most interesting things with it, but this time I saw something interesting passing by during one of my BPM reverse engineering activities. It all has to do with the ownership of an activity, and the person who is executing the activities. Again (as always), useful information to share with you which might be beneficial also for you project.


Let get right into it…

In the first place, we need 2 accounts to make this post work out. For my VM, I have account ‘awdev’ and ‘awtest’ on sale. ‘awdev’ has the ‘Developer’ role applied, and ‘awtest’ has the ‘Entity Runtime User’ role applied (so, he has an ‘Inbox’ to receive tasks in runtime).
On the developer side of things we start with a simple prototyped entity with a name project. Just add a couple of nice properties to it, and generate all the rest, so we can start creating new project instances in runtime. Before I forget…

  • This ‘project’ entity will also have the ‘Web services’ BB applied with an enabled “Read” operation.
  • It also requires a ‘Security’ BB where we make sure the ‘Identity user’ role (which is applied to all users by default!) has sufficient permissions to do something with our ‘project’ entity. Without this setting, our ‘awtest’ account won’t see any ‘project’ related information.

Have yourself a first publication and create your first project with the ‘awtest’ account. No fancy stuff here…All OOTB features.

The exposed “Read” operation requires an ‘Application server’ connector! It will create a new service container in the ‘System Resource Manager’ and makes sure requests for our operation will be managed by this service container. We explained it a couple of times already on this site, so have a search or comment me for a direction.

In the second place, we will enable auditing on our platform which will bring more insight to certain actions we play with during this post. For this we need access to the ‘/system’ organization (also known as the ‘shared’ space). We use the ‘sysadmin’ account and open the ‘Audit Configuration’ artifact. Here we will enable the ‘Entity Data Audit’ for later usage:

owner_001

Notes:

  • It’s a setting applied across organizations, so keep in mind the size of your data!
  • Don’t forget to also start (and enable for auto-start) the ‘Auditing’ service container in the ‘system’ space.
  • We also have this post available on audit usage.

With the above two sections in place, it’s time to craft our first BPM.


BPM creation (with entity ‘Action’ rule)

We need a simple 2-activities flow:

  1. Manual activity for an ‘awtest’ inbox task

Make it an interesting name like ‘Review’. Right-click the activity and insert an ‘Entity Layout’ to it. Just choose one as it doesn’t matter for our post (I choose the renamed ‘view_layout’ on my project entity!). With this insert we get a user-icon on the activity which indicates it’s a manual activity. Finally, we assign it “hardcoded” to our ‘awtest’ account.

owner_002

The value you see looks like this: 'cn=awtest@appworks,cn=organizational users,o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com'. Where did I get it from? Well, from the ‘User Manager’ artifact! Select a user and have a view in the status bar of the artifact.
NOTE: Don’t forget those single-quotes to make it a “String” input value!

  1. Auto activity for a call to our ‘Read’ project operation. Right-click the activity and insert a ‘Web service’. Select the exposed ‘Readproject’ webservice (managed by the created ‘AppServer’ service container!). We create a mapping between ‘rootEntityInstanceId’ and ‘ItemId’ in the message map of the BPM:

owner_003

This will be the end-result with a saved and published BPM named ‘bpm_review’ (saved in a ‘bpms’ folder of the project):

owner_004

Back to our ‘project’ entity where we create a new action type of ‘Rule’ BB. Give it a name ‘a_start_review’, and configure it like this (really simple):

owner_005

As we also have a ‘Security’ BB in place for our ‘awtest’ account, we also need to make the action rule available from the security point of view. Double-check this topic for yourself!

Publish it all…Time for an interesting test! 🤔


The test

These are the steps to be executed in runtime:

  1. With the ‘awdev’ account, “create and open” a new (or open an existing) instance of the project entity.
  2. Once the instance is open, we have the ability to hit our action rule ‘Start review’
  3. Switch to the ‘awtest’ account, have a look in the inbox, and complete the task:

owner_006

  1. Back to our ‘awdev’ account, only this time in design-time where we open our valuable PIM artifact. Find your BPM instance and have a look at an image like this one:

owner_007

Now for the great question! Who executed the ‘Readproject’ webservice activity!? Well, let’s first have a look at our auto-activity again, and more specific the properties of the activity in the ‘General’ tab. Here we see a setting for ‘Execution User Type’ which has a value of ‘Current User’. Who is this mysterious “current user” at this moment in BPM time as it’s an automatic service activity!? After some research, it looks like the current user equals the “current owner” of the BPM instance. Have a look again in the PIM on the message map for the instance:

owner_007

Click on the ‘instanceProperties’ element, and have a look at an interesting (and unexpected) value:

owner_008

So, our ‘awtest’ account became the owner of the BPM instance, and our ‘Readproject’ is also executed under this account…WHAT? Is this really the case? Well, that’s where our audit trail will give a final conclusion. Open the ‘Audit Viewer’ artifact with a search like this (and our solving answer!):

owner_009

Ok, but what if this account doesn’t have the permission to execute/read this information!? Well, we could switch the ‘Execution User Type’ on the activity:

owner_010

Guess who is responsible at that moment in time with that same test? Correct!…Our ‘awdev’ account (if you noticed the ‘startedBy’ elements in the screenshots above)! What if this account also doesn’t have any permission!? Yes, my friends…That’s where the fun starts! 😜

Don’t forget to undo this last setting. So, the execution user type is ‘Current User’ for the next section!


The “fun” part of the post

What do we try to accomplish here? Well, it’s the execution of a service call under a given account! So, when we start a BPM activity (like that service call), we make sure this service executes from a “wrapper” message assigned to a certain account. This way our “wrapper” is called (as service) in the BPM activity with the current user (or the process initiator as we’ve seen). In the wrapper method itself we execute the real service call under the given account…Does this make sense? Keep reading as it took me a while before it fell in place!

For this section we require some Java coding. There are other posts available (like this one) which explain how to start developing with AppWorks dependencies via a Maven project. All interesting stuff to dive into, but we skip it for this post, and I assume you have some IntelliJ, Java, and Maven knowledge.

Create a Maven IntelliJ “BpmUtils” project like this (my AppWorks platform is running Java 11, so use the same Java version for your project!):

owner_011

Now create a new class nl.bos.BpmUtils with this content part (keep on reading for the code-explanation):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package nl.bos;

import com.cordys.cpc.bsf.busobject.exception.BsfApplicationRuntimeException;
import com.eibus.applicationconnector.email.util.SOAPException;
import com.eibus.applicationconnector.email.util.SOAPWrapper;
import com.eibus.xml.nom.Node;

import java.util.logging.Level;
import java.util.logging.Logger;

public class BpmUtils {
private static final Logger LOGGER = Logger.getLogger(BpmUtils.class.getName());

public static int readProject(String userName, String organization, String itemId) {
LOGGER.log(Level.INFO, String.format("Start readProject {userName: %s, organization: %s, itemId: %s}", userName, organization, itemId));

SOAPWrapper soapWrapper = null;
try {
soapWrapper = new SOAPWrapper();
soapWrapper.setUser(userName);
soapWrapper.setOrganization(organization);
int soapMethod = soapWrapper.createSoapMethod("Readproject", "http://schemas/appworkstipsgeneric/project/operations");

int projectId = Node.createElementWithParentNS("project-id", "", soapMethod);
Node.createElementWithParentNS("ItemId", itemId, projectId);

LOGGER.log(Level.INFO, "End readProject");

return soapWrapper.sendAndWait(soapMethod);
} catch (SOAPException e) {
throw new BsfApplicationRuntimeException(e.getMessage(), e);
} finally {
if(soapWrapper != null) {
soapWrapper.freeXMLNodes();
}
}
}
}

You can get a lot of missing dependencies which are part of the AppWorks platform. This can easily be fixed by making sure you have a copy of the platform libraries locally on your disk:

1
2
3
4
5
6
7
8
/opt/opentext/AppWorksPlatform/defaultInst/certificates
/opt/opentext/AppWorksPlatform/defaultInst/components
/opt/opentext/AppWorksPlatform/defaultInst/config
/opt/opentext/AppWorksPlatform/defaultInst/crosscontext
/opt/opentext/AppWorksPlatform/defaultInst/ext
/opt/opentext/AppWorksPlatform/defaultInst/lib
/opt/opentext/AppWorksPlatform/defaultInst/applicationservercp.jar
/opt/opentext/AppWorksPlatform/defaultInst/cordyscp.jar

Once you have them locally; you can install them into your local Maven dependency repository like this (these are the only 4 dependencies required for this post):

1
2
3
4
mvn install:install-file -Dfile=C:\CORDYS_21_4\components\emailconn\emailconn.jar -DgroupId=com.cordys -DartifactId=email -Dversion=21.4 -Dpackaging=jar
mvn install:install-file -Dfile=C:\CORDYS_21_4\components\wsappserver\wsappserver.jar -DgroupId=com.cordys -DartifactId=wsappserver -Dversion=21.4 -Dpackaging=jar
mvn install:install-file -Dfile=C:\CORDYS_21_4\components\eibxml\eibxml.jar -DgroupId=com.cordys -DartifactId=eibxml -Dversion=21.4 -Dpackaging=jar
mvn install:install-file -Dfile=C:\CORDYS_21_4\components\basicutil\basicutil.jar -DgroupId=com.cordys -DartifactId=basicutil -Dversion=21.4 -Dpackaging=jar

From you Maven project ‘pom.xml’ perspective, you can now add these dependencies (all ‘provided’ by the platform):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependencies>
<dependency>
<groupId>com.cordys</groupId>
<artifactId>email</artifactId>
<version>21.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.cordys</groupId>
<artifactId>wsappserver</artifactId>
<version>21.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.cordys</groupId>
<artifactId>eibxml</artifactId>
<version>21.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.cordys</groupId>
<artifactId>basicutil</artifactId>
<version>21.4</version>
<scope>provided</scope>
</dependency>
</dependencies>

After reloading the dependencies within your project, you should be fine again without any errors!

Next step is to add a wrapper interface class nl.bos.BpmUtilsInterface

Why? Well, later on we’ll play with a document of type ‘Java Class Metadata’, but this one is not always that keen on dependencies (this way we work around the import dependencies)!

1
2
3
4
5
6
7
package nl.bos;

public class BpmUtilsInterface {
public static int readProject(String userName, String organization, String itemId) {
return BpmUtils.readProject(userName, organization, itemId);
}
}

To understand what where coding here, we’ll have a look at the exposed ‘Readproject’ webservice to match the code. Use the ‘Web Service Interface Explorer’ for a ‘Test’ call:

owner_012

You will see the XML-part passing by (Have a look again in the BpmUtils code…Do you see the similarities!?):

1
2
3
4
5
6
7
8
9
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<Readproject xmlns="http://schemas/appworkstipsgeneric/project/operations">
<ns0:project-id xmlns:ns0="http://schemas/appworkstipsgeneric/project">
<ns0:ItemId>PARAMETER</ns0:ItemId>
</ns0:project-id>
</Readproject>
</SOAP:Body>
</SOAP:Envelope>

So, now you see what our code in doing…right? Well, if not:

  • Creating an interesting SOAPWrapper object which contains the soap message we can send out like we also do with the ‘Web Service Interface Explorer’. It can “hold” the user executing the thing!
  • So, on the wrapper we set tree things; the user, the organization, and the soap message to be called
  • The soap message requires some input like the ‘ItemId’ which we can pass in via the ‘Node.createElementWithParentNS()’ calls
  • Finally, we send it out (under the given account), and we wait for a response. We could also do a fire-and-forget call (all up to you!)

How about the password for the user? Well, that’s the beauty! You don’t need it! The only thing you do need is the correct runtime security…Keep reading…

Time, for building a JAR with: mvn clean package. The end-result will be a file called BpmUtils-1.0-SNAPSHOT.jar to be found in the ‘target’ folder of the project. This is my file.

Upload the file to your VM and make it available like this:

1
2
3
4
mkdir /opt/opentext/AppWorksPlatform/defaultInst/custom
cp ~/BpmUtils-1.0-SNAPSHOT.jar /opt/opentext/AppWorksPlatform/defaultInst/custom/
sudo chmod 777 -R /opt/opentext/AppWorksPlatform/defaultInst/custom
sudo chown tomcat:tomcat -R /opt/opentext/AppWorksPlatform/defaultInst/custom

We now use a quick-and-dirty way to expose a custom JAR to the platform. If you follow my posts, you know there is a more elegant way to do so. Have a search for ‘java archive definition’.

Our JAR file is available on the platform, time for a new service container of type ‘Ws-Application Server’. Have a look here how to accomplish the task.

Once available we need to add this string: /opt/opentext/AppWorksPlatform/defaultInst/custom/BpmUtils-1.0-SNAPSHOT.jar:/opt/opentext/AppWorksPlatform/defaultInst/components/emailconn/emailconn.jar to the JRE classpath of the WS-AppServer service container:

owner_013

With this change we are required to restart TomEE on our VM: systemctl restart tomee

Why do we need this service container? In the next part we’re going to play with a new document type which makes it possible to generate webservices out of Java-code! Now you guess who is responsible to manage those service calls? Indeed, correct…The WS-AppServer!

When we’re back online, we can create a new document of type ‘Java Class Metadata’ (nicely saved in the ‘metadata’ folder of our project).

owner_014

Use this as “Java Archive Path”: /opt/opentext/AppWorksPlatform/defaultInst/custom/BpmUtils-1.0-SNAPSHOT.jar

Once saved, have a look in your project, right-click the new document and generate webservices out of it:

owner_015

Behind the red arrow, you can define your parameter value with descriptive names!
Have also a look in the second tab ‘Web Service Interface Details’ for values like this (simply good practices!):
owner_015

Hit the ‘Finish’ button, and on project level we can now publish the webservice. Can we evaluate it now? Nope, as we first need to connect the interface to our ‘WS-AppServer’ service container!

owner_016

Once this is in place, we can do a first test with a call like this:

1
2
3
4
5
6
7
8
9
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<readProject xmlns="http://schemas.bos.nl/services">
<userName>cn=awdev@appworks,cn=organizational users,o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com</userName>
<organization>o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com</organization>
<itemId>080027e84f06a1ec96fbf27f8ca01035.1</itemId>
</readProject>
</SOAP:Body>
</SOAP:Envelope>
  • userName: Copy it from the “User Management” artifact
  • organization: Copy from the same user (from a BPM perspective, it can be retrieved from the ‘processInstanceProperties’)
  • itemId: Copy it from the URL of my ‘project’ instance in runtime for now (from within BPM, we can use again the ‘processInstanceProperties’)

Do also a second call with an update on the username ‘awtest@appworks’. Double-check both calls from the ‘Audit viewer’:

owner_017

NICEEE! ✅

Let’s also have a look at this log file (as you saw also in the code!): sudo tail -999f /opt/tomee/latest/logs/catalina.out

1
2
INFO BpmUtils.readProject Start readProject {userName: cn=awdev@appworks,cn=organizational users,o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com, organization: o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com, itemId: 080027e84f06a1ec96fbf27f8ca01035.1}
INFO BpmUtils.readProject End readProject

To finish the post we’ll update the already created BPM where we’ll replace the current ‘Readproject’ service call, with the new wrapper ‘readProject’ service call. Before we make this update, make sure to set the runtime security of our brand-new service. Right-click the interface of our new service and define the runtime security.

owner_018

Add a new functional role ‘resource’ (nicely saved in the ‘roles’ folder of the project), and mark the ‘Execute’ permission for the role:

owner_018

The only thing we need to do now is make sure our ‘awtest’ account gets the assignment to this role from within the ‘User Manager’ artifact. The ‘awdev’ account has the ‘Developer’ role applied and doesn’t need it!

Did you notice something strange? In this runtime security I was not able to select the ‘Identity User’ as role…Interesting! I also can’t imagine why that would be as the ‘Identity User’ can definitely be used in the runtime security for a BPM!? A bug? Let me know in comments…

Time to update the BPM with our new service. Well, you know the drill; this consolidated view of the message map should provide sufficient information to complete the task:

owner_019

You see I make a mapping from ‘instanceProperties.startedBy’ to ‘readProject.userName’ as it already contains a valid username value, but it could be any name!

We make the BPM configuration to do this trick:

  • ‘awdev’ starts the BPM
  • ‘awtest’ gets the inbox task. After completion, he will be owner of the BPM!
  • Make sure our service activity executes with the current owner ‘awtest’ because of the ‘Execution User Type’. Because we pass the ‘awdev’ account, the real service call executes under this provided account!

Well, where are we waiting for…Publish and test in runtime! Monitor the PIM, the audit viewer, and the ‘catalina.out’ log-file! 😅

BIG NOTE: As far as I could see, the result of our call can only be a primitive value like ‘int’ or ‘short’ (check the code-part again!) to check for successfully execution. Just a small disadvantage because we can also…

  • Update/Delete entity data under another account
  • Update a related ‘ProcessData’ entity where we can save the state of our call for further processing
  • Call any other SOAP related service like we’ve done in my current project for SOAP service ‘UpdateTaskData’!

Use your own creativity as every AppWorks SOAP service is possible to be called this way. It’s up to you what magic service this will be!

On top of the 🎂, I also would like to share a 🍒 which is a SOAP call I used during my own exploration for this post. An interesting call as it makes it possible to retrieve inbox information from a specific user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Body>
<GetHumanTasks xmlns="http://schemas.cordys.com/notification/workflow/1.0">
<Query xmlns="http://schemas.cordys.com/cql/1.0">
<Select>
<QueryableObject>TASK_INSTANCE</QueryableObject>
<Field>TaskId</Field>
<Field>ParentTaskId</Field>
<Field>SourceInstanceId</Field>
<Field>State</Field>
<Field>ProcessName</Field>
<Field>Activity</Field>
<Field>Priority</Field>
<Field>Target</Field>
<Field>Sender</Field>
<Field>Assignee</Field>
<Field>DeliveryDate</Field>
<Field>StartDate</Field>
</Select>
<Filters>
<And>
<EQ field="SHOW_BUSINESS_ATTRIBUTES">
<Value>false</Value>
</EQ>
<EQ field="RETURN_TASK_DATA">
<Value>false</Value>
</EQ>
<EQ field="Target">
<Value>user:cn=awtest@appworks,cn=organizational users,o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com</Value>
</EQ>
</And>
</Filters>
<Cursor position="0" numRows="100" maxRows="100"/>
</Query>
</GetHumanTasks>
</SOAP:Body>
</SOAP:Envelope>

This service is part of the older platform features and falls in the interface category “Method Set Notification 1.0” which is still pretty useful when playing with tasks in your solution. Just for you to know and hereby also written down for my own registration! 😀


It’s a great coding “DONE” this time, where we learned how to execute a SOAP service under a different account. It all started by exploring the ‘Execution User Type’ setting which tells us to execute an activity under the account. This account owns the BPM at that moment versus the account who initiated the BPM at that time. We saw a limitation and produced an interesting piece of Java code where we make use of a wrapper object with the name SOAPWrapper. This object can execute a service under a given account name. Well, grab your advantage with this information on your own project and share your interesting findings in the comments. I CU next week in another post on a new AppWorks Tips installment.

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?