001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.processor;
018
019import java.util.concurrent.Callable;
020import java.util.concurrent.ExecutorService;
021
022import org.apache.camel.AsyncCallback;
023import org.apache.camel.AsyncProcessor;
024import org.apache.camel.CamelContext;
025import org.apache.camel.Exchange;
026import org.apache.camel.ExchangePattern;
027import org.apache.camel.Message;
028import org.apache.camel.Ordered;
029import org.apache.camel.Predicate;
030import org.apache.camel.Processor;
031import org.apache.camel.Route;
032import org.apache.camel.Traceable;
033import org.apache.camel.support.ServiceSupport;
034import org.apache.camel.support.SynchronizationAdapter;
035import org.apache.camel.util.AsyncProcessorHelper;
036import org.apache.camel.util.ExchangeHelper;
037import org.apache.camel.util.ServiceHelper;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import static org.apache.camel.util.ObjectHelper.notNull;
042
043/**
044 * Processor implementing <a href="http://camel.apache.org/oncompletion.html">onCompletion</a>.
045 *
046 * @version 
047 */
048public class OnCompletionProcessor extends ServiceSupport implements AsyncProcessor, Traceable {
049
050    private static final Logger LOG = LoggerFactory.getLogger(OnCompletionProcessor.class);
051    private final CamelContext camelContext;
052    private final Processor processor;
053    private final ExecutorService executorService;
054    private final boolean shutdownExecutorService;
055    private final boolean onCompleteOnly;
056    private final boolean onFailureOnly;
057    private final Predicate onWhen;
058    private final boolean useOriginalBody;
059    private final boolean afterConsumer;
060
061    public OnCompletionProcessor(CamelContext camelContext, Processor processor, ExecutorService executorService, boolean shutdownExecutorService,
062                                 boolean onCompleteOnly, boolean onFailureOnly, Predicate onWhen, boolean useOriginalBody, boolean afterConsumer) {
063        notNull(camelContext, "camelContext");
064        notNull(processor, "processor");
065        this.camelContext = camelContext;
066        this.processor = processor;
067        this.executorService = executorService;
068        this.shutdownExecutorService = shutdownExecutorService;
069        this.onCompleteOnly = onCompleteOnly;
070        this.onFailureOnly = onFailureOnly;
071        this.onWhen = onWhen;
072        this.useOriginalBody = useOriginalBody;
073        this.afterConsumer = afterConsumer;
074    }
075
076    @Override
077    protected void doStart() throws Exception {
078        ServiceHelper.startService(processor);
079    }
080
081    @Override
082    protected void doStop() throws Exception {
083        ServiceHelper.stopService(processor);
084    }
085
086    @Override
087    protected void doShutdown() throws Exception {
088        ServiceHelper.stopAndShutdownService(processor);
089        if (shutdownExecutorService) {
090            getCamelContext().getExecutorServiceManager().shutdownNow(executorService);
091        }
092    }
093
094    public CamelContext getCamelContext() {
095        return camelContext;
096    }
097
098    public void process(Exchange exchange) throws Exception {
099        AsyncProcessorHelper.process(this, exchange);
100    }
101
102    public boolean process(Exchange exchange, AsyncCallback callback) {
103        if (processor != null) {
104            // register callback
105            if (afterConsumer) {
106                exchange.getUnitOfWork().addSynchronization(new OnCompletionSynchronizationAfterConsumer());
107            } else {
108                exchange.getUnitOfWork().addSynchronization(new OnCompletionSynchronizationBeforeConsumer());
109            }
110        }
111
112        callback.done(true);
113        return true;
114    }
115
116    protected boolean isCreateCopy() {
117        // we need to create a correlated copy if we run in parallel mode or is in after consumer mode (as the UoW would be done on the original exchange otherwise)
118        return executorService != null || afterConsumer;
119    }
120
121    /**
122     * Processes the exchange by the processors
123     *
124     * @param processor the processor
125     * @param exchange the exchange
126     */
127    protected static void doProcess(Processor processor, Exchange exchange) {
128        // must remember some properties which we cannot use during onCompletion processing
129        // as otherwise we may cause issues
130        Object stop = exchange.removeProperty(Exchange.ROUTE_STOP);
131        Object failureHandled = exchange.removeProperty(Exchange.FAILURE_HANDLED);
132        Object caught = exchange.removeProperty(Exchange.EXCEPTION_CAUGHT);
133        Object errorhandlerHandled = exchange.removeProperty(Exchange.ERRORHANDLER_HANDLED);
134        Object rollbackOnly = exchange.removeProperty(Exchange.ROLLBACK_ONLY);
135        Object rollbackOnlyLast = exchange.removeProperty(Exchange.ROLLBACK_ONLY_LAST);
136
137        Exception cause = exchange.getException();
138        exchange.setException(null);
139
140        try {
141            processor.process(exchange);
142        } catch (Exception e) {
143            exchange.setException(e);
144        } finally {
145            // restore the options
146            if (stop != null) {
147                exchange.setProperty(Exchange.ROUTE_STOP, stop);
148            }
149            if (failureHandled != null) {
150                exchange.setProperty(Exchange.FAILURE_HANDLED, failureHandled);
151            }
152            if (caught != null) {
153                exchange.setProperty(Exchange.EXCEPTION_CAUGHT, caught);
154            }
155            if (errorhandlerHandled != null) {
156                exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, errorhandlerHandled);
157            }
158            if (rollbackOnly != null) {
159                exchange.setProperty(Exchange.ROLLBACK_ONLY, rollbackOnly);
160            }
161            if (rollbackOnlyLast != null) {
162                exchange.setProperty(Exchange.ROLLBACK_ONLY, rollbackOnlyLast);
163            }
164            if (cause != null) {
165                exchange.setException(cause);
166            }
167        }
168    }
169
170    /**
171     * Prepares the {@link Exchange} to send as onCompletion.
172     *
173     * @param exchange the current exchange
174     * @return the exchange to be routed in onComplete
175     */
176    protected Exchange prepareExchange(Exchange exchange) {
177        Exchange answer;
178
179        if (isCreateCopy()) {
180            // for asynchronous routing we must use a copy as we dont want it
181            // to cause side effects of the original exchange
182            // (the original thread will run in parallel)
183            answer = ExchangeHelper.createCorrelatedCopy(exchange, false);
184            if (answer.hasOut()) {
185                // move OUT to IN (pipes and filters)
186                answer.setIn(answer.getOut());
187                answer.setOut(null);
188            }
189            // set MEP to InOnly as this onCompletion is a fire and forget
190            answer.setPattern(ExchangePattern.InOnly);
191        } else {
192            // use the exchange as-is
193            answer = exchange;
194        }
195
196        if (useOriginalBody) {
197            LOG.trace("Using the original IN message instead of current");
198
199            Message original = exchange.getUnitOfWork().getOriginalInMessage();
200            answer.setIn(original);
201        }
202
203        // add a header flag to indicate its a on completion exchange
204        answer.setProperty(Exchange.ON_COMPLETION, Boolean.TRUE);
205
206        return answer;
207    }
208
209    private final class OnCompletionSynchronizationAfterConsumer extends SynchronizationAdapter implements Ordered {
210
211        public int getOrder() {
212            // we want to be last
213            return Ordered.LOWEST;
214        }
215
216        @Override
217        public void onComplete(final Exchange exchange) {
218            if (onFailureOnly) {
219                return;
220            }
221
222            if (onWhen != null && !onWhen.matches(exchange)) {
223                // predicate did not match so do not route the onComplete
224                return;
225            }
226
227            // must use a copy as we dont want it to cause side effects of the original exchange
228            final Exchange copy = prepareExchange(exchange);
229
230            if (executorService != null) {
231                executorService.submit(new Callable<Exchange>() {
232                    public Exchange call() throws Exception {
233                        LOG.debug("Processing onComplete: {}", copy);
234                        doProcess(processor, copy);
235                        return copy;
236                    }
237                });
238            } else {
239                // run without thread-pool
240                LOG.debug("Processing onComplete: {}", copy);
241                doProcess(processor, copy);
242            }
243        }
244
245        public void onFailure(final Exchange exchange) {
246            if (onCompleteOnly) {
247                return;
248            }
249
250            if (onWhen != null && !onWhen.matches(exchange)) {
251                // predicate did not match so do not route the onComplete
252                return;
253            }
254
255
256            // must use a copy as we dont want it to cause side effects of the original exchange
257            final Exchange copy = prepareExchange(exchange);
258            final Exception original = copy.getException();
259            final boolean originalFault = copy.hasOut() ? copy.getOut().isFault() : copy.getIn().isFault();
260            // must remove exception otherwise onFailure routing will fail as well
261            // the caused exception is stored as a property (Exchange.EXCEPTION_CAUGHT) on the exchange
262            copy.setException(null);
263            // must clear fault otherwise onFailure routing will fail as well
264            if (copy.hasOut()) {
265                copy.getOut().setFault(false);
266            } else {
267                copy.getIn().setFault(false);
268            }
269
270            if (executorService != null) {
271                executorService.submit(new Callable<Exchange>() {
272                    public Exchange call() throws Exception {
273                        LOG.debug("Processing onFailure: {}", copy);
274                        doProcess(processor, copy);
275                        // restore exception after processing
276                        copy.setException(original);
277                        return null;
278                    }
279                });
280            } else {
281                // run without thread-pool
282                LOG.debug("Processing onFailure: {}", copy);
283                doProcess(processor, copy);
284                // restore exception after processing
285                copy.setException(original);
286                // restore fault after processing
287                if (copy.hasOut()) {
288                    copy.getOut().setFault(originalFault);
289                } else {
290                    copy.getIn().setFault(originalFault);
291                }
292            }
293        }
294
295        @Override
296        public String toString() {
297            if (!onCompleteOnly && !onFailureOnly) {
298                return "onCompleteOrFailure";
299            } else if (onCompleteOnly) {
300                return "onCompleteOnly";
301            } else {
302                return "onFailureOnly";
303            }
304        }
305    }
306
307    private final class OnCompletionSynchronizationBeforeConsumer extends SynchronizationAdapter implements Ordered {
308
309        public int getOrder() {
310            // we want to be last
311            return Ordered.LOWEST;
312        }
313
314        @Override
315        public void onAfterRoute(Route route, Exchange exchange) {
316            if (exchange.isFailed() && onCompleteOnly) {
317                return;
318            }
319
320            if (!exchange.isFailed() && onFailureOnly) {
321                return;
322            }
323
324            if (onWhen != null && !onWhen.matches(exchange)) {
325                // predicate did not match so do not route the onComplete
326                return;
327            }
328
329            // must use a copy as we dont want it to cause side effects of the original exchange
330            final Exchange copy = prepareExchange(exchange);
331
332            if (executorService != null) {
333                executorService.submit(new Callable<Exchange>() {
334                    public Exchange call() throws Exception {
335                        LOG.debug("Processing onAfterRoute: {}", copy);
336                        doProcess(processor, copy);
337                        return copy;
338                    }
339                });
340            } else {
341                // run without thread-pool
342                LOG.debug("Processing onAfterRoute: {}", copy);
343                doProcess(processor, copy);
344            }
345        }
346
347        @Override
348        public String toString() {
349            return "onAfterRoute";
350        }
351    }
352
353    @Override
354    public String toString() {
355        return "OnCompletionProcessor[" + processor + "]";
356    }
357
358    public String getTraceLabel() {
359        return "onCompletion";
360    }
361}