This chapter describes how to use Oracle Enterprise Scheduler to invoke asynchronous Java jobs to support long-running or non-container-managed jobs that invoke Java code.
This chapter includes the following sections:
Normally Oracle Enterprise Scheduler Java job requests run inside Oracle WebLogic Server in a dedicated thread; however, there are cases that require the ability to submit long running or non-container managed Java job requests.
Oracle Enterprise Scheduler supports asynchronous Java job invocation with the following features:
From the Oracle Enterprise Scheduler user point of view there is no difference in scheduling asynchronous Java job invocation.
From Oracle Enterprise Scheduler perspective, the asynchronous Java job invocation job request is submitted and is added to the queue, and returns immediately after running (and the job request enters the RUNNING
state). Oracle Enterprise Scheduler continues operating until it hears back from the job at which point Oracle Enterprise Scheduler can apply post-processing or complete the job.
Asynchronous Java jobs begin any variety of external jobs outside of Oracle Enterprise Scheduler. The external job, or the entity that manages it, must communicate the status of the job to Oracle Enterprise Scheduler.
An Oracle Enterprise Scheduler asynchronous Java job consists of an Oracle Enterprise Scheduler job request and an external mechanism. The Oracle Enterprise Scheduler job request is implemented similarly to a standard Oracle Enterprise Scheduler Java job request; however, unlike a standard Oracle Enterprise Scheduler request, an asynchronous Java job request might not do any work, depending on the scenario. The only purpose of an asynchronous Java job request is to trigger the external mechanism. The external mechanism executes the payload (monitoring a database, calculating pi, or any other long lived process), and must be separable from the thread running the Oracle Enterprise Scheduler Java job. The external mechanism can be a SOA composite (BPEL) or asynchronous Oracle ADF Business Components web service, another thread, JVM, machine, or some other mechanism. The means of communication between the external mechanism and the client application is left to the job owner. However, an important point for the asynchronous Java job is that the pointer to the physical Java object representing the asynchronous job is not stored in Oracle Enterprise Scheduler memory. This is because:
The job can run for an indeterminate amount of time and caching this handle is a waste of resources.
Long lived jobs should be able to survive container restarts. Because this object is not cached and most likely garbage collected, the job should be stateless and its submitting application is responsible for maintaining the correlation between job requests and the external mechanisms running them. Oracle Enterprise Scheduler provides the job request ID and job request handle for this reason. This information should be persisted in order to survive restarts.
An asynchronous Java job invocation must implement the AsyncExecutable
interface.
The duty of an asynchronous Java jobs's execute()
method is to set up the external mechanism in which the real work runs; this should start the external mechanism and then return. The asynchronous Java job invocation execute()
method may not do any actual work. An exception can be thrown during the execute method to tell Oracle Enterprise Scheduler that this job had a problem during initialization and failed to run. The exception during the execute method does not tell Oracle Enterprise Scheduler that the actual work running on the external mechanism encountered a problem. It is the responsibility of the job owner to make sure any resources that may have been started or used are released, since Oracle Enterprise Scheduler does no further processing if it catches an exception. Assuming no exception is thrown, Oracle Enterprise Scheduler puts the job into the running state and then releases the handle on the job's object so that it may be garbage collected.
An asynchronous Java job can set web service addressing headers to simplify the work of the remote job.
The WSA messageID
header is used to correlate the response message with the request. Oracle Enterprise Scheduler provides the method RequestExecutionContext.getIdString
, which returns an ID to be used for the value of the WSA messageID
header.
The WSA ReplyTo
and FaultTo
headers can be used to direct replies to the Oracle Enterprise Scheduler generic callback service. There is currently no Oracle Enterprise Scheduler support for obtaining these addresses.
Oracle Enterprise Scheduler provides a web service operation for asynchronous callbacks, setAsyncRequestStatus
(see the interface in Example 16-15). It requires typed information such as status and the status message, as well as the correlation information to be explicitly given.
Oracle Enterprise Scheduler provides another mechanism: a generic Java Required Files web service provider for asynchronous callbacks. The web service provider accepts payloads of any type, and messages are delivered as SOAPMessage
objects. The WSA relatesTo
header is extracted so as to correlate the message with the request. This header is populated with the WSA messageID
header of the original request. The Action
header is used to determine whether the response is due to the completion of the asynchronous job or a fault. If the response is due to a fault, the asynchronous job request status is provisionally set to ERROR
. If the response is due to the successful completion of the asynchronous job, the asynchronous job request status is provisionally set to SUCCESS
. The SOAPMessage
body is extracted and converted to a string which is passed to the Updatable.onEvent
method.
The web service provider address is http://<
host
>:<
port
>/ess-async/essasynccallback
.
Oracle Enterprise Scheduler provides the interface oracle.as.scheduler.Updatable
, which allows the job request to receive update events initiated by the application code. When a job request is updated, Oracle Enterprise Scheduler determines whether the client class implements the Updatable
interface. If the client class does implement the Updatable
interface, it instantiates a new object of the job class and calls the onEvent
method in the context of the MDB of the hosting application. This method accepts the request status as determined by the web service invocation and a string representing information in a format known to the job, for example, the SOAPMessage
body from the Oracle Enterprise Scheduler web service. This method may log information or do some other processing. It then returns an UpdateAction
object including a status and a status message.
The call to onEvent
occurs in the context of the user associated with the execution of the request.
If the job does not implement the Updatable
interface, the event is processed based on the status passed to onEvent
, for example, the status determined from the asynchronous callback to Oracle Enterprise Scheduler.
For more information about the Updatable interface, see Example 16-12.
There are two ways to notify Oracle Enterprise Scheduler when an asynchronous job completes:
Using a web service interface.
Using an EJB interface.
When you invoke the Oracle Enterprise Scheduler web service operation, setAsyncRequestStatus
, this sets the asynchronous request's status and associated information. Associated with this operation, the following pieces of information are needed:
setAsyncRequestStatus(String requestExecutionContext, AsyncStatus status, String statusMessage)
Where:
requestExecutionContext
is a string that should be passed in as part of the initiating event. This parameter is derived from the Oracle Enterprise Scheduler job's RequestExecutionContext
object.
status
is one of the following: SUCCESS
, ERROR
, WARNING
, PAUSE
, CANCEL
, BIZ_ERROR
or UPDATE
.
statusMessage
is:
An error message if the status is ERROR
or BIZ_ERROR
.
A warning message if the status is WARNING
.
A paused state if the status is PAUSED
.
A customized string you define and have the job interpret accordingly if the status is UPDATE
.
The value is ignored if the status is SUCCESS
or CANCEL
.
For more information about implementing a web service in a web application, see the chapters "Integrating Web Services Into a Fusion Web Application" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework and "Securing and Administering WebLogic Web Services" in Oracle Fusion Middleware Security and Administrator's Guide for Oracle Web Services.
When an asynchronous Java jobs's execute()
method is successful and the job request is running on the external mechanism, Oracle Enterprise Scheduler continues processing other jobs. When the job request is complete or encounters an error, it must communicate back to its submitting application. This communication channel is the responsibility of the agent and the client application owners. The submitting application then communicates the status of the job to Oracle Enterprise Scheduler through a local EJB. This EJB will also have a remote interface, so alternatively the external mechanism may invoke the remote EJB itself. The EJB sets the job status and does any appropriate post-processing. A helper class is provided which encapsulates all the EJB references. This helper only works when it is used inside the container since the helper uses dependency injection. The helper class contains methods for communicating success, errors, warnings, and cancellations.
If you want the job to be cancellable, you must also implement the AsyncCancellable
interface. This interface differs from the normal cancellable interface in that its cancel method also provides the RequestExecutionContext
and the RequestParameters
for that job. The provided context and parameters should be used to determine which external mechanism is running the payload and then ask it to stop. The external mechanism (rather than the job's AsyncCancellable.cancel()
implementation) notifies Oracle Enterprise Scheduler that the job has been cancelled.
Note:
Currently, there is no way to terminate a running asynchronous Oracle ADF Business Components web service process.Using an asynchronous request you can invoke a BPEL process from Oracle Enterprise Scheduler. An asynchronous Oracle Enterprise Scheduler Java job is used to invoke the BPEL process. When the BPEL process completes, whether successfully, with an error or warning, or if it is canceled, the BPEL process notifies Oracle Enterprise Scheduler using a Oracle Enterprise Scheduler web service operation.
This method for invoking a BPEL process involves the following steps:
Create an asynchronous Oracle Enterprise Scheduler Java job.
Invoke a BPEL process from the Oracle Enterprise Scheduler Java job.
When the BPEL process is done, call back to the Oracle Enterprise Scheduler web service with the completion status. Use the web service operation method to inform Oracle Enterprise Scheduler of the request completion. For more information, see Section 16.2.6.1, "Using the Web Service to Notify When an Asynchronous Job Completes".
Once Oracle Enterprise Scheduler has the completion information, it will complete any required post-processing of the request (if required).
You can invoke the associated web service directly or you can publish an event telling the event mediator to start the BPEL process, as shown in Example 16-1.
Example 16-1 Job that Initiates a BPEL Process Through an Event Mediator
import oracle.as.scheduler.RequestParameters; import oracle.as.scheduler.ExecutionCancelledException; import oracle.as.scheduler.ExecutionErrorException; import oracle.as.scheduler.ExecutionPausedException; import oracle.as.scheduler.ExecutionWarningException; import oracle.as.scheduler.RequestExecutionContext; import javax.xml.namespace.QName; import oracle.fabric.blocks.event.BusinessEventConnection; import oracle.fabric.blocks.event.BusinessEventConnectionFactory; import oracle.fabric.common.BusinessEvent; import oracle.integration.platform.blocks.event.BusinessEventBuilder; import oracle.integration.platform.blocks.event.BusinessEventConnectionFactorySupport; import oracle.xml.parser.v2.XMLDocument; import org.w3c.dom.Element; // Async imports import oracle.as.scheduler.AsyncExecutable; import oracle.as.scheduler.AsyncCancellable; public class BPELJob implements AsyncExecutable, AsyncCancellable { public BPELJob() { } public void execute(RequestExecutionContext ctx, RequestParameters params) throws ExecutionErrorException, ExecutionWarningException, ExecutionCancelledException, ExecutionPausedException { // Publish an event to the Event Mediator publishEvent(ctx.getRequestId() + "", ctx.toString(), "ESS_EVENT"); } // Cancel public void cancel (RequestExecutionContext ctx, RequestParameters requestParams) { publishEvent(ctx.getRequestId() + "", ctx.toString(), "CANCEL_ESS_EVENT"); return; } // cancel // Event publishing private final String eventName = "ESSDemoEvent"; private final String eventElement = "ESSDemoEventElement"; private final String eventNamespace = "http://xmlns.oracle.com/apps/ta/essdemo/events/edl"; private final String schemaNamespace = "http://xmlns.oracle.com/apps/ta/essdemo/events/schema"; private XMLDocument buildEventPayload(String correlationId, String key, String eventType) { Element masterElem, childElem1, childElem2, childElem3; XMLDocument document = new XMLDocument(); masterElem = document.createElementNS(schemaNamespace, eventElement); document.appendChild(masterElem); childElem1 = document.createElementNS(schemaNamespace, "requestId"); childElem1.appendChild(document.createTextNode(correlationId)); masterElem.appendChild(childElem1); childElem2 = document.createElementNS(schemaNamespace, "executionContext"); childElem2.appendChild(document.createTextNode(key)); masterElem.appendChild(childElem2); childElem3 = document.createElementNS(schemaNamespace, "eventType"); childElem3.appendChild(document.createTextNode(eventType)); masterElem.appendChild(childElem3); return document; } private void publishEvent(String correlationId, String key, String eventType) { try { // Get event connection BusinessEventConnectionFactory cf = BusinessEventConnectionFactorySupport. findRelevantBusinessEventConnectionFactory(true); if (cf != null) { BusinessEventConnection conn = cf.createBusinessEventConnection(); // Build event BusinessEventBuilder builder = BusinessEventBuilder.newInstance(); // Specify the event name and namespace. In this prototype, // they are constants, eventNamespace, eventName builder.setEventName(new QName(eventNamespace, eventName)); // Specify the event payload. In this prototype, the // getXMLPayload custom method constructs the payload builder.setBody(buildEventPayload(correlationId, key, eventType).getDocumentElement()); BusinessEvent event = builder.createEvent(); // Publish event conn.publishEvent(event, 5); // For debug only System.out.println("Event was sent sucessfully"); } else { // For debug only System.out.println("cf is null"); } } catch (Exception exp) { // For debug only System.out.println("Failed sending event: " + exp.getMessage()); exp.printStackTrace(); } } // publishEvent }
You can use an asynchronous Java job to run a BPEL process. The process initiated by an event, handled by the Event Mediator which starts the process. For an example, see Figure 16-1.
The real work of the process is done in the DoMyWork module.
If the work completes successfully, control will flow to AssignAsyncSuccess/AsyncCallbackSUCCESS, which invokes the Oracle Enterprise Scheduler web service callback specifying SUCCESS
for the status and no status message.
If the Oracle Enterprise Scheduler request is canceled, the Oracle Enterprise Scheduler job's cancel method will be called. The job object would then notify the remote job that it should be canceled. If the cancel succeeds, the remote job notifies Oracle Enterprise Scheduler using the callback mechanism, setting the status to CANCEL. In this case, control would jump to the branch on the far right.
If a fault occurs, control will jump to the middle branch. AsyncCallbackERROR invokes the Oracle Enterprise Scheduler web service callback specifying ERROR
for the status and an error message from the fault. AsyncCallbackCANCEL invokes the Oracle Enterprise Scheduler web service callback specifying CANCEL
for the status and no status message.
Figure 16-1 Java Job to Call a BPEL Process and Return with Asynchronous Request
In the BPEL process, you need the web service operation values to the Oracle Enterprise Scheduler asynchronous callback, as shown in Figure 16-2, Figure 16-3, and Figure 16-4 for the AssignAsyncError
assignment activity.
Figure 16-2 AsyncCallBackError Argument Mapping for statusMessage Element
Figure 16-3 AsyncCallbackError Argument Mapping for requestExecutionContext
Figure 16-4 AsyncCallbackError Argument Mapping for status Element
Use cases for implementing a BPEL process as an asynchronous job are as follows:
Gaining approval for a task using human workflow notifications and other SOA-specific activities.
Notifying Oracle Enterprise Scheduler that a job has completed, while allowing other jobs to run or proceed to the next job in a set.
Asynchronous Oracle Enterprise Scheduler jobs are Java jobs that implement the AsyncExecutable
interface, which is invoked by Oracle Enterprise Scheduler by implementing the execute()
method. This method enables initiating a long running or remote task where the execute()
method completes (such as raising a business event), while Oracle Enterprise Scheduler keeps the job in RUNNING
status. The remote task completes and notifies Oracle Enterprise Scheduler of its completion using a status message using one of the following implementations:
The RuntimeService
EJB
The Oracle Enterprise Scheduler web service setAsyncRequestStatus
operation.
This pattern assumes the remote task to be invoked is a BPEL process which is triggered by raising a business event in the execute()
method of the asynchronous job. Upon termination of the process through completion, error or cancellation, the BPEL process invokes the Oracle Enterprise Scheduler web service and sets the status accordingly.
Oracle Enterprise Scheduler, SOA Meditator and BPEL, as shown in Figure 16-5.
Figure 16-5 BPEL Call from Oracle Enterprise Scheduler Asynchronous Job
There are use cases where Oracle Enterprise Scheduler jobs need to invoke BPEL processes in a bi-directional fashion to track completion of that BPEL before moving on to other jobs. As invoking asynchronous web services from Java code (Oracle Enterprise Scheduler or Oracle ADF Business Components) in Oracle Fusion Applications is prohibited, an Oracle Enterprise Scheduler job cannot invoke an asynchronous BPEL process directly and must rely on the asynchronous job implementation type.
This approach is recommended because it leverages existing functionality in Oracle Fusion Middleware, such as events and BPEL.
Instead of the asynchronous Oracle Enterprise Scheduler job functionality, the following approaches are possible but not allowed:
Invoking asynchronous web services such as Oracle ADF Business Components or BPEL via JAX-WS proxies - blocked threads and callback services are disallowed in Oracle Enterprise Scheduler.
Raising a business event to trigger BPEL, BPEL invokes an Oracle ADF Business Components service which invokes the RuntimeService EJB to set the status, a complex and error prone procedure.
An Expenses system has a periodic Oracle Enterprise Scheduler job which runs to import and process expenses which requires submission of BPEL processes to leverage Human Workflow for notification and approvals. In this use case, an Oracle Enterprise Scheduler job would be responsible for importing the expenses and lines and submitting subrequests for each expense to trigger the asynchronous BPEL functionality per expense. This subrequest is implemented as an asynchronous Oracle Enterprise Scheduler job which raises a business event, completing it's Java execute() method, and staying in a running state while BPEL is initiated, submits the Human Task notification and awaits the outcome from user interaction. Once this outcome is obtained, BPEL invokes the Oracle Enterprise Scheduler web service signaling that this particular subrequest is completed.
Implementing an Oracle Enterprise Scheduler asynchronous job in BPEL requires performing the following steps:
Author the Oracle Enterprise Scheduler Java job to implement the AsyncExecutable
and AsyncCancellable
interfaces by writing execute()
and cancel()
methods.
Create the asynchronous Oracle Enterprise Scheduler job definition.
Design the event payload schema (XSD) and event definition (EDL) files.
Programmatically raise a business event from the asynchronous Oracle Enterprise Scheduler job execute() and (optionally) cancel methods.
Design the SOA Composite with Meditator and BPEL.
Add fault handling and correlated onMessage
branch for error and cancel job status updates.
In your Oracle Enterprise Scheduler Application, be sure to add the Applications Core, and Enterprise Scheduler Service Oracle JDeveloper libraries and create a new Java class with appropriate class naming and directory structure (per standards) which will implement both the Oracle Enterprise Scheduler AsyncExecutable
and AsyncCancellable
interfaces. Importing both of these interfaces require you to implement the execute()
and cancel()
methods which Oracle Enterprise Scheduler RuntimeService
bean invokes to initiate the desired behavior in your Oracle Enterprise Scheduler job, as shown in Example 16-2.
Example 16-2 Adding Oracle JDeveloper Libraries
public class ASMEventAsyncJob implements AsyncExecutable, AsyncCancellable { public ASMEventAsyncJob() { super(); } public void execute(RequestExecutionContext ctx, RequestParameters params) throws ExecutionErrorException, ExecutionWarningException, ExecutionCancelledException, ExecutionPausedException { publishEvent(ctx.getRequestId() + "", ctx.toString(), "ESS_EVENT"); return; } public void cancel (RequestExecutionContext ctx, RequestParameters requestParams) { publishEvent(ctx.getRequestId() + "", ctx.toString(), "CANCEL_ESS_EVENT"); return; } // cancel
In your Oracle Enterprise Scheduler JDeveloper workspace, click "New', choose the Enterprise Scheduler Service technology group and select "Job Definition". Enter the name off your Oracle Enterprise Scheduler job definition, choose the provided "JavaJobType" and select the class build in step 1 as the overriding Java class for this job definition, as shown in Figure 16-6.
Now choose the class developed in Step 1 as the overriding Java class for this job definition, define parameters and access control as required by your use case, as shown in Figure 16-7.
Figure 16-7 Create Job Definition with Job Type Defined
The SOA composite designer has UI features to assist in designing business event payload definitions (EDL); however your schema (.xsd) will need to be designed first. Example 16-3 shows a sample XSD file.
<?xml version="1.0" encoding="UTF-8" ?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://xmlns.oracle.com/apps/ta/essdemo/events/schema" targetNamespace="http://xmlns.oracle.com/apps/ta/essdemo/events/schema" attributeFormDefault="unqualified" elementFormDefault="qualified"> <xsd:element name="ESSDemoEventElement" type="ESSDemoEventElementType"/> <xsd:complexType name="ESSDemoEventElementType"> <xsd:sequence> <xsd:element name="requestId" type="xsd:string"/> <xsd:element name="executionContext" type="xsd:string"/> <xsd:element name="eventType" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:schema>
With the payload element type completed, you can either create the EDL by hand or use the event definition builder. To use the builder, open the SOA composite editor and click the lightning bolt icon at the top of the UI to open the Event Definition Creation window, as shown in Figure 16-8
Next, assign a name and namespace and click Add to add a new event to this definition, as shown in Figure 16-9.
Click OK. The event definition summary displays the completed event definition. Add more events as needed for your requirements, as shown in Figure 16-10.
Example 16-4 shows a sample of the EDL file that is created.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <definitions xmlns="http://schemas.oracle.com/events/edl" targetNamespace="http://xmlns.oracle.com/ AsyncEssDemoComposite/EventDefinition1"> <schema-import namespace="http://xmlns.oracle.com/singleString" location="xsd/singleString.xsd"/> <schema-import namespace="http://xmlns.oracle.com/apps/ta /essdemo/events/schema" location="xsd/ESSDemoEventSchema.xsd"/> <event-definition name="ESSEvent"> <content xmlns:ns1="http://xmlns.oracle.com/apps/ta/essdemo/events/schema" element="ns1:ESSDemoEventElement"/> </event-definition> </definitions>
The business event raised from the asynchronous Oracle Enterprise Scheduler job must contain the request execution context's toString()
value in order for BPEL to indicate which job is completed/cancelled/errored. Programmatically Raising Business Events from Java is covered in the "Initiating SOA from ADF" section which contains the specifics on how to write Java code that raises business events. You will need to design an event schema (.xsd) and definition (EDL) in order to declaratively build the SOA composite which will subscribe to this raised business event. Your Java code must create this XML document from scratch and it must exactly match QName values such as element and namespace attributes in the payload structure.
Note that your execute()
method is invoked when Oracle Enterprise Scheduler starts to run your job, when an end user or external entity instructs Oracle Enterprise Scheduler to cancel the running job, Oracle Enterprise Scheduler sets the job's status to 'CANCELLING" and will then invoke the cancel() method. It's recommended that both methods raise events that contain similar payload types/namespaces so correlation sets can be used and the cancel event can be sent to the in-flight BPEL process in order to have it perform alternative functionality and then invoke the Oracle Enterprise Scheduler web service to set the job status to 'CANCELLED'.
This sample places the event raising code in the Oracle Enterprise Scheduler job's class code, however, the best approach is to share the code as an Oracle ADF Library which you can then import into this project to reduce duplication of publishing code.
Sample code calling the event raising code passing in requestID
(for the BPEL correlation set to allow in-flight cancel) and the execution context's toString()
value:
publishEvent(ctx.getRequestId() + "", ctx.toString(), "ESS_EVENT");
Sample event raising code is shown in Example 16-5.
Example 16-5 Event Raising Code
private final String eventElement = "ESSDemoEventElement"; private final String eventNamespace = "http://xmlns.oracle.com/apps/ta/essdemo/events/edl"; private final String schemaNamespace = "http://xmlns.oracle.com/apps/ta/essdemo/events/schema"; private XMLDocument buildEventPayload(String correlationId, String key, String eventType) { Element masterElem, childElem1, childElem2, childElem3; XMLDocument document = new XMLDocument(); masterElem = document.createElementNS(schemaNamespace, eventElement); document.appendChild(masterElem); childElem1 = document.createElementNS(schemaNamespace, "requestId"); childElem1.appendChild(document.createTextNode(correlationId)); masterElem.appendChild(childElem1); childElem2 = document.createElementNS(schemaNamespace, "executionContext"); childElem2.appendChild(document.createTextNode(key)); masterElem.appendChild(childElem2); childElem3 = document.createElementNS(schemaNamespace, "eventType"); childElem3.appendChild(document.createTextNode(eventType)); masterElem.appendChild(childElem3); return document; } public void publishEvent(String correlationId, String key, String eventType) { // Determine whether we are outside of a JTA transaction try { // Get event connection BusinessEventConnectionFactory cf = BusinessEventConnectionFactorySupport.findRelevantBusinessEventConnectionFactory (true); if (cf != null) { BusinessEventConnection conn = cf.createBusinessEventConnection(); // Build event BusinessEventBuilder builder = BusinessEventBuilder.newInstance(); // Specify the event name and namespace. In this prototype, // they are constants, eventNamespace, eventName builder.setEventName(new QName(eventNamespace, eventName)); // Specify the event payload. In this prototype, the // getXMLPayload custom method constructs the payload builder.setBody(buildEventPayload(correlationId, key, eventType).getDocumentElement()); BusinessEvent event = builder.createEvent(); // Publish event conn.publishEvent(event, 5); // For debug only System.out.println("Event was sent sucessfully"); conn.close(); } else { // For debug only System.out.println("cf is null"); } } catch (Exception exp) { // For debug only System.out.println("Failed sending event: " + exp.getMessage()); exp.printStackTrace(); } } // publishEvent }
Since this use case depends on BPEL functionality it is necessary to build a SOA composite which contains a Mediator for event subscription which can then transform the payload and initiate the BPEL process.
In your SOA workspace, create a new SOA composite. To setup the composite for this pattern, add a Mediator that subscribes to your Oracle Enterprise Scheduler raised event and wire it to a BPEL process. Add a service reference to the Oracle Enterprise Scheduler web service WSDL. For example,
http://myhost.com:7001/ess/esswebservice?WSDL
Continue to build the required functionality in the BPEL process using one or more nested scopes. Bear in mind that your functionality should reside within at least one primary scope on which you can add an onMessage
event (for in-flight cancel message receipt) and fault handler branches, as shown in Figure 16-11.
Figure 16-11 Composite with BPEL and ESSWebService
For more information about invoking the Oracle Enterprise Scheduler web service, see Chapter 11, "Using the Oracle Enterprise Scheduler Web Service."
Oracle Enterprise Scheduler does not perform any sort of heartbeat monitoring of asynchronous Oracle Enterprise Scheduler jobs after the execute()
method's Java code has completed. Once the job is submitted it exists in a RUNNING
state within the Oracle Enterprise Scheduler infrastructure until the remote job code, BPEL, or end user interacts with Oracle Enterprise Scheduler directly to set the status of the job. Because of this caveat, developers need to design their BPEL processes to handle, at a minimum, two types of scenarios that will most often occur in the life span of an Oracle Enterprise Scheduler job and, whenever possible, push that state information back to Oracle Enterprise Scheduler so monitoring UIs can reflect the correct state of the job to end users.
For example, if the end user interacts with the monitoring UI and requests that the job be cancelled Oracle Enterprise Scheduler will then update the job's status to CANCELLING
and wait for the remote functionality to tidy up and confirm that it has cancelled, as shown in Figure 16-12.
Additionally, when the remote functionality encounters a failure, the responsibility to notify Oracle Enterprise Scheduler of this failure falls on the shoulders of the remote functionality (in this case, BPEL) to notify Oracle Enterprise Scheduler that the job's status is ERROR
and provide a status message in addition to any logging that was performed. This is illustrated in Figure 16-13.
In order to acknowledge cancellation and arbitrate proper status back to the Oracle Enterprise Scheduler infrastructure, BPEL must be designed within a certain layout to support receipt of the incoming cancellation message and trapping of any failures such that, in either case, the Oracle Enterprise Scheduler subsystem can be updated. For this purpose, in the BPEL Process, there should be at least one scope which will contain the functionality for this asynchronous job. This will allow sufficient control for handling cancel and error states which must then be sent to the Oracle Enterprise Scheduler web service in order to update the job's status in the Oracle Enterprise Scheduler runtime.
To build the basic process flow to support these states, the following steps should be completed in order:
Create the correlation set and flag it for imitate on the incoming Receive activity.
Create the onMessage
branch with use of correlation set created in sub-step 1.
Create the fault handling branch.
Populate the onMessage
and fault handling branches with cleanup activities as needed and invoke the Oracle Enterprise Scheduler web service with appropriate status.
In order to support receiving the cancel event while the BPEL process is in the middle of performing other activities or waiting for an asynchronous callback the process must be configured with a correlation set. A correlation set is key value that is built from one or more incoming payload attributes which are used to uniquely identify the BPEL process to the BPEL engine whereby additional service requests that contain matching sets of attributes can be routed to the process that is currently running instead of initiating a new one. While correlation is standard functionality used for asynchronous request responses, it can also be used to change the flow of execution in a BPEL process through scope-level onMessage
branches.
To setup the correlation set, open the BPEL process in the designer, double-click the Receive activity and click the correlations tab.
Note that coarctation sets have an "initiate" property which indicates which activity will be the starting point for this correlation set's life cycle. In this case, the start of the BPEL process will be the point at which the correlation set's life cycle should begin allowing correlated events to route to this process at any point during the process.
To create a correlation set:
Click the "New" icon in the Correlations tab of any Receive, Invoke or onMessage
activity and provide a name for the correlation set.
Next, click "Add" to define one or more property attributes to use as the correlation key.
Choose a variable attribute as the set property and click "OK".
Repeat steps 2 and 3 as necessary to build an attribute set that will always be unique.
Set the initiate flag on the correlation to "Yes" on the activity for which the correlation set's life cycle should begin.
Primary (first) Receive Activity with Defined Correlation Set and "Initiate" flagged to "Yes", as shown in Figure 16-14.
Figure 16-14 Correlations for Receive Activity
CorrelationSet_1 definition with a single property defined (define more as needed to ensure unique keys are created), as shown in Figure 16-15.
Once the correlation set has been defined and set for initiate it's now possible to create the onMessage
branch on the scope which will contain the activities necessary to accept the incoming cancellation message, perform any compensation or cleanup and then assign the job's completion status to CANCEL
.
Note:
At this point, theonMessage
branch could contain the invoke activity or finish allowing a higher order scope to perform the invoke, reducing the overall number of necessary invoke activities in the flow.The following steps guide you through adding the previously created correlation set to the onMessage
branch activity, as shown in Figure 16-16.
On the nested scope containing the process functionality, click the 'Add onMessage branch' icon which should create a new flow off to the side of the scope.
Double-click the onMessage
branch activity to open the activity editor.
Choose the "Correlations" tab.
Click the Add '+' icon and select the previously created correlation set ensuring that the initiate flag is set to 'No' and click "Ok".
Through the course of performing the various activities in the nested work scope BPEL may encounter faults from business services or system functionality. In most cases, business services will define one or more WSDL-defined faults that can be thrown back to the calling process. Ordinarily, a BPEL CatchAll fault branch will trap any and all faults that are raised regardless of their type and origin but there may be cases where product teams have requirements to perform different sets of behavior in response to specific business faults. In cases where it's desirable to perform unique compensation behavior for specific business faults, the developer should create a named fault handling branch for each WSDL-defined fault. In addition to these named fault handler branches, it is still necessary to add a CatchAll fault handling branch to trap any system level or unmanaged faults that are raised from the scope.
Click the CatchFault and CatchAll scope icons to create the desired fault handling branches, then double-click the named fault handling branches and define the named fault those branches will catch.
Note the available status, as shown in Figure 16-17.
You need to populate the onMessage and Fault branch with cleanup activities as needed and invoke Oracle Enterprise Scheduler web service with appropriate status.
In the event of a fault or receipt of the cancellation message through the onMessage branch the Oracle Enterprise Scheduler infrastructure needs to be updated directly via the Oracle Enterprise Scheduler web service in order to reflect the job's status and status message properly in the monitoring UIs. As a result, each fault handling or onMessage branch should assign the correct status and status message value to the Oracle Enterprise Scheduler web service invoke variable and optionally contain the invoke activity or, by design, return to a higher order scope which is designed to be agnostic to the outcome of the job status and will perform the invoke activity on the Oracle Enterprise Scheduler web service before completing.
Additionally, drag activities into the onMessage and fault branches as needed to cleanup/log/compensate.
Example scope with onMessage and Fault handling branches is shown in Figure 16-18.
To test that the functionality works you must perform the following sequence of steps:
Turn on the EDN-DB-LOG page by navigating to the following site to make sure it reads "Log is Enabled". If not, click the link for "Enable",
http://host:port/soa-infra/events/edn-db-log
Submit your job through your own application, Fusion Middleware Control the task flow user interface for submitting job requests and confirm that the status of the job is RUNNING
.
Your event should immediately show up in the EDN-DB-LOG page. Check for this event payload, as shown in Example 16-6.
Example:Enqueing event: http://xmlns.oracle.com/apps/ta/essdemo/events/edl::ESSDemoEvent from J Body: <business-event xmlns:ns="http://xmlns.oracle.com/apps/ta/essdemo/events/edl" xmlns="http://oracle.com/fabric/businessEvent"> <name>ns:ESSDemoEvent</name> <id>df8e34c1-4c65-4379-b9be-2c692670ebbe</id> <content> <ESSDemoEventElement xmlns="http://xmlns.oracle.com/apps/ta/essdemo/events/schema"> <requestId>3</requestId> <executionContext>3, false, null, 6A4A16757764CD60E0402382B7703F44, 12</executionContext> <eventType>ESS_EVENT</eventType> </ESSDemoEventElement> </content> </business-event> Subject name: Enqueing complete Enqueing event: http://xmlns.oracle.com/apps/ta/essdemo/events/edl::ESSDemoEvent from J Body: <business-event xmlns:ns="http://xmlns.oracle.com/apps/ta/essdemo/events/edl" xmlns="http://oracle.com/fabric/businessEvent"> <name>ns:ESSDemoEvent</name> <id>a4104da8-5579-4434-ab8b-d31a226e3b0f</id> <content> <ESSDemoEventElement xmlns="http://xmlns.oracle.com/apps/ta/essdemo/events/schema"> <requestId>4</requestId> <executionContext>4, false, null, 6A4A2BC7E5477C60E0402382B77041C9, 12</executionContext> <eventType>ESS_EVENT</eventType> </ESSDemoEventElement> </content> </business-event>
Your subscribing mediator will have been triggered, you can check Fusion Middleware Control ($DOMAIN_HOME/as.log) or soa-diagnostic logs ($DOMAIN_HOME/servers/<serverName>logs/<serverName>.log) to see any mediator activity as a result of your event, as shown in Example 16-7.
Example 16-7 Mediator Activity
INFO: MediatorServiceEngine received an event = {http://xmlns.oracle.com/apps/ta/ess/demo/events/edl}ESSDemoEvent Apr 17, 2009 1:57:26 PM oracle.tip.mediator.common.persistence.MediatorPersistor persistCallback INFO: No call back info set in incoming message Apr 17, 2009 1:57:26 PM oracle.tip.mediator.common.persistence.MediatorPersistor persistCallback INFO: Message properties : {id=041ecfcf-8b73-4055-b5c0-0b89af04f425, tracking.compositeInstanceId=50003, tracking.ecid=0000I2pqzVCBLA5xrOI7SY19uEYF00004g:47979} Apr 17, 2009 1:57:26 PM oracle.tip.mediator.dispatch.InitialMessageDispatcher dispatch INFO: Executing Routing Service.. Apr 17, 2009 1:57:26 PM oracle.tip.mediator.dispatch.InitialMessageDispatcher processCases INFO: Unfiltered case list size :1 Apr 17, 2009 1:57:26 PM oracle.tip.mediator.monitor.MediatorActivityMonitor createMediatorCaseInstance INFO: Creating case instance with name :ESSDemoProcess.essdemoprocess_client.process Apr 17, 2009 1:57:26 PM oracle.tip.mediator.dispatch.InitialMessageDispatcher processCase INFO: Immediate case {ESSDemoProcess.adedemoprocess_client.process}with case id : {5B52B4A02B9211DEAF64D3EF6E2FB21D}will be executed Apr 17, 2009 1:57:26 PM oracle.tip.mediator.service.filter.FilterFactory createFilterHandler INFO: No Condition defined
Check the Oracle Enterprise Manager Fusion Middleware Control Console for an instance of your SOA composite and check for errors.
http://host:port/em
If your BPEL process has not errored and is expecting a response from the human workflow notification, navigate to the worklist, login as the assigned approver and approve or reject the notification per your design requirements.
From here, the BPEL process should complete and invoke the Oracle Enterprise Scheduler web service to set the job's completion status and status message. Check the monitoring UI diagnostic logs for stack traces and log messages.
Additionally, you can check the REQUEST_HISTORY
table in the Oracle Enterprise Scheduler schema for details on your job's state.
To troubleshoot issues with the Oracle ADF UI functionality such as the monitoring and submission task flows use the server's console log, applications log and server diagnostic logs for information on what is failing and why.
To troubleshoot issues with the events functionality, such as the event not reaching the BPEL process with request execution context intact, use the EDN database log page (http://host:post/soa-infra/events/edn-db-log
) to inspect the event payload and carefully compare it to the schema definition, even slight mismatches can cause the transformation to 'succeed' but produce an skeleton payload to BPEL which is missing any request context values. Oracle JDeveloper and third-party tools can be used to validate the schema of the event payload and debug the transformation against that payload.
To troubleshoot the mediator, BPEL SOA functionality, use the Oracle Enterprise Manager and server console or diagnostics log files for diagnostics and AppsLogger Sensor variables for logging.
For more information about troubleshooting Oracle Enterprise Scheduler at runtime, see the chapter "Troubleshooting Oracle Enterprise Scheduler" in Oracle Fusion Middleware Administrator's Guide for Oracle Enterprise Scheduler.
Oracle Enterprise Scheduler asynchronous Java jobs depend on the remote job to update Oracle Enterprise Scheduler with its completion status before it can finish processing the request. Due to the nature of remote communication, there may be cases where Oracle Enterprise Scheduler does not receive the remote request status because of network failures, and so on. In these cases, the request may be stuck in a non-terminal state.
Transitioning a timed out request to a terminal state is important as it:
Frees any incompatibility locks held by that job request.
If the job request is a job set step, allows the job set to continue.
If the request is a subrequest, allows the parent request to resume.
Allows the job request to be deleted or purged.
An Oracle Enterprise Scheduler system property, SystemProperty.ASYNC_REQUEST_TIMEOUT
, enables setting job request time out values for asynchronous Java jobs. By default, the property is not enabled, such that its value is less than or equal to zero.
The property may be set in the job definition metadata or when the job request is submitted. The value represents the duration, in minutes, from the time the job request begins local execution until a terminal asynchronous job status is received from the remote job.
For a given asynchronous job request, set the system property SystemProperty.ASYNC_REQUEST_TIMEOUT
to a value greater than 0.
For a given request, RequestDetail.isTimedOut
indicates the status of the time out. Requests that have timed out can be discovered using the query shown in Example 16-8.
Example 16-8 Indicating the Time Out Status
Filter timedOutRunningFilter = new Filter( RuntimeService.QueryField.TIMED_OUT.fieldName(), Filter.Comparator.EQUALS, Boolean.TRUE) .and( RuntimeService.QueryField.STATE.fieldName(), Filter.Comparator.EQUALS, State.RUNNING.value()); runtimeService.queryRequests(handle, timedOutRunningFilter, null, true);
A similar query can be run using REQUEST_HISTORY_VIEW
, as shown in Example 16-9.
In the absence of a time out value, asynchronous requests whose remote job has completed without delivering the status to Oracle Enterprise Scheduler may be completed directly using RuntimeMXBean.completeAsyncRequest
. Because there is no time out value to flag the request as needing attention, you must carefully track requests without time outs.
For more information about managing job requests without time outs, see the chapter "Troubleshooting Oracle Enterprise Scheduler" in Oracle Fusion Middleware Administrator's Guide for Oracle Enterprise Scheduler.
Oracle Enterprise Scheduler periodically checks for asynchronous job requests on which the property SystemProperty.ASYNC_REQUEST_TIMEOUT
has been set. When the time has exceeded without a terminal status having been received, the job is flagged as timed out. Otherwise, the job state is unaffected, and remains in a RUNNING
state. Meanwhile, Oracle Enterprise Scheduler continues to accept status updates from the remote job. The flag indicates that the status of the remote job may need to be investigated.
If the remote job completed but its status was not delivered to Oracle Enterprise Scheduler, you can complete the request manually.
In some cases, the status of a job status cannot be determined automatically, such that it is unknown whether or not a job is executing, for example. If the job is executing, the job request must not transition to a terminal state. If the job does transition to a terminal state, incompatibility locks could be released, possibly causing incompatible job requests to run simultaneously.
For example:
An asynchronous Java job encounters an error when starting a remote service, such that it is unclear that the remote service has actually been invoked. The job request must not go to an error state until it is determined whether the remote job is running. If the job might be running, the job should throw an oracle.as.scheduler.ExecutionManualRecoveryException
to indicate to Oracle Enterprise Scheduler that the job request must transition to ERROR_MANUAL_RECOVERY
state.
An Oracle Enterprise Scheduler asynchronous Java job throws a java.lang.Error
which does not indicate to Oracle Enterprise Scheduler whether the remote service has been invoked.
A spawned job is running in a clustered environment, with the job request running on Oracle Enterprise Scheduler instance1. The Oracle Enterprise Scheduler instance1 server goes down, along with the associated Perl agent. If instance1 is not going to recover for a while, the job status is unknown. The property State.ERROR_MANUAL_RECOVERY
is used for this type of situation. This is a non-terminal state that suspends processing on a job request until a recovery operation is manually invoked. Any incompatibility locks acquired will be retained until manual recovery completes.
For more information about handling asynchronous jobs marked for manual recovery, see the section "Handling Stuck Asynchronous Jobs Requiring Manual Recovery" in the chapter "Troubleshooting Oracle Enterprise Scheduler" in Oracle Fusion Middleware Administrator's Guide for Oracle Enterprise Scheduler.
If some job requests are stuck in an incomplete state, it should first be determined whether the job requests can complete by normal means. For instance, if a job request is in RUNNING
state, it may be for an asynchronous Java job running remotely. If the remote job is unable to respond, then you must try to cancel the job request. This transitions the job request to CANCELLING
state. If the job request does not transition to CANCELLED
state, then it may be a candidate for recovery.
All child requests of the request to be recovered must have already completed, meaning that its process phase is ProcessPhase.Complete
. You can retrieve the process phase by executing RequestDetail.getProcessPhase()
.
Using RuntimeService.queryRequests
, you can run a query to determine incomplete child requests using the filter shown in Example 16-10.
Example 16-10 Filtering for Incomplete Child Requests
Filter filter = new Filter(RuntimeService.QueryField.ABSPARENTID.fieldName(), Filter.Comparator.EQUALS, requestId) .and(RuntimeService.QueryField.REQUESTID.fieldName(), Filter.Comparator.NOT_EQUALS, requestId) .and(RuntimeService.QueryField.PROCESS_PHASE.fieldName(), Filter.Comparator.NOT_EQUALS, ProcessPhase.Complete.value());
If it is determined that any child requests require manual recovery, then invoke recoverRequest
for those jobs first. If recoverRequest
is invoked on a parent request with incomplete child requests, an exception will be thrown. The exception message will list child requests that are incomplete. Example 16-11 shows the recoverRequest
syntax.
/** * Attempts to force a request to complete under certain conditions. * <p> * 1. The request must already by in a terminal state, {@code * State.CANCELLING}, or {@code State.ERROR_MANUAL_RECOVER}. * If a request is in another state, * {@code RuntimeService.cancel} must be called first. If the * request does not eventually transition to {@code State.CANCELLED}, * then this operation may be invoked on the request. * 2. All child requests of the given request must already be complete. * <p> * A <b>completed></b> request is a request in a terminal state with * a process phase of {@code ProcessPhase.Complete}. * <p> * Note that this operation will lock the request. * <p> * @param requestId the request identifier of the request. * @throws IOException if a protocol error occurred. * @throws InstanceNotFoundException if the request is not found * @throws OperationException if the given request has child requests * that are not complete. * @throws RuntimeOperationsException if a RuntimeService subsystem failure * occurs. */ public void recoverRequest( long requestId ) throws IOException, InstanceNotFoundException, OperationsException, RuntimeOperationsException;
For more information about manually handling synchronous Java jobs, see the section "Handling Synchronous Java Jobs Requiring Manual Recovery" in "Troubleshooting Oracle Enterprise Scheduler" in Oracle Fusion Middleware Administrator's Guide for Oracle Enterprise Scheduler.
Sample code illustrating the new Oracle Enterprise Scheduler asynchronous callback interfaces and classes are shown in Example 16-12, Example 16-13, Example 16-14 and Example 16-15.
Example 16-12 Oracle Enterprise Scheduler Updatable Interface
public interface Updatable { /** * Invoked by Enterprise Scheduler when a job request is updated. * This method must eventually return control to the caller. * * @param context An oracle.as.scheduler.RequestExecutionContext * object for this request. * * @param parameters the request parameters associated with this request * * @param resultCode the {@code * oracle.as.scheduler.async.UpdateAction.ActionCode} indicating the * action that generated this event. * * @param messagePayload a {@code String} representing the body of this * event. The content and format are not known by the Enterprise Scheduler. */ public UpdateAction onEvent( RequestExecutionContext context, RequestParameters parameters, oracle.as.scheduler.async.AsyncStatus resultCode, String messagePayload ); }
The UpdateAction
class is returned by Updatable.onEvent
.
Example 16-13 Oracle Enterprise Scheduler UpdateAction Class
package oracle.as.scheduler.async; /** * Enumeration of return values from application execution callout. The * action returned determines how the subsequent processing of the request * will proceed. */ public class UpdateAction { /** * Constructor. Creates an UpdateAction object from the status * and message components. * * @param status Indicates the status of execution of this update event. * This status may result in a state transition for the request. * * @param message A message that, depending on the value of {@code status}, * may be used for various purposes. */ public UpdateAction( AsyncStatus status, String message ); public AsyncStatus getAsyncStatus( ); public String getMessage( ); }
The AsyncStatus
enum has been modified.
Example 16-14 Oracle Enterprise Scheduler AsyncStatus Enum
Package oracle.as.scheduler.async; /** * Valid values for the callback status of an asynchronous java job. * Returning an {@code AsyncStatus} does not guarantee that the state of the * request will change to the corresponding value. The new state of the request * will depend on the old state, the async status, the result of the * post-Process handler (if any), and any errors that may occur in * subsequent processing. */ public enum AsyncStatus { /** * The asynchronous job ran successfully. */ SUCCESS, /** * The asynchronous job has paused for the execution of sub-requests. */ PAUSE, /** * The asynchronous job is issuing a WARNING. */ WARNING, /** * The asynchronous job encountered an error. */ ERROR, /** * The asynchronous job has canceled its execution. Usually this * originates from a {@code RuntimeService.cancel} call. */ CANCEL, /** * The asynchronous job is updated. The request state is not changed * by this action. */ UPDATE } /** * The asynchronous job encountered a business error. */ BIZ_ERROR, /** * The asynchronous job requests manual recovery to complete the request. */ ERROR_MANUAL_RECOVERY;
Example 16-15 Existing Asynchronous Callback Web Service Operation
/** * Set the status of an Oracle Enterprise Scheduler asynchronous java job. * * @param requestExecutionContext A java.lang.String representing * an oracle.as.scheduler.RequestExecutionContext object. * @param status * @param statusMessage * An error message if the status is ERROR, * A business error message if the status is BIZ_ERROR, * A warning message if the status is WARNING, * A paused state if the status is PAUSED. * The value is ignored if the status is SUCCESS or CANCEL. * */ public void setAsyncRequestStatus( String requestExecutionContext, AsyncStatus status, String statusMessage ) throws RequestNotFoundException, RuntimeServiceException ;