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