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.ArrayList;
020import java.util.Date;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.concurrent.RejectedExecutionException;
024
025import org.apache.camel.AsyncCallback;
026import org.apache.camel.CamelContext;
027import org.apache.camel.Exchange;
028import org.apache.camel.MessageHistory;
029import org.apache.camel.Ordered;
030import org.apache.camel.Processor;
031import org.apache.camel.Route;
032import org.apache.camel.StatefulService;
033import org.apache.camel.StreamCache;
034import org.apache.camel.api.management.PerformanceCounter;
035import org.apache.camel.management.DelegatePerformanceCounter;
036import org.apache.camel.management.mbean.ManagedPerformanceCounter;
037import org.apache.camel.model.ProcessorDefinition;
038import org.apache.camel.model.ProcessorDefinitionHelper;
039import org.apache.camel.processor.interceptor.BacklogDebugger;
040import org.apache.camel.processor.interceptor.BacklogTracer;
041import org.apache.camel.processor.interceptor.DefaultBacklogTracerEventMessage;
042import org.apache.camel.spi.InflightRepository;
043import org.apache.camel.spi.MessageHistoryFactory;
044import org.apache.camel.spi.RouteContext;
045import org.apache.camel.spi.RoutePolicy;
046import org.apache.camel.spi.StreamCachingStrategy;
047import org.apache.camel.spi.Transformer;
048import org.apache.camel.spi.UnitOfWork;
049import org.apache.camel.util.MessageHelper;
050import org.apache.camel.util.OrderedComparator;
051import org.apache.camel.util.StopWatch;
052import org.apache.camel.util.UnitOfWorkHelper;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056/**
057 * Internal {@link Processor} that Camel routing engine used during routing for cross cutting functionality such as:
058 * <ul>
059 *     <li>Execute {@link UnitOfWork}</li>
060 *     <li>Keeping track which route currently is being routed</li>
061 *     <li>Execute {@link RoutePolicy}</li>
062 *     <li>Gather JMX performance statics</li>
063 *     <li>Tracing</li>
064 *     <li>Debugging</li>
065 *     <li>Message History</li>
066 *     <li>Stream Caching</li>
067 *     <li>{@link Transformer}</li>
068 * </ul>
069 * ... and more.
070 * <p/>
071 * This implementation executes this cross cutting functionality as a {@link CamelInternalProcessorAdvice} advice (before and after advice)
072 * by executing the {@link CamelInternalProcessorAdvice#before(org.apache.camel.Exchange)} and
073 * {@link CamelInternalProcessorAdvice#after(org.apache.camel.Exchange, Object)} callbacks in correct order during routing.
074 * This reduces number of stack frames needed during routing, and reduce the number of lines in stacktraces, as well
075 * makes debugging the routing engine easier for end users.
076 * <p/>
077 * <b>Debugging tips:</b> Camel end users whom want to debug their Camel applications with the Camel source code, then make sure to
078 * read the source code of this class about the debugging tips, which you can find in the
079 * {@link #process(org.apache.camel.Exchange, org.apache.camel.AsyncCallback)} method.
080 * <p/>
081 * The added advices can implement {@link Ordered} to control in which order the advices are executed.
082 */
083public class CamelInternalProcessor extends DelegateAsyncProcessor {
084
085    private static final Logger LOG = LoggerFactory.getLogger(CamelInternalProcessor.class);
086    private final List<CamelInternalProcessorAdvice> advices = new ArrayList<CamelInternalProcessorAdvice>();
087
088    public CamelInternalProcessor() {
089    }
090
091    public CamelInternalProcessor(Processor processor) {
092        super(processor);
093    }
094
095    /**
096     * Adds an {@link CamelInternalProcessorAdvice} advice to the list of advices to execute by this internal processor.
097     *
098     * @param advice  the advice to add
099     */
100    public void addAdvice(CamelInternalProcessorAdvice advice) {
101        advices.add(advice);
102        // ensure advices are sorted so they are in the order we want
103        advices.sort(new OrderedComparator());
104    }
105
106    /**
107     * Gets the advice with the given type.
108     *
109     * @param type  the type of the advice
110     * @return the advice if exists, or <tt>null</tt> if no advices has been added with the given type.
111     */
112    public <T> T getAdvice(Class<T> type) {
113        for (CamelInternalProcessorAdvice task : advices) {
114            if (type.isInstance(task)) {
115                return type.cast(task);
116            }
117        }
118        return null;
119    }
120
121    @Override
122    public boolean process(Exchange exchange, AsyncCallback callback) {
123        // ----------------------------------------------------------
124        // CAMEL END USER - READ ME FOR DEBUGGING TIPS
125        // ----------------------------------------------------------
126        // If you want to debug the Camel routing engine, then there is a lot of internal functionality
127        // the routing engine executes during routing messages. You can skip debugging this internal
128        // functionality and instead debug where the routing engine continues routing to the next node
129        // in the routes. The CamelInternalProcessor is a vital part of the routing engine, as its
130        // being used in between the nodes. As an end user you can just debug the code in this class
131        // in between the:
132        //   CAMEL END USER - DEBUG ME HERE +++ START +++
133        //   CAMEL END USER - DEBUG ME HERE +++ END +++
134        // you can see in the code below.
135        // ----------------------------------------------------------
136
137        if (processor == null || !continueProcessing(exchange)) {
138            // no processor or we should not continue then we are done
139            callback.done(true);
140            return true;
141        }
142
143        final List<Object> states = new ArrayList<Object>(advices.size());
144        for (CamelInternalProcessorAdvice task : advices) {
145            try {
146                Object state = task.before(exchange);
147                states.add(state);
148            } catch (Throwable e) {
149                exchange.setException(e);
150                callback.done(true);
151                return true;
152            }
153        }
154
155        // create internal callback which will execute the advices in reverse order when done
156        callback = new InternalCallback(states, exchange, callback);
157
158        // UNIT_OF_WORK_PROCESS_SYNC is @deprecated and we should remove it from Camel 3.0
159        Object synchronous = exchange.removeProperty(Exchange.UNIT_OF_WORK_PROCESS_SYNC);
160        if (exchange.isTransacted() || synchronous != null) {
161            // must be synchronized for transacted exchanges
162            if (LOG.isTraceEnabled()) {
163                if (exchange.isTransacted()) {
164                    LOG.trace("Transacted Exchange must be routed synchronously for exchangeId: {} -> {}", exchange.getExchangeId(), exchange);
165                } else {
166                    LOG.trace("Synchronous UnitOfWork Exchange must be routed synchronously for exchangeId: {} -> {}", exchange.getExchangeId(), exchange);
167                }
168            }
169            // ----------------------------------------------------------
170            // CAMEL END USER - DEBUG ME HERE +++ START +++
171            // ----------------------------------------------------------
172            try {
173                processor.process(exchange);
174            } catch (Throwable e) {
175                exchange.setException(e);
176            }
177            // ----------------------------------------------------------
178            // CAMEL END USER - DEBUG ME HERE +++ END +++
179            // ----------------------------------------------------------
180            callback.done(true);
181            return true;
182        } else {
183            final UnitOfWork uow = exchange.getUnitOfWork();
184
185            // allow unit of work to wrap callback in case it need to do some special work
186            // for example the MDCUnitOfWork
187            AsyncCallback async = callback;
188            if (uow != null) {
189                async = uow.beforeProcess(processor, exchange, callback);
190            }
191
192            // ----------------------------------------------------------
193            // CAMEL END USER - DEBUG ME HERE +++ START +++
194            // ----------------------------------------------------------
195            if (LOG.isTraceEnabled()) {
196                LOG.trace("Processing exchange for exchangeId: {} -> {}", exchange.getExchangeId(), exchange);
197            }
198            boolean sync = processor.process(exchange, async);
199            // ----------------------------------------------------------
200            // CAMEL END USER - DEBUG ME HERE +++ END +++
201            // ----------------------------------------------------------
202
203            // execute any after processor work (in current thread, not in the callback)
204            if (uow != null) {
205                uow.afterProcess(processor, exchange, callback, sync);
206            }
207
208            if (LOG.isTraceEnabled()) {
209                LOG.trace("Exchange processed and is continued routed {} for exchangeId: {} -> {}",
210                        new Object[]{sync ? "synchronously" : "asynchronously", exchange.getExchangeId(), exchange});
211            }
212            return sync;
213        }
214    }
215
216    @Override
217    public String toString() {
218        return processor != null ? processor.toString() : super.toString();
219    }
220
221    /**
222     * Internal callback that executes the after advices.
223     */
224    private final class InternalCallback implements AsyncCallback {
225
226        private final List<Object> states;
227        private final Exchange exchange;
228        private final AsyncCallback callback;
229
230        private InternalCallback(List<Object> states, Exchange exchange, AsyncCallback callback) {
231            this.states = states;
232            this.exchange = exchange;
233            this.callback = callback;
234        }
235
236        @Override
237        public void done(boolean doneSync) {
238            // NOTE: if you are debugging Camel routes, then all the code in the for loop below is internal only
239            // so you can step straight to the finally block and invoke the callback
240
241            // we should call after in reverse order
242            try {
243                for (int i = advices.size() - 1; i >= 0; i--) {
244                    CamelInternalProcessorAdvice task = advices.get(i);
245                    Object state = states.get(i);
246                    try {
247                        task.after(exchange, state);
248                    } catch (Exception e) {
249                        exchange.setException(e);
250                        // allow all advices to complete even if there was an exception
251                    }
252                }
253            } finally {
254                // ----------------------------------------------------------
255                // CAMEL END USER - DEBUG ME HERE +++ START +++
256                // ----------------------------------------------------------
257                // callback must be called
258                callback.done(doneSync);
259                // ----------------------------------------------------------
260                // CAMEL END USER - DEBUG ME HERE +++ END +++
261                // ----------------------------------------------------------
262            }
263        }
264    }
265
266    /**
267     * Strategy to determine if we should continue processing the {@link Exchange}.
268     */
269    protected boolean continueProcessing(Exchange exchange) {
270        Object stop = exchange.getProperty(Exchange.ROUTE_STOP);
271        if (stop != null) {
272            boolean doStop = exchange.getContext().getTypeConverter().convertTo(Boolean.class, stop);
273            if (doStop) {
274                LOG.debug("Exchange is marked to stop routing: {}", exchange);
275                return false;
276            }
277        }
278
279        // determine if we can still run, or the camel context is forcing a shutdown
280        boolean forceShutdown = exchange.getContext().getShutdownStrategy().forceShutdown(this);
281        if (forceShutdown) {
282            String msg = "Run not allowed as ShutdownStrategy is forcing shutting down, will reject executing exchange: " + exchange;
283            LOG.debug(msg);
284            if (exchange.getException() == null) {
285                exchange.setException(new RejectedExecutionException(msg));
286            }
287            return false;
288        }
289
290        // yes we can continue
291        return true;
292    }
293
294    /**
295     * Advice to invoke callbacks for before and after routing.
296     */
297    public static class RouteLifecycleAdvice implements CamelInternalProcessorAdvice<Object> {
298
299        private Route route;
300
301        public void setRoute(Route route) {
302            this.route = route;
303        }
304
305        @Override
306        public Object before(Exchange exchange) throws Exception {
307            UnitOfWork uow = exchange.getUnitOfWork();
308            if (uow != null) {
309                uow.beforeRoute(exchange, route);
310            }
311            return null;
312        }
313
314        @Override
315        public void after(Exchange exchange, Object object) throws Exception {
316            UnitOfWork uow = exchange.getUnitOfWork();
317            if (uow != null) {
318                uow.afterRoute(exchange, route);
319            }
320        }
321    }
322
323    /**
324     * Advice for JMX instrumentation of the process being invoked.
325     * <p/>
326     * This advice keeps track of JMX metrics for performance statistics.
327     * <p/>
328     * The current implementation of this advice is only used for route level statistics. For processor levels
329     * they are still wrapped in the route processor chains.
330     */
331    public static class InstrumentationAdvice implements CamelInternalProcessorAdvice<StopWatch> {
332
333        private PerformanceCounter counter;
334        private String type;
335
336        public InstrumentationAdvice(String type) {
337            this.type = type;
338        }
339
340        public void setCounter(Object counter) {
341            ManagedPerformanceCounter mpc = null;
342            if (counter instanceof ManagedPerformanceCounter) {
343                mpc = (ManagedPerformanceCounter) counter;
344            }
345
346            if (this.counter instanceof DelegatePerformanceCounter) {
347                ((DelegatePerformanceCounter) this.counter).setCounter(mpc);
348            } else if (mpc != null) {
349                this.counter = mpc;
350            } else if (counter instanceof PerformanceCounter) {
351                this.counter = (PerformanceCounter) counter;
352            }
353        }
354
355        protected void beginTime(Exchange exchange) {
356            counter.processExchange(exchange);
357        }
358
359        protected void recordTime(Exchange exchange, long duration) {
360            if (LOG.isTraceEnabled()) {
361                LOG.trace("{}Recording duration: {} millis for exchange: {}", new Object[]{type != null ? type + ": " : "", duration, exchange});
362            }
363
364            if (!exchange.isFailed() && exchange.getException() == null) {
365                counter.completedExchange(exchange, duration);
366            } else {
367                counter.failedExchange(exchange);
368            }
369        }
370
371        public String getType() {
372            return type;
373        }
374
375        public void setType(String type) {
376            this.type = type;
377        }
378
379        @Override
380        public StopWatch before(Exchange exchange) throws Exception {
381            // only record time if stats is enabled
382            StopWatch answer = counter != null && counter.isStatisticsEnabled() ? new StopWatch() : null;
383            if (answer != null) {
384                beginTime(exchange);
385            }
386            return answer;
387        }
388
389        @Override
390        public void after(Exchange exchange, StopWatch watch) throws Exception {
391            // record end time
392            if (watch != null) {
393                recordTime(exchange, watch.stop());
394            }
395        }
396    }
397
398    /**
399     * Advice to inject the current {@link RouteContext} into the {@link UnitOfWork} on the {@link Exchange}
400     *
401     * @deprecated this logic has been merged into {@link org.apache.camel.processor.CamelInternalProcessor.UnitOfWorkProcessorAdvice}
402     */
403    @Deprecated
404    public static class RouteContextAdvice implements CamelInternalProcessorAdvice<UnitOfWork> {
405
406        private final RouteContext routeContext;
407
408        public RouteContextAdvice(RouteContext routeContext) {
409            this.routeContext = routeContext;
410        }
411
412        @Override
413        public UnitOfWork before(Exchange exchange) throws Exception {
414            // push the current route context
415            final UnitOfWork unitOfWork = exchange.getUnitOfWork();
416            if (unitOfWork != null) {
417                unitOfWork.pushRouteContext(routeContext);
418            }
419            return unitOfWork;
420        }
421
422        @Override
423        public void after(Exchange exchange, UnitOfWork unitOfWork) throws Exception {
424            if (unitOfWork != null) {
425                unitOfWork.popRouteContext();
426            }
427        }
428    }
429
430    /**
431     * Advice to keep the {@link InflightRepository} up to date.
432     */
433    public static class RouteInflightRepositoryAdvice implements CamelInternalProcessorAdvice {
434
435        private final InflightRepository inflightRepository;
436        private final String id;
437
438        public RouteInflightRepositoryAdvice(InflightRepository inflightRepository, String id) {
439            this.inflightRepository = inflightRepository;
440            this.id = id;
441        }
442
443        @Override
444        public Object before(Exchange exchange) throws Exception {
445            inflightRepository.add(exchange, id);
446            return null;
447        }
448
449        @Override
450        public void after(Exchange exchange, Object state) throws Exception {
451            inflightRepository.remove(exchange, id);
452        }
453    }
454
455    /**
456     * Advice to execute any {@link RoutePolicy} a route may have been configured with.
457     */
458    public static class RoutePolicyAdvice implements CamelInternalProcessorAdvice {
459
460        private final List<RoutePolicy> routePolicies;
461        private Route route;
462
463        public RoutePolicyAdvice(List<RoutePolicy> routePolicies) {
464            this.routePolicies = routePolicies;
465        }
466
467        public void setRoute(Route route) {
468            this.route = route;
469        }
470
471        /**
472         * Strategy to determine if this policy is allowed to run
473         *
474         * @param policy the policy
475         * @return <tt>true</tt> to run
476         */
477        protected boolean isRoutePolicyRunAllowed(RoutePolicy policy) {
478            if (policy instanceof StatefulService) {
479                StatefulService ss = (StatefulService) policy;
480                return ss.isRunAllowed();
481            }
482            return true;
483        }
484
485        @Override
486        public Object before(Exchange exchange) throws Exception {
487            // invoke begin
488            for (RoutePolicy policy : routePolicies) {
489                try {
490                    if (isRoutePolicyRunAllowed(policy)) {
491                        policy.onExchangeBegin(route, exchange);
492                    }
493                } catch (Exception e) {
494                    LOG.warn("Error occurred during onExchangeBegin on RoutePolicy: " + policy
495                            + ". This exception will be ignored", e);
496                }
497            }
498            return null;
499        }
500
501        @Override
502        public void after(Exchange exchange, Object data) throws Exception {
503            // do not invoke it if Camel is stopping as we don't want
504            // the policy to start a consumer during Camel is stopping
505            if (isCamelStopping(exchange.getContext())) {
506                return;
507            }
508
509            for (RoutePolicy policy : routePolicies) {
510                try {
511                    if (isRoutePolicyRunAllowed(policy)) {
512                        policy.onExchangeDone(route, exchange);
513                    }
514                } catch (Exception e) {
515                    LOG.warn("Error occurred during onExchangeDone on RoutePolicy: " + policy
516                            + ". This exception will be ignored", e);
517                }
518            }
519        }
520
521        private static boolean isCamelStopping(CamelContext context) {
522            if (context instanceof StatefulService) {
523                StatefulService ss = (StatefulService) context;
524                return ss.isStopping() || ss.isStopped();
525            }
526            return false;
527        }
528    }
529
530    /**
531     * Advice to execute the {@link BacklogTracer} if enabled.
532     */
533    public static final class BacklogTracerAdvice implements CamelInternalProcessorAdvice, Ordered {
534
535        private final BacklogTracer backlogTracer;
536        private final ProcessorDefinition<?> processorDefinition;
537        private final ProcessorDefinition<?> routeDefinition;
538        private final boolean first;
539
540        public BacklogTracerAdvice(BacklogTracer backlogTracer, ProcessorDefinition<?> processorDefinition,
541                                   ProcessorDefinition<?> routeDefinition, boolean first) {
542            this.backlogTracer = backlogTracer;
543            this.processorDefinition = processorDefinition;
544            this.routeDefinition = routeDefinition;
545            this.first = first;
546        }
547
548        @Override
549        public Object before(Exchange exchange) throws Exception {
550            if (backlogTracer.shouldTrace(processorDefinition, exchange)) {
551                Date timestamp = new Date();
552                String toNode = processorDefinition.getId();
553                String exchangeId = exchange.getExchangeId();
554                String messageAsXml = MessageHelper.dumpAsXml(exchange.getIn(), true, 4,
555                        backlogTracer.isBodyIncludeStreams(), backlogTracer.isBodyIncludeFiles(), backlogTracer.getBodyMaxChars());
556
557                // if first we should add a pseudo trace message as well, so we have a starting message (eg from the route)
558                String routeId = routeDefinition != null ? routeDefinition.getId() : null;
559                if (first) {
560                    Date created = exchange.getProperty(Exchange.CREATED_TIMESTAMP, timestamp, Date.class);
561                    DefaultBacklogTracerEventMessage pseudo = new DefaultBacklogTracerEventMessage(backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId, messageAsXml);
562                    backlogTracer.traceEvent(pseudo);
563                }
564                DefaultBacklogTracerEventMessage event = new DefaultBacklogTracerEventMessage(backlogTracer.incrementTraceCounter(), timestamp, routeId, toNode, exchangeId, messageAsXml);
565                backlogTracer.traceEvent(event);
566            }
567
568            return null;
569        }
570
571        @Override
572        public void after(Exchange exchange, Object data) throws Exception {
573            // noop
574        }
575
576        @Override
577        public int getOrder() {
578            // we want tracer just before calling the processor
579            return Ordered.LOWEST - 1;
580        }
581
582    }
583
584    /**
585     * Advice to execute the {@link org.apache.camel.processor.interceptor.BacklogDebugger} if enabled.
586     */
587    public static final class BacklogDebuggerAdvice implements CamelInternalProcessorAdvice<StopWatch>, Ordered {
588
589        private final BacklogDebugger backlogDebugger;
590        private final Processor target;
591        private final ProcessorDefinition<?> definition;
592        private final String nodeId;
593
594        public BacklogDebuggerAdvice(BacklogDebugger backlogDebugger, Processor target, ProcessorDefinition<?> definition) {
595            this.backlogDebugger = backlogDebugger;
596            this.target = target;
597            this.definition = definition;
598            this.nodeId = definition.getId();
599        }
600
601        @Override
602        public StopWatch before(Exchange exchange) throws Exception {
603            if (backlogDebugger.isEnabled() && (backlogDebugger.hasBreakpoint(nodeId) || backlogDebugger.isSingleStepMode())) {
604                StopWatch watch = new StopWatch();
605                backlogDebugger.beforeProcess(exchange, target, definition);
606                return watch;
607            } else {
608                return null;
609            }
610        }
611
612        @Override
613        public void after(Exchange exchange, StopWatch stopWatch) throws Exception {
614            if (stopWatch != null) {
615                backlogDebugger.afterProcess(exchange, target, definition, stopWatch.stop());
616            }
617        }
618
619        @Override
620        public int getOrder() {
621            // we want debugger just before calling the processor
622            return Ordered.LOWEST;
623        }
624    }
625
626    /**
627     * Advice to inject new {@link UnitOfWork} to the {@link Exchange} if needed, and as well to ensure
628     * the {@link UnitOfWork} is done and stopped.
629     */
630    public static class UnitOfWorkProcessorAdvice implements CamelInternalProcessorAdvice<UnitOfWork> {
631
632        private final RouteContext routeContext;
633
634        public UnitOfWorkProcessorAdvice(RouteContext routeContext) {
635            this.routeContext = routeContext;
636        }
637
638        @Override
639        public UnitOfWork before(Exchange exchange) throws Exception {
640            // if the exchange doesn't have from route id set, then set it if it originated
641            // from this unit of work
642            if (routeContext != null && exchange.getFromRouteId() == null) {
643                String routeId = routeContext.getRoute().idOrCreate(routeContext.getCamelContext().getNodeIdFactory());
644                exchange.setFromRouteId(routeId);
645            }
646
647            // only return UnitOfWork if we created a new as then its us that handle the lifecycle to done the created UoW
648            UnitOfWork created = null;
649
650            if (exchange.getUnitOfWork() == null) {
651                // If there is no existing UoW, then we should start one and
652                // terminate it once processing is completed for the exchange.
653                created = createUnitOfWork(exchange);
654                exchange.setUnitOfWork(created);
655                created.start();
656            }
657
658            // for any exchange we should push/pop route context so we can keep track of which route we are routing
659            if (routeContext != null) {
660                UnitOfWork existing = exchange.getUnitOfWork();
661                if (existing != null) {
662                    existing.pushRouteContext(routeContext);
663                }
664            }
665
666            return created;
667        }
668
669        @Override
670        public void after(Exchange exchange, UnitOfWork uow) throws Exception {
671            UnitOfWork existing = exchange.getUnitOfWork();
672
673            // execute done on uow if we created it, and the consumer is not doing it
674            if (uow != null) {
675                UnitOfWorkHelper.doneUow(uow, exchange);
676            }
677
678            // after UoW is done lets pop the route context which must be done on every existing UoW
679            if (routeContext != null && existing != null) {
680                existing.popRouteContext();
681            }
682        }
683
684        protected UnitOfWork createUnitOfWork(Exchange exchange) {
685            return exchange.getContext().getUnitOfWorkFactory().createUnitOfWork(exchange);
686        }
687
688    }
689
690    /**
691     * Advice when an EIP uses the <tt>shareUnitOfWork</tt> functionality.
692     */
693    public static class ChildUnitOfWorkProcessorAdvice extends UnitOfWorkProcessorAdvice {
694
695        private final UnitOfWork parent;
696
697        public ChildUnitOfWorkProcessorAdvice(RouteContext routeContext, UnitOfWork parent) {
698            super(routeContext);
699            this.parent = parent;
700        }
701
702        @Override
703        protected UnitOfWork createUnitOfWork(Exchange exchange) {
704            // let the parent create a child unit of work to be used
705            return parent.createChildUnitOfWork(exchange);
706        }
707
708    }
709
710    /**
711     * Advice when an EIP uses the <tt>shareUnitOfWork</tt> functionality.
712     */
713    public static class SubUnitOfWorkProcessorAdvice implements CamelInternalProcessorAdvice<UnitOfWork> {
714
715        @Override
716        public UnitOfWork before(Exchange exchange) throws Exception {
717            // begin savepoint
718            exchange.getUnitOfWork().beginSubUnitOfWork(exchange);
719            return exchange.getUnitOfWork();
720        }
721
722        @Override
723        public void after(Exchange exchange, UnitOfWork unitOfWork) throws Exception {
724            // end sub unit of work
725            unitOfWork.endSubUnitOfWork(exchange);
726        }
727    }
728
729    /**
730     * Advice when Message History has been enabled.
731     */
732    @SuppressWarnings("unchecked")
733    public static class MessageHistoryAdvice implements CamelInternalProcessorAdvice<MessageHistory> {
734
735        private final MessageHistoryFactory factory;
736        private final ProcessorDefinition<?> definition;
737        private final String routeId;
738
739        public MessageHistoryAdvice(MessageHistoryFactory factory, ProcessorDefinition<?> definition) {
740            this.factory = factory;
741            this.definition = definition;
742            this.routeId = ProcessorDefinitionHelper.getRouteId(definition);
743        }
744
745        @Override
746        public MessageHistory before(Exchange exchange) throws Exception {
747            List<MessageHistory> list = exchange.getProperty(Exchange.MESSAGE_HISTORY, List.class);
748            if (list == null) {
749                list = new LinkedList<>();
750                exchange.setProperty(Exchange.MESSAGE_HISTORY, list);
751            }
752
753            // we may be routing outside a route in an onException or interceptor and if so then grab
754            // route id from the exchange UoW state
755            String targetRouteId = this.routeId;
756            if (targetRouteId == null) {
757                UnitOfWork uow = exchange.getUnitOfWork();
758                if (uow != null && uow.getRouteContext() != null) {
759                    targetRouteId = uow.getRouteContext().getRoute().getId();
760                }
761            }
762
763            MessageHistory history = factory.newMessageHistory(targetRouteId, definition, new Date());
764            list.add(history);
765            return history;
766        }
767
768        @Override
769        public void after(Exchange exchange, MessageHistory history) throws Exception {
770            if (history != null) {
771                history.nodeProcessingDone();
772            }
773        }
774    }
775
776    /**
777     * Advice for {@link org.apache.camel.spi.StreamCachingStrategy}
778     */
779    public static class StreamCachingAdvice implements CamelInternalProcessorAdvice<StreamCache>, Ordered {
780
781        private final StreamCachingStrategy strategy;
782
783        public StreamCachingAdvice(StreamCachingStrategy strategy) {
784            this.strategy = strategy;
785        }
786
787        @Override
788        public StreamCache before(Exchange exchange) throws Exception {
789            // check if body is already cached
790            Object body = exchange.getIn().getBody();
791            if (body == null) {
792                return null;
793            } else if (body instanceof StreamCache) {
794                StreamCache sc = (StreamCache) body;
795                // reset so the cache is ready to be used before processing
796                sc.reset();
797                return sc;
798            }
799            // cache the body and if we could do that replace it as the new body
800            StreamCache sc = strategy.cache(exchange);
801            if (sc != null) {
802                exchange.getIn().setBody(sc);
803            }
804            return sc;
805        }
806
807        @Override
808        public void after(Exchange exchange, StreamCache sc) throws Exception {
809            Object body;
810            if (exchange.hasOut()) {
811                body = exchange.getOut().getBody();
812            } else {
813                body = exchange.getIn().getBody();
814            }
815            if (body != null && body instanceof StreamCache) {
816                // reset so the cache is ready to be reused after processing
817                ((StreamCache) body).reset();
818            }
819        }
820
821        @Override
822        public int getOrder() {
823            // we want stream caching first
824            return Ordered.HIGHEST;
825        }
826    }
827
828    /**
829     * Advice for delaying
830     */
831    public static class DelayerAdvice implements CamelInternalProcessorAdvice {
832
833        private final long delay;
834
835        public DelayerAdvice(long delay) {
836            this.delay = delay;
837        }
838
839        @Override
840        public Object before(Exchange exchange) throws Exception {
841            try {
842                LOG.trace("Sleeping for: {} millis", delay);
843                Thread.sleep(delay);
844            } catch (InterruptedException e) {
845                LOG.debug("Sleep interrupted");
846                Thread.currentThread().interrupt();
847                throw e;
848            }
849            return null;
850        }
851
852        @Override
853        public void after(Exchange exchange, Object data) throws Exception {
854            // noop
855        }
856    }
857}