/*
* Copyright 2013 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.executor.impl.wih;
import java.util.Date;
import java.util.List;
import org.drools.core.process.instance.impl.WorkItemImpl;
import org.drools.core.time.TimeUtils;
import org.kie.api.executor.CommandContext;
import org.kie.api.executor.ExecutorService;
import org.kie.api.executor.RequestInfo;
import org.kie.api.executor.STATUS;
import org.kie.api.runtime.process.WorkItem;
import org.kie.api.runtime.process.WorkItemHandler;
import org.kie.api.runtime.process.WorkItemManager;
import org.kie.api.runtime.query.QueryContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Asynchronous work item handler that utilizes power of <code>ExecutorService</code>.
* it expects following parameters to be present on work item for proper execution:
* <ul>
* <li>CommandClass - FQCN of the command to be executed - mandatory unless this handler is configured with default command class</li>
* <li>Retries - number of retries for the command execution - optional</li>
* <li>RetryDelay - Comma separated list of time expressions (5s, 2m, 4h) to be used in case of errors and retry needed.</li>
* <li>Delay - optionally delay which job should be executed after given as time expression (5s, 2m, 4h) that will be calculated starting from current time</li>
* <li>AutoComplete - allows to use "fire and forget" style so it will not wait for job completion and allow to move on (default false)</li>
* <li>Priority - priority for the job execution - from 0 (the lowest) to 9 (the highest)</li>
* </ul>
* During execution it will set contextual data that will be available inside the command:
* <ul>
* <li>businessKey - generated from process instance id and work item id in following format: [processInstanceId]:[workItemId]</li>
* <li>workItem - actual work item instance that is being executed (including all parameters)</li>
* <li>processInstanceId - id of the process instance that triggered this work item execution</li>
* </ul>
*
* In case work item shall be aborted handler will attempt to cancel active requests based on business key (process instance id and work item id)
*/
public class AsyncWorkItemHandler implements WorkItemHandler {
private static final Logger logger = LoggerFactory.getLogger(AsyncWorkItemHandler.class);
private ExecutorService executorService;
private String commandClass;
public AsyncWorkItemHandler(ExecutorService executorService) {
this.executorService = executorService;
}
public AsyncWorkItemHandler(ExecutorService executorService, String commandClass) {
this(executorService);
this.commandClass = commandClass;
}
public AsyncWorkItemHandler(Object executorService, String commandClass) {
this((ExecutorService) executorService);
this.commandClass = commandClass;
}
@Override
public void executeWorkItem(WorkItem workItem, WorkItemManager manager) {
if (executorService == null || !executorService.isActive()) {
throw new IllegalStateException("Executor is not set or is not active");
}
boolean autoComplete = false;
if (workItem.getParameter("AutoComplete") != null) {
autoComplete = Boolean.parseBoolean(workItem.getParameter("AutoComplete").toString());
}
String businessKey = buildBusinessKey(workItem);
logger.debug("Executing work item {} with built business key {}", workItem, businessKey);
String cmdClass = (String) workItem.getParameter("CommandClass");
if (cmdClass == null) {
cmdClass = this.commandClass;
}
logger.debug("Command class for this execution is {}", cmdClass);
CommandContext ctxCMD = new CommandContext();
ctxCMD.setData("businessKey", businessKey);
ctxCMD.setData("workItem", workItem);
ctxCMD.setData("processInstanceId", getProcessInstanceId(workItem));
ctxCMD.setData("deploymentId", ((WorkItemImpl)workItem).getDeploymentId());
// in case auto complete is selected skip callback
if (!autoComplete) {
ctxCMD.setData("callbacks", AsyncWorkItemHandlerCmdCallback.class.getName());
}
if (workItem.getParameter("Retries") != null) {
ctxCMD.setData("retries", Integer.parseInt(workItem.getParameter("Retries").toString()));
}
if (workItem.getParameter("Owner") != null) {
ctxCMD.setData("owner", workItem.getParameter("Owner"));
}
if (workItem.getParameter("RetryDelay") != null) {
ctxCMD.setData("retryDelay", workItem.getParameter("RetryDelay"));
}
if (workItem.getParameter("Priority") != null) {
ctxCMD.setData("priority", Integer.parseInt(workItem.getParameter("Priority").toString()));
}
Date scheduleDate = new Date();
if (workItem.getParameter("Delay") != null) {
long delayInMillis = TimeUtils.parseTimeString((String)workItem.getParameter("Delay"));
scheduleDate = new Date(System.currentTimeMillis() + delayInMillis);
}
logger.trace("Command context {}", ctxCMD);
Long requestId = executorService.scheduleRequest(cmdClass, scheduleDate, ctxCMD);
logger.debug("Request scheduled successfully with id {}", requestId);
if (autoComplete) {
logger.debug("Auto completing work item with id {}", workItem.getId());
manager.completeWorkItem(workItem.getId(), null);
}
}
@Override
public void abortWorkItem(WorkItem workItem, WorkItemManager manager) {
String businessKey = buildBusinessKey(workItem);
logger.info("Looking up for not cancelled and not done requests for business key {}", businessKey);
List<RequestInfo> requests = executorService.getRequestsByBusinessKey(businessKey, new QueryContext());
if (requests != null) {
for (RequestInfo request : requests) {
if (request.getStatus() != STATUS.CANCELLED && request.getStatus() != STATUS.DONE
&& request.getStatus() != STATUS.ERROR) {
logger.info("About to cancel request with id {} and business key {} request state {}",
request.getId(), businessKey, request.getStatus());
executorService.cancelRequest(request.getId());
}
}
}
}
protected String buildBusinessKey(WorkItem workItem) {
String businessKeyIn = (String) workItem.getParameter("BusinessKey");
if (businessKeyIn != null && !businessKeyIn.isEmpty()) {
return businessKeyIn;
}
StringBuffer businessKey = new StringBuffer();
businessKey.append(getProcessInstanceId(workItem));
businessKey.append(":");
businessKey.append(workItem.getId());
return businessKey.toString();
}
protected long getProcessInstanceId(WorkItem workItem) {
return ((WorkItemImpl) workItem).getProcessInstanceId();
}
}