package org.camunda.bpm.camel.component.externaltasks; import static org.camunda.bpm.camel.component.CamundaBpmConstants.EXCHANGE_HEADER_PROCESS_DEFINITION_ID; import static org.camunda.bpm.camel.component.CamundaBpmConstants.EXCHANGE_HEADER_PROCESS_DEFINITION_KEY; import static org.camunda.bpm.camel.component.CamundaBpmConstants.EXCHANGE_HEADER_PROCESS_INSTANCE_ID; import static org.camunda.bpm.camel.component.CamundaBpmConstants.EXCHANGE_HEADER_PROCESS_PRIO; import static org.camunda.bpm.camel.component.CamundaBpmConstants.EXCHANGE_HEADER_TASK; import java.lang.reflect.Method; import java.util.Comparator; import java.util.List; import java.util.PriorityQueue; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import org.apache.camel.AsyncCallback; import org.apache.camel.AsyncProcessor; import org.apache.camel.Exchange; import org.apache.camel.ExchangePattern; import org.apache.camel.Message; import org.apache.camel.PollingConsumer; import org.apache.camel.Processor; import org.apache.camel.RuntimeCamelException; import org.apache.camel.impl.ScheduledBatchPollingConsumer; import org.apache.camel.util.CastUtils; import org.camunda.bpm.camel.common.CamundaUtils; import org.camunda.bpm.camel.component.CamundaBpmEndpoint; import org.camunda.bpm.engine.ExternalTaskService; import org.camunda.bpm.engine.externaltask.ExternalTaskQueryTopicBuilder; import org.camunda.bpm.engine.externaltask.LockedExternalTask; public class BatchConsumer extends ScheduledBatchPollingConsumer { private final CamundaBpmEndpoint camundaEndpoint; // parameters private int timeout; private final long lockDuration; private final String topic; private final boolean completeTask; private final List<String> variablesToFetch; private final boolean deserializeVariables; private static Method deserializeVariablesMethod; private final String workerId; private final TaskProcessor taskProcessor; static { try { deserializeVariablesMethod = ExternalTaskQueryTopicBuilder.class.getMethod( "enableCustomObjectDeserialization"); } catch (Exception e) { // ignore because the Camunda version below 7.6.0 is used } } public static boolean systemKnowsDeserializationOfVariables() { return deserializeVariablesMethod != null; } public BatchConsumer(final CamundaBpmEndpoint endpoint, final Processor processor, final int retries, final long retryTimeout, final long[] retryTimeouts, final long lockDuration, final String topic, final boolean completeTask, final List<String> variablesToFetch, final boolean deserializeVariables, final String workerId) { super(endpoint, processor); this.camundaEndpoint = endpoint; this.lockDuration = lockDuration; this.topic = topic; this.completeTask = completeTask; this.variablesToFetch = variablesToFetch; this.deserializeVariables = deserializeVariables; this.workerId = workerId; this.taskProcessor = new TaskProcessor(endpoint, topic, retries, retryTimeout, retryTimeouts, completeTask, true, workerId); } public BatchConsumer(final CamundaBpmEndpoint endpoint, final Processor processor, final ScheduledExecutorService executor, final int retries, final long retryTimeout, final long[] retryTimeouts, final long lockDuration, final String topic, final boolean completeTask, final List<String> variablesToFetch, final boolean deserializeVariables, final String workerId) { super(endpoint, processor, executor); this.camundaEndpoint = endpoint; this.lockDuration = lockDuration; this.topic = topic; this.completeTask = completeTask; this.variablesToFetch = variablesToFetch; this.deserializeVariables = deserializeVariables; this.workerId = workerId; this.taskProcessor = new TaskProcessor(endpoint, topic, retries, retryTimeout, retryTimeouts, completeTask, true, workerId); } @Override public int processBatch(Queue<Object> exchanges) throws Exception { int total = exchanges.size(); int answer = total; for (int index = 0; index < total && isBatchAllowed(); index++) { // only loop if we are started (allowed to run) // use poll to remove the head so it does not consume memory even // after we have processed it Exchange exchange = (Exchange) exchanges.poll(); // add current index and total as properties exchange.setProperty(Exchange.BATCH_INDEX, index); exchange.setProperty(Exchange.BATCH_SIZE, total); exchange.setProperty(Exchange.BATCH_COMPLETE, index == total - 1); // update pending number of exchanges pendingExchanges = total - index - 1; boolean started = processExchange(exchange); // if we did not start processing then decrement the counter if (!started) { answer--; } } // drain any in progress files as we are done with this batch removeExcessiveInProgressTasks(CastUtils.cast((Queue<?>) exchanges, Exchange.class), 0); return answer; } private boolean processExchange(final Exchange exchange) throws Exception { taskProcessor.process(exchange); final Processor currentProcessor = getProcessor(); if (currentProcessor instanceof AsyncProcessor) { ((AsyncProcessor) currentProcessor).process(exchange, new AsyncCallback() { @Override public void done(boolean doneSync) { // we are not interested in this event } }); } else { currentProcessor.process(exchange); } return true; } /** * Drain any in progress files as we are done with this batch * * @param exchanges * the exchanges * @param limit * the limit */ protected void removeExcessiveInProgressTasks(Queue<Exchange> exchanges, int limit) { while (exchanges.size() > limit) { // must remove last Exchange exchange = exchanges.poll(); releaseTask(exchange); } } private void releaseTask(final Exchange exchange) { exchange.setProperty(Exchange.ROLLBACK_ONLY, Boolean.TRUE); taskProcessor.internalProcessing(exchange); } private ExternalTaskService getExternalTaskService() { return camundaEndpoint.getProcessEngine().getExternalTaskService(); } protected int poll() throws Exception { int messagesPolled = 0; PriorityQueue<Exchange> exchanges = new PriorityQueue<Exchange>( /* * default-value, unfortunately * PriorityQueue.DEFAULT_INITIAL_CAPACITY is private and the * constructor with one parameter of type Comparator is not * available in JSE 7 */ 11, new Comparator<Exchange>() { @Override public int compare(Exchange o1, Exchange o2) { Long prio1 = (Long) o1.getProperty(EXCHANGE_HEADER_PROCESS_PRIO, 0); Long prio2 = (Long) o2.getProperty(EXCHANGE_HEADER_PROCESS_PRIO, 0); return prio1.compareTo(prio2); } }); if (isPollAllowed()) { final List<LockedExternalTask> tasks = CamundaUtils.retryIfOptimisticLockingException( new Callable<List<LockedExternalTask>>() { @Override public List<LockedExternalTask> call() { ExternalTaskQueryTopicBuilder query = getExternalTaskService() // .fetchAndLock(maxMessagesPerPoll, workerId, true) // .topic(topic, lockDuration) // .variables(variablesToFetch); if (deserializeVariables && (deserializeVariablesMethod != null)) { try { query = (ExternalTaskQueryTopicBuilder) deserializeVariablesMethod.invoke(query); } catch (Exception e) { throw new RuntimeCamelException(e); } } return query.execute(); } }); messagesPolled = tasks.size(); for (final LockedExternalTask task : tasks) { final ExchangePattern pattern = completeTask ? ExchangePattern.InOut : ExchangePattern.InOnly; Exchange exchange = getEndpoint().createExchange(pattern); exchange.setFromEndpoint(getEndpoint()); exchange.setExchangeId(task.getWorkerId() + "/" + task.getId()); exchange.setProperty(EXCHANGE_HEADER_PROCESS_INSTANCE_ID, task.getProcessInstanceId()); exchange.setProperty(EXCHANGE_HEADER_PROCESS_DEFINITION_KEY, task.getProcessDefinitionKey()); exchange.setProperty(EXCHANGE_HEADER_PROCESS_DEFINITION_ID, task.getProcessDefinitionId()); exchange.setProperty(EXCHANGE_HEADER_PROCESS_PRIO, task.getPriority()); final Message in = exchange.getIn(); in.setHeader(EXCHANGE_HEADER_TASK, task); in.setBody(task.getVariables()); exchanges.add(exchange); } } processBatch(CastUtils.cast(exchanges)); return messagesPolled; } public int getTimeout() { return timeout; } /** * Sets a timeout to use with {@link PollingConsumer}. <br/> * <br/> * Use <tt>timeout < 0</tt> for {@link PollingConsumer#receive()}. <br/> * Use <tt>timeout == 0</tt> for {@link PollingConsumer#receiveNoWait()}. * <br/> * Use <tt>timeout > 0</tt> for {@link PollingConsumer#receive(long)}}. * <br/> * The default timeout value is <tt>0</tt> * * @param timeout * the timeout value */ public void setTimeout(int timeout) { this.timeout = timeout; } }