/*
* RemoteWorkflowEngine.java
*
* Version 1.0 Sep 25, 2008
*
* Copyright notice
*
* Brief description
*
* (c) 2008 by dbreuer
*/
package de.fhkoeln.cosima.workflow;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.rpc.client.RPCServiceClient;
import de.fhkoeln.cosima.services.CoreService;
import de.fhkoeln.cosima.services.IODescriptor;
import de.fhkoeln.cosima.services.OutputDescriptor;
import de.fhkoeln.cosima.services.registry.ServiceRegistry;
import de.fhkoeln.cosima.workflow.WorkflowElement.Input;
import de.fhkoeln.cosima.workflow.storage.ProcessStore;
/**
* A {@link RemoteWorkflowEngine} instance runs a workflow specified by its
* {@link WorkflowDefinition}. The single workflow elements are web
* services which have to be invoked remotely. Their URI is specified
* in the WorkflowDefinition. Every runner instance needs, in addition
* to a WorkflowDefinition reference, also a reference to a
* ProcessStore. In a ProcessStore the workflow can store the return
* values of a component, so it can be assinged later to another
* component.
*
* @author Dirk Breuer
* @version 1.0 Sep 25, 2008
*/
public class RemoteWorkflowEngine extends WorkflowEngine {
/**
* The ProcessStore reference of this runner instance.
*/
private ProcessStore processStore;
/**
* The Service Registry instance to query for service URIs.
*/
private ServiceRegistry registry;
/**
* This is the main method of every enigne. After successful
* initialization of the runner instance the run methods starts the
* process. Every element will be invoked as described in the
* definition object. While working through the definition elements
* certain the following steps are performed by the run method:
*
* <ul>
* <li>It is asked if the element needs any input (in most of
* the cases this is true)</li>
* <ul>
* <li>If yes and the input is external add the reference
* to the external content IODescriptor instance for
* the current element.</li>
* <li>If yes and the input is internal get the reference
* from the {@link ProcessStore} reference and add it
* to the IODescriptor instance.</li>
* <li>If no, which means, there is no more input required
* for this component, proceed with execution</li>
* </ul>
* <li>Setup the WS client.</li>
* <li>Set the IODescriptor instance at the remote service.</li>
* <li>Call the <code>execute()</code> method of the service.</li>
* <li>Retrieve the result which is an {@link IODescriptor}</li>
* <li>If the IODescriptor contains any elements, they represent
* the output of the service. Store the reference of the output
* in the ProcessStore.</li>
* <li>Proceed to the next element in the workflow definition</li>
* </ul>
*
* (Further information is provided in the 'WorkflowEngine_Flowchart.graffle' Document.
*/
public void execute() {
// iterate through the workflow definition elements
Iterator<Set<WorkflowElement>> elementsIterator = getWorkflowDefinition().elementsIterator();
while (elementsIterator.hasNext()) {
for (WorkflowElement element : elementsIterator.next()) {
// define a default inputDescriptor
IODescriptor inputDescriptor = new IODescriptor();
if (element.needsInput()) {
for (Input elementInput : element.getInput()) {
if (elementInput.isExternal()) {
inputDescriptor.add(elementInput.getData());
} else if (elementInput.isInternal()) {
inputDescriptor.add(getStorage().getInputByKey(
elementInput.getUri()));
}
}
}
IODescriptor outputDescriptor = invokeServiceForElementWithInputDescriptor(element, inputDescriptor);
if (!outputDescriptor.isEmpty()) {
getStorage().putDataForKey(outputDescriptor.first(), element.getOutputUri());
}
}
}
System.out.println("-> Workflow successfully executed!");
}
/**
* @return The store which holds temporary information of the process
*/
public ProcessStore getStorage() {
return processStore;
}
/**
* @param processStore
* The ProcessStore instance which should be used during
* the runtime of this workflow.
*/
public void setProcessStore(ProcessStore processStore) {
this.processStore = processStore;
}
/**
* Sets the concrete Service Registry implementation.
*
* @param registry A service registry implementation.
*/
public void setRegistry(ServiceRegistry registry) {
this.registry = registry;
}
/**
* This method encapsulated the call of the remote service. So far it only can handle SOAP services.
* All information of connecting to the service component are encapsulated in the workflow element.
*
* FIXME: If we receive a timeout from the remote service we do not get the {@link IODescriptor} instance
* we were expecting. Due to this follow up operations can fail. This should be handled in a reasonable way.
* At the moment we just set a higher timeout value to handle this effects.
*
* @param element
* The WorkflowElement of which we call its service
* @param inputDescriptor
* The InputDescriptor for the service
* @return An OutputDescriptor which holds the result of the service
* invocation.
*/
@SuppressWarnings("unchecked")
private IODescriptor invokeServiceForElementWithInputDescriptor(WorkflowElement element,
IODescriptor inputDescriptor) {
IODescriptor output = new OutputDescriptor();
try {
RPCServiceClient client = new RPCServiceClient();
Options options = client.getOptions();
String serviceUri = registry.query(element.getDescription());
Logger.info("Setting EPR to: " + serviceUri);
EndpointReference targetEPR = new EndpointReference(serviceUri);
options.setTo(targetEPR);
// We want to set the timeout a bit higher to handle long running remote services.
options.setTimeOutInMilliSeconds(500000);
// Setting input arguments first
QName setInputOperation = new QName(element.getNamespace(), CoreService.SERVICE_SET_INPUT_OPERATION);
Object[] setInputOperationArgs = new Object[] { inputDescriptor };
client.invokeRobust(setInputOperation, setInputOperationArgs);
// Run the service action and get back the output value
QName executeOperation = new QName(element.getNamespace(), CoreService.SERVICE_EXECUTE_OPERATION);
Object[] executeOperationArgs = new Object[] {};
Class[] returnTypes = new Class[] { IODescriptor.class };
Object[] response = client.invokeBlocking(executeOperation, executeOperationArgs, returnTypes);
output = (IODescriptor) response[0];
} catch (AxisFault e) {
e.printStackTrace();
}
return output;
}
}