/**
* Copyright 2010 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jbpm.process.workitem.bpmn2;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.xml.namespace.QName;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.ClientCallback;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.drools.core.process.instance.impl.WorkItemImpl;
import org.jbpm.bpmn2.core.Bpmn2Import;
import org.jbpm.process.workitem.AbstractLogOrThrowWorkItemHandler;
import org.jbpm.workflow.core.impl.WorkflowProcessImpl;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.manager.RuntimeManager;
import org.kie.api.runtime.process.WorkItem;
import org.kie.api.runtime.process.WorkItemManager;
import org.kie.internal.runtime.Cacheable;
import org.kie.internal.runtime.manager.RuntimeManagerRegistry;
import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServiceTaskHandler extends AbstractLogOrThrowWorkItemHandler implements Cacheable {
public static final String WSDL_IMPORT_TYPE = "http://schemas.xmlsoap.org/wsdl/";
private static final Logger logger = LoggerFactory.getLogger(ServiceTaskHandler.class);
private ConcurrentHashMap<String, Client> clients = new ConcurrentHashMap<String, Client>();
private JaxWsDynamicClientFactory dcf;
private KieSession ksession;
private int asyncTimeout = 10;
private ClassLoader classLoader;
enum WSMode {
SYNC,
ASYNC,
ONEWAY;
}
public ServiceTaskHandler() {
this.dcf = JaxWsDynamicClientFactory.newInstance();
}
public ServiceTaskHandler(KieSession ksession) {
this.dcf = JaxWsDynamicClientFactory.newInstance();
this.ksession = ksession;
}
public ServiceTaskHandler(KieSession ksession, ClassLoader classloader) {
this.dcf = JaxWsDynamicClientFactory.newInstance();
this.ksession = ksession;
this.classLoader = classloader;
}
public ServiceTaskHandler(KieSession ksession, int timeout) {
this.dcf = JaxWsDynamicClientFactory.newInstance();
this.ksession = ksession;
this.asyncTimeout = timeout;
}
public void executeWorkItem(WorkItem workItem, final WorkItemManager manager) {
String implementation = (String) workItem.getParameter("implementation");
if ("##WebService".equalsIgnoreCase(implementation)) {
// since JaxWsDynamicClientFactory will change the TCCL we need to restore it after creating client
ClassLoader origClassloader = Thread.currentThread().getContextClassLoader();
String interfaceRef = (String) workItem.getParameter("interfaceImplementationRef");
String operationRef = (String) workItem.getParameter("operationImplementationRef");
Object parameter = workItem.getParameter("Parameter");
WSMode mode = WSMode.valueOf(workItem.getParameter("mode") == null ? "SYNC" : ((String) workItem.getParameter("mode")).toUpperCase());
try {
Client client = getWSClient(workItem, interfaceRef);
if (client == null) {
throw new IllegalStateException("Unable to create client for web service " + interfaceRef + " - " + operationRef);
}
switch (mode) {
case SYNC:
Object[] result = client.invoke(operationRef, parameter);
Map<String, Object> output = new HashMap<String, Object>();
if (result == null || result.length == 0) {
output.put("Result", null);
} else {
output.put("Result", result[0]);
}
manager.completeWorkItem(workItem.getId(), output);
break;
case ASYNC:
final ClientCallback callback = new ClientCallback();
final long workItemId = workItem.getId();
final String deploymentId = nonNull(((WorkItemImpl)workItem).getDeploymentId());
final long processInstanceId = workItem.getProcessInstanceId();
client.invoke(callback, operationRef, parameter);
new Thread(new Runnable() {
public void run() {
try {
Object[] result = callback.get(asyncTimeout, TimeUnit.SECONDS);
Map<String, Object> output = new HashMap<String, Object>();
if (callback.isDone()) {
if (result == null) {
output.put("Result", null);
} else {
output.put("Result", result[0]);
}
}
RuntimeManager manager = RuntimeManagerRegistry.get().getManager(deploymentId);
if (manager != null) {
RuntimeEngine engine = manager.getRuntimeEngine(ProcessInstanceIdContext.get(processInstanceId));
engine.getKieSession().getWorkItemManager().completeWorkItem(workItemId, output);
manager.disposeRuntimeEngine(engine);
} else {
// in case there is no RuntimeManager available use available ksession,
// as it might be used without runtime manager at all
ksession.getWorkItemManager().completeWorkItem(workItemId, output);
}
} catch (Exception e) {
logger.error("Error encountered while invoking ws operation asynchronously ", e);
}
}
}).start();
break;
case ONEWAY:
ClientCallback callbackFF = new ClientCallback();
client.invoke(callbackFF, operationRef, parameter);
manager.completeWorkItem(workItem.getId(), new HashMap<String, Object>());
break;
default:
break;
}
} catch (Exception e) {
handleException(e, interfaceRef, "", operationRef, parameter.getClass().getName(), parameter);
} finally {
Thread.currentThread().setContextClassLoader(origClassloader);
}
} else {
executeJavaWorkItem(workItem, manager);
}
}
@SuppressWarnings("unchecked")
protected synchronized Client getWSClient(WorkItem workItem, String interfaceRef) {
if (clients.containsKey(interfaceRef)) {
return clients.get(interfaceRef);
}
long processInstanceId = ((WorkItemImpl) workItem).getProcessInstanceId();
WorkflowProcessImpl process = ((WorkflowProcessImpl) ksession.getProcessInstance(processInstanceId).getProcess());
List<Bpmn2Import> typedImports = (List<Bpmn2Import>)process.getMetaData("Bpmn2Imports");
if (typedImports != null ){
Client client = null;
for (Bpmn2Import importObj : typedImports) {
if (WSDL_IMPORT_TYPE.equalsIgnoreCase(importObj.getType())) {
try {
client = dcf.createClient(importObj.getLocation(), new QName(importObj.getNamespace(), interfaceRef), getInternalClassLoader(), null);
clients.put(interfaceRef, client);
logger.info("WS Client is created for {" + importObj.getNamespace() + "}" + interfaceRef);
return client;
} catch (Exception e) {
logger.info("Error when creating WS Client. You can ignore this error as long as a client is eventually created", e);
continue;
}
}
}
}
return null;
}
private ClassLoader getInternalClassLoader() {
if (this.classLoader != null) {
return this.classLoader;
}
return Thread.currentThread().getContextClassLoader();
}
public void executeJavaWorkItem(WorkItem workItem, WorkItemManager manager) {
String i = (String) workItem.getParameter("Interface");
String iImplementationRef = (String) workItem.getParameter("interfaceImplementationRef");
String operation = (String) workItem.getParameter("Operation");
String parameterType = (String) workItem.getParameter("ParameterType");
Object parameter = workItem.getParameter("Parameter");
String[] interfaces = {i, iImplementationRef};
Class<?> c = null;
for(String interf : interfaces) {
try {
c = Class.forName(interf, true, getInternalClassLoader());
break;
} catch (ClassNotFoundException cnfe) {
if(interf.compareTo(interfaces[interfaces.length - 1]) == 0) {
handleException(cnfe, i, iImplementationRef, operation, parameterType, parameter);
}
}
}
try {
Object instance = c.newInstance();
Class<?>[] classes = null;
Object[] params = null;
if (parameterType != null) {
classes = new Class<?>[] {
Class.forName(parameterType, true, getInternalClassLoader())
};
params = new Object[] {
parameter
};
}
Method method = c.getMethod(operation, classes);
Object result = method.invoke(instance, params);
Map<String, Object> results = new HashMap<String, Object>();
results.put("Result", result);
manager.completeWorkItem(workItem.getId(), results);
} catch (ClassNotFoundException e) {
handleException(e, i, iImplementationRef, operation, parameterType, parameter);
} catch (InstantiationException e) {
handleException(e, i, iImplementationRef, operation, parameterType, parameter);
} catch (IllegalAccessException e) {
handleException(e, i, iImplementationRef, operation, parameterType, parameter);
} catch (NoSuchMethodException e) {
handleException(e, i, iImplementationRef, operation, parameterType, parameter);
} catch (InvocationTargetException e) {
handleException(e, i, iImplementationRef, operation, parameterType, parameter);
}
}
private void handleException(Throwable cause, String service, String iImplementationRef, String operation, String paramType, Object param) {
logger.debug("Handling exception {} inside service {} or {} and operation {} with param type {} and value {}",
cause.getMessage(), service, operation, paramType, param);
Map<String, Object> data = new HashMap<String, Object>();
data.put("Interface", service);
data.put("InterfaceImplementationRef", iImplementationRef);
data.put("Operation", operation);
data.put("ParameterType", paramType);
data.put("Parameter", param);
handleException(cause, data);
}
public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {
// Do nothing, cannot be aborted
}
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
protected String nonNull(String value) {
if (value == null) {
return "";
}
return value;
}
@Override
public void close() {
if (clients != null) {
for (Client client : clients.values()) {
client.destroy();
}
}
}
}