
org.jbpm.process.longrest.LongRunningRestServiceWorkItemHandler Maven / Gradle / Ivy
/*
* Copyright 2021 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.longrest;
import java.io.IOException;
import java.net.HttpCookie;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.jbpm.process.longrest.util.Mapper;
import org.jbpm.process.longrest.util.ProcessUtils;
import org.jbpm.process.longrest.util.Strings;
import org.jbpm.process.workitem.core.AbstractLogOrThrowWorkItemHandler;
import org.jbpm.process.workitem.core.util.RequiredParameterValidator;
import org.jbpm.process.workitem.core.util.Wid;
import org.jbpm.process.workitem.core.util.WidMavenDepends;
import org.jbpm.process.workitem.core.util.WidParameter;
import org.jbpm.process.workitem.core.util.WidResult;
import org.jbpm.process.workitem.core.util.service.WidAction;
import org.jbpm.process.workitem.core.util.service.WidAuth;
import org.jbpm.process.workitem.core.util.service.WidService;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeManager;
import org.kie.api.runtime.process.WorkItem;
import org.kie.api.runtime.process.WorkItemManager;
import org.kie.api.runtime.process.WorkflowProcessInstance;
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.mvel2.integration.VariableResolverFactory;
import org.mvel2.integration.impl.MapVariableResolverFactory;
import org.mvel2.templates.CompiledTemplate;
import org.mvel2.templates.TemplateRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.mvel2.templates.TemplateCompiler.compileTemplate;
@Wid(widfile = "LongRunningRestService.wid",
name = "LongRunningRestService",
displayName = "LongRunningRestService",
defaultHandler = "mvel: new org.jbpm.process.longrest.LongRunningRestServiceWorkItemHandler(runtimeManager)",
category = "long-running-rest-workitem",
documentation = "",
parameters = {
@WidParameter(name = "url", required = true),
@WidParameter(name = "method", required = true),
@WidParameter(name = "headers", required = false),
@WidParameter(name = "template", required = false),
@WidParameter(name = "cancelUrlJsonPointer", required = false),
@WidParameter(name = "cancelUrlTemplate", required = false),
@WidParameter(name = "socketTimeout", required = false),
@WidParameter(name = "connectTimeout", required = false),
@WidParameter(name = "connectionRequestTimeout", required = false)
},
results = {
@WidResult(name = "responseCode"),
@WidResult(name = "result"),
@WidResult(name = "cancelUrl"),
@WidResult(name = "error")
},
mavenDepends = {
@WidMavenDepends(group = "org.jbpm.contrib", artifact = "long-running-rest-workitem", version = "7.74.0.Final")
},
serviceInfo = @WidService(category = "REST service", description = "",
keywords = "rest,long-running",
action = @WidAction(title = "Long running REST service handler ver. 7.74.0.Final"),
authinfo = @WidAuth(required = true, params = {"url"})
)
)
public class LongRunningRestServiceWorkItemHandler extends AbstractLogOrThrowWorkItemHandler {
private static final Logger logger = LoggerFactory.getLogger(LongRunningRestServiceWorkItemHandler.class);
/**
* Cookies are required for sticky session in cases where cancel request must hit the same node behind the load balancer.
*/
private static final String COOKIES_KEY = "cookies";
private final RuntimeManager runtimeManager;
ParserContext mvelContext = new ParserContext();
public LongRunningRestServiceWorkItemHandler(RuntimeManager runtimeManager) {
this.runtimeManager = runtimeManager;
logger.debug("Constructing with runtimeManager ...");
initializeMvelContext();
setLogThrownException(false);
}
public LongRunningRestServiceWorkItemHandler() {
logger.debug("Constructing without runtimeManager ...");
runtimeManager = null;
initializeMvelContext();
setLogThrownException(false);
}
private void initializeMvelContext() {
mvelContext.addImport(
"quote",
MVEL.getStaticMethod(Strings.class, "quoteString", new Class[]{Object.class}));
mvelContext.addImport("asJson",
MVEL.getStaticMethod(Mapper.class, "writeValueAsString", new Class[]{Object.class, boolean.class}));
mvelContext.addImport("asJson",
MVEL.getStaticMethod(Mapper.class, "writeValueAsString", new Class[]{Object.class}));
}
public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {
try {
RequiredParameterValidator.validate(this.getClass(), workItem);
long processInstanceId = workItem.getProcessInstanceId();
WorkflowProcessInstance processInstance = ProcessUtils.getProcessInstance(runtimeManager, processInstanceId);
String cancelUrlJsonPointer = ProcessUtils.getStringParameter(workItem, Constant.CANCEL_URL_JSON_POINTER_VARIABLE);
String cancelUrlTemplate = ProcessUtils.getStringParameter(workItem, Constant.CANCEL_URL_TEMPLATE_VARIABLE);
String requestUrl = ProcessUtils.getStringParameter(workItem, "url");
String requestMethod = ProcessUtils.getStringParameter(workItem, "method");
String requestTemplate = ProcessUtils.getStringParameter(workItem, "template");
String requestHeaders = ProcessUtils.getStringParameter(workItem, "headers");
int socketTimeout = ProcessUtils.getIntParameter(workItem, "socketTimeout", 5000);
int connectTimeout = ProcessUtils.getIntParameter(workItem, "connectTimeout", 5000);
int connectionRequestTimeout = ProcessUtils.getIntParameter(workItem, "connectionRequestTimeout", 5000);
KieSession kieSession = ProcessUtils.getKsession(runtimeManager, processInstanceId);
String containerId = (String) kieSession.getEnvironment().get("deploymentId");
//should this service run
logger.debug("Should run ProcessInstance.id: {}.", processInstance.getId());
try {
invokeRemoteService(
processInstance,
manager,
workItem.getId(),
requestUrl,
requestMethod,
requestTemplate,
containerId,
cancelUrlJsonPointer,
cancelUrlTemplate,
requestHeaders,
socketTimeout,
connectTimeout,
connectionRequestTimeout);
} catch (RemoteInvocationException e) {
String message = MessageFormat.format("Failed to invoke remote service. ProcessInstanceId {0}.", processInstanceId);
logger.warn(message, e);
completeWorkItem(manager, workItem.getId(), e);
} catch (ResponseProcessingException e) {
String message = MessageFormat.format("Failed to process response. ProcessInstanceId {0}.", processInstanceId);
logger.warn(message, e);
completeWorkItem(manager, workItem.getId(), e);
}
} catch (Throwable cause) {
logger.error("Failed to execute workitem handler due to the following error.", cause);
completeWorkItem(manager, workItem.getId(), cause);
}
}
private void invokeRemoteService(
WorkflowProcessInstance processInstance,
WorkItemManager manager,
long workItemId,
String requestUrl,
String httpMethod,
String requestTemplate,
String containerId,
String cancelUrlJsonPointer,
String cancelUrlTemplate,
String requestHeaders,
int socketTimeout,
int connectTimeout,
int connectionRequestTimeout) throws RemoteInvocationException, ResponseProcessingException {
logger.debug("requestTemplate: {}", requestTemplate);
VariableResolverFactory variableResolverFactory = getVariableResolverFactory(processInstance, containerId);
String requestBodyEvaluated;
if (requestTemplate != null && !requestTemplate.equals("")) {
CompiledTemplate compiled = compileTemplate(requestTemplate, mvelContext);
requestBodyEvaluated = (String) TemplateRuntime.execute(compiled, mvelContext, variableResolverFactory);
} else {
requestBodyEvaluated = "";
}
Map requestHeadersMap = new HashMap<>();
// Add cookies to the request
Map cookies = (Map) processInstance.getVariable(COOKIES_KEY);
if (cookies != null) {
String cookieHeader = cookies.entrySet().stream()
.map(c -> c.getKey() + "=" + c.getValue())
.collect(Collectors.joining("; "));
requestHeadersMap.put("Cookie", cookieHeader);
}
requestHeadersMap.putAll(Strings.toMap(requestHeaders));
HttpResponse httpResponse = httpRequest(
requestUrl,
requestBodyEvaluated,
httpMethod,
requestHeadersMap,
socketTimeout,
connectTimeout,
connectionRequestTimeout);
int statusCode = httpResponse.getStatusLine().getStatusCode();
logger.info("Remote endpoint returned status: {}.", statusCode);
if (statusCode < 200 || statusCode >= 300) {
String message = MessageFormat.format("Remote service responded with error status code {0} and reason: {1}. ProcessInstanceId {2}.", statusCode, httpResponse.getStatusLine().getReasonPhrase(), processInstance.getId());
throw new RemoteInvocationException(message);
}
storeCookies(httpResponse, processInstance);
HttpEntity responseEntity = httpResponse.getEntity();
if (statusCode == 204 || responseEntity.getContentLength() == 0L) {
completeWorkItem(manager, workItemId, statusCode, Collections.emptyMap(), "");
} else {
String responseString;
try {
responseString = EntityUtils.toString(responseEntity, "UTF-8");
logger.debug("Invocation response: {}", responseString);
} catch (IOException e) {
throw new ResponseProcessingException("Cannot read remote entity.", e);
}
JsonNode root;
Map serviceInvocationResponse;
try {
root = Mapper.getInstance().readTree(responseString);
if (JsonNodeType.ARRAY.equals(root.getNodeType())) {
//convert array to indexed map
serviceInvocationResponse = new LinkedHashMap<>();
Object[] array = Mapper.getInstance().convertValue(root, new TypeReference
© 2015 - 2025 Weber Informatics LLC | Privacy Policy