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