This blog describes a way to optimize performance of a custom ACM/BPM API worklist client implementation. This is optimization recipe is applicable in a specific use case. Before running of and implementing this recipe, please first consider if the recipe applies to your situation.
The example that is used is a service for retrieving the caseIds of running ACM composite instances.
Some of the code is inspired by Niall Commiskey’s blog ( http://niallcblogs.blogspot.nl/2014/01/304-adaptive-case-management-java-api.html) – thanks Niall!
The example is on 11g, but also can be done similarly in 12c.
Many of you are familiar with how to access the ACM/BPM/SOA platform API services. A typical situation is that where a BPM worklist client is built in some non-Oracle technology, and the communication with the BPM processes/tasks is done via the platform API ( http://docs.oracle.com/cd/E28280_01/dev.1111/e10224/bp_worklistcust.htm#SOASE839) .
Also typical in that situation, is to not let the BPM worklist client use the platform API directly. Instead, some API abstraction layer is built in between, where the platform API services are simplified or combined. Goal of such API abstraction layer is to offer services that are more tailored to the need of the BPM worklist client:
Such use case requires authentication/authorization. Often approached as …
- the BPM API worklist client does authentication/authorization for a specific end user
- the BPM system is a back-end system that is not aware of the end users of the BPM API worklist client
- so … the ‘end user’ is passed as a parameter towards the API abstraction layer
- the API abstraction layer uses the ‘weblogic’ user for accessing the API
- … and for each operation is implemented in 2 steps:
- authenticate as ‘weblogic’ user
- access the API, using the end-user or other parameters to access the right functionality / instances
The use case is illustrated below:
So, from functional point of view, we’re OK. Until the number of service calls start to go up. In the above situation, a ‘BPM logon’ is done for each service invoke. That can partly be handled by tuning the cache for the involved directory (LDAP) server, but still: the WorkflowContext has to be established for each operation. And then you decide … you need to cache the WorkflowContext!
A solution to implement caching in the API abstraction layer is to use Stateless EJBs that are deployed as a web service:
- A Stateless Session EJB can have multiple operations. The lifecycle of a Stateless Session EJB is described here ( https://docs.oracle.com/cd/E23943_01/web.1111/e13719/session.htm#EJBPG282) . After an instance of this EJB is created, it will process all invokes on its operations.
- The Stateless Session EJB is managed by WebLogic: it will create / destroy runtime instances of this bean: the more the Stateless Session EJB is used, the more instances WebLogic creates.
- An instance of a Stateless Session Bean
Development can be done in 2 ways:
- Develop an EJB and deploy it as a web service
- Generate the EJB from a WSDL
@1: The resulting WSDL depends on the generation process of the web service;
Focus is on the API logic and the resulting code is very straightforward
@2: WSDL driven development will ensure that the service contract is fully under control;
The generated code has to be examined for side effects of the code generation process
Both ways are well supported in JDeveloper: we will pick number 2, because we want to limit the chance of inadvertently breaking the service contract (and becoming incompatible with the BPM worklist client).
Create the project
Start by making a ‘Generic Application’ project in JDeveloper with the technologies selected as below:
Leave all other settings at default.
Create the WSDL, XSD
For our web service, we need a WSDL and XSD that describe its contract. Put them in the src directory of the project:
The WSDL has an operation for getting the caseIds:
<operation name="getCaseData"> <input message="tns:getCaseData"/> <output message="tns:getCaseDataResponse"/> </operation>
The XSD specifies the corresponding data types:
<xs:element name="getCaseData" type="tns:getCaseData"/> <xs:element name="getCaseDataResponse" type="tns:getCaseDataResponse"/> <xs:complexType name="getCaseData"> <xs:sequence> </xs:sequence> </xs:complexType> <xs:complexType name="getCaseDataResponse"> <xs:sequence> <xs:element name="cases" type="tns:caseDataList"/> </xs:sequence> </xs:complexType> <xs:complexType name="caseDataList"> <xs:sequence> <xs:element name="case" type="tns:caseData" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:complexType name="caseData"> <xs:sequence> <xs:element name="caseId" type="xs:string"/> </xs:sequence> </xs:complexType>
Generating the EJB and Web Service code from the WSDL
Now, the EJB can be generated from the WSDL.
Start by right-mouse click on the WSDL file and then select ‘create web service’
On the first wizard page, click Next.
On the ‘Select Web Service Description’ page, select ÉJB 3.0′ for the Service Type:
On the ‘Specify Default Mapping Options’ page, give the package names:
On the ‘Configure Policies’ page, leave the default settings and click Next.
On the ‘Provide Handler Details’ page, leave the default settings and click Next.
The click Finish to generate the ejb and web service:
In the above figure, you will notice the additional code for 2 operations that were added to the WSDL:
- helloworld : doing its thing
- getCacheExpiryTime : which will return the time at which the cache (implementation shown below) expires
The libraries/jars that are shown below have to be added to the project. Right-click the project, go to ‘project properties’ –> ‘Libraries and Classpath’:
Creating the cache
Now, the cache for the WorkflowContext can be designed:
- upon initialization, the ‘cache refresh period’ and the ‘cache expiry time’ are set
- the initCache method updates the ‘cache expiry time’ and sets 4 cached objects:
- a BPMServiceClientFactory
- a IBPMUserAuthenticationService
- a ICaseService
- a IBPMContext
Hook up the cache to the EJB
The EJB is implemented in class CaseApiServiceImpl. In this class:
- a variable of type Cache is added
- a PostConstruct annotation is added, where the cache is created/initialized after the EJB instance is created
- a PreDestroy annotation is added, where the cache can be cleared before the EJB instance is destroyed
Implementing the business logic
In our example, the Business Logic concerns the retrieval of a list of Case instance Ids. This business logic will be coded in a class named ‘CaseInstanceLogic’.
Each business logic method will have – at least – the cache as an argument. In that way, a business logic implementation can use the objects in the cache.
In our case, the method for the retrieval of the case ids:
Invoke the business logic from the EJB
The business logic can now be invoked from the EJB. In the – generated – method getCaseData in the Bean class CaseApiServiceImpl, the invoke can be added:
Now the code is completed.
For deployment, some consideration needs to be given to the jars that the EJB needs. It is possible to use the brute force approach and include in the deployment all the jars that are needed for the EJB to work. However, under the assumption that this EJB is deployed on the same host as where ACM/BPM is running, there is a more elegant way: Oracle has deployed the libs that are needed as ‘shared libraries’. Therefore, a reference to these shared libraries is sufficient. So, when the environment is patched, the patches will also immediately be available to our service!
On project level, nothing needs to be changed:
- the default deployment profile does not have to be changed
- an ejb jar file is created that contains the required class files
On application level, a new deployment profile has to be generated. Start by right-clicking the application, and then Deploy –> New Deployment Profile
In the Deployment Profile, select ‘EAR File’ and click OK:
For the ‘Deployment Profile Name’, enter a meaningful name like ‘CaseApiService’ and click OK:
Leave all remaining values at default.
The project level ejb jar file now has to be included in the application ear.
Goto the ‘Application Properties’ by right-clicking the application. Click the Edit button for the CaseApiService deployment profile:
Goto ‘Application Assembly’ and check the ‘EjbWebServices’ check box, and click OK:
The final thing that needs to be done is to add the right library references to the META-INF/weblogic-application.xml deployment descriptor on the ear (application) level. First, create the deployment description.
Goto File –> New, select ‘Deployment Descriptors’ – ‘WebLogic Deployment Descriptor’ and click OK:
In the next wizard screen, select ‘weblogic-application.xml’ and click Finish:
Edit the file for adding the required library references. It has to look like:
Deploy and Test
The application can be deployed and tested with the test client:
Changes in the future: re-generation from changed WSDL
Future changes in the WSDL require a re-generation of the EJB:
After this re-generation, you should carefully examine how the re-generated EJB bean code looks like. An automated unit test suite is recommended.
The following considerations for this approach:
- The WebLogic application server creates / destroys bean instances based on how many invocations there are. Each bean has its own cache with instances of the cached objects. That is nice: if the beans were to share one single cache, that could create a bottle neck.
- The EJB bean pool size should be tuned in WebLogic. E.g., consider to set the minimum pool size to 1, instead of the default 0.
- In the example, the WebLogic credentials are hard coded. Production quality code should not do that. A good way to handle this topic is to use the CSF: Credential Store Framework…
- In the example, a simple and robust mechanism what used for determining the cache validity period. More efficient mechanism can be envisaged – but simplicity has its charm …
The source code for this example can be found here: CaseApiService