/** * 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.webservice; import java.lang.reflect.Array; 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.endpoint.dynamic.DynamicClientFactory; import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory; import org.apache.cxf.message.Message; 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 WebServiceWorkItemHandler extends AbstractLogOrThrowWorkItemHandler implements Cacheable { public static final String WSDL_IMPORT_TYPE = "http://schemas.xmlsoap.org/wsdl/"; private static Logger logger = LoggerFactory.getLogger(WebServiceWorkItemHandler.class); private ConcurrentHashMap<String, Client> clients = new ConcurrentHashMap<String, Client>(); private DynamicClientFactory dcf = null; private KieSession ksession; private int asyncTimeout = 10; private ClassLoader classLoader; enum WSMode { SYNC, ASYNC, ONEWAY; } public WebServiceWorkItemHandler(KieSession ksession) { this.ksession = ksession; } public WebServiceWorkItemHandler(KieSession ksession, ClassLoader classloader) { this.ksession = ksession; this.classLoader = classloader; } public WebServiceWorkItemHandler(KieSession ksession, int timeout) { this.ksession = ksession; this.asyncTimeout = timeout; } public void executeWorkItem(WorkItem workItem, final WorkItemManager manager) { // since JaxWsDynamicClientFactory will change the TCCL we need to restore it after creating client ClassLoader origClassloader = Thread.currentThread().getContextClassLoader(); Object[] parameters = null; String interfaceRef = (String) workItem.getParameter("Interface"); String operationRef = (String) workItem.getParameter("Operation"); String endpointAddress = (String) workItem.getParameter("Endpoint"); if ( workItem.getParameter("Parameter") instanceof Object[]) { parameters = (Object[]) workItem.getParameter("Parameter"); } else if (workItem.getParameter("Parameter") != null && workItem.getParameter("Parameter").getClass().isArray()) { int length = Array.getLength(workItem.getParameter("Parameter")); parameters = new Object[length]; for(int i = 0; i < length; i++) { parameters[i] = Array.get(workItem.getParameter("Parameter"), i); } } else { parameters = new Object[]{ workItem.getParameter("Parameter")}; } String modeParam = (String) workItem.getParameter("Mode"); WSMode mode = WSMode.valueOf(modeParam == null ? "SYNC" : modeParam.toUpperCase()); try { Client client = getWSClient(workItem, interfaceRef); if (client == null) { throw new IllegalStateException("Unable to create client for web service " + interfaceRef + " - " + operationRef); } //Override endpoint address if configured. if (endpointAddress != null && !"".equals(endpointAddress)) { client.getRequestContext().put(Message.ENDPOINT_ADDRESS, endpointAddress) ; } switch (mode) { case SYNC: Object[] result = client.invoke(operationRef, parameters); Map<String, Object> output = new HashMap<String, Object>(); if (result == null || result.length == 0) { output.put("Result", null); } else { output.put("Result", result[0]); } logger.debug("Received sync response {} completeing work item {}", result, workItem.getId()); 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, parameters); 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]); } } logger.debug("Received async response {} completeing work item {}", result, workItemId); 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) { e.printStackTrace(); throw new RuntimeException("Error encountered while invoking ws operation asynchronously", e); } } }).start(); break; case ONEWAY: ClientCallback callbackFF = new ClientCallback(); client.invoke(callbackFF, operationRef, parameters); logger.debug("One way operation, not going to wait for response, completing work item {}", workItem.getId()); manager.completeWorkItem(workItem.getId(), new HashMap<String, Object>()); break; default: break; } } catch (Exception e) { handleException(e); } finally { Thread.currentThread().setContextClassLoader(origClassloader); } } @SuppressWarnings("unchecked") protected synchronized Client getWSClient(WorkItem workItem, String interfaceRef) { if (clients.containsKey(interfaceRef)) { return clients.get(interfaceRef); } String importLocation = (String) workItem.getParameter("Url"); String importNamespace = (String) workItem.getParameter("Namespace"); if (importLocation != null && importLocation.trim().length() > 0 && importNamespace != null && importNamespace.trim().length() > 0) { Client client = getDynamicClientFactory().createClient(importLocation, new QName(importNamespace, interfaceRef), getInternalClassLoader(), null); clients.put(interfaceRef, client); return client; } 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 = getDynamicClientFactory().createClient(importObj.getLocation(), new QName(importObj.getNamespace(), interfaceRef), getInternalClassLoader(), null); clients.put(interfaceRef, client); return client; } catch (Exception e) { logger.error("Error when creating WS Client", e); continue; } } } } return null; } protected synchronized DynamicClientFactory getDynamicClientFactory() { if (this.dcf == null) { this.dcf = JaxWsDynamicClientFactory.newInstance(); } return this.dcf; } public void abortWorkItem(WorkItem workItem, WorkItemManager manager) { // Do nothing, cannot be aborted } private ClassLoader getInternalClassLoader() { if (this.classLoader != null) { return this.classLoader; } return Thread.currentThread().getContextClassLoader(); } 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(); } } } }