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     */
017    package org.apache.camel.processor.interceptor;
018    
019    import java.util.ArrayList;
020    import java.util.Collections;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.concurrent.RejectedExecutionException;
024    
025    import org.apache.camel.AsyncCallback;
026    import org.apache.camel.AsyncProcessor;
027    import org.apache.camel.CamelContext;
028    import org.apache.camel.CamelContextAware;
029    import org.apache.camel.Channel;
030    import org.apache.camel.Exchange;
031    import org.apache.camel.Processor;
032    import org.apache.camel.Service;
033    import org.apache.camel.model.ModelChannel;
034    import org.apache.camel.model.ProcessorDefinition;
035    import org.apache.camel.processor.InterceptorToAsyncProcessorBridge;
036    import org.apache.camel.processor.RouteContextProcessor;
037    import org.apache.camel.spi.InterceptStrategy;
038    import org.apache.camel.spi.LifecycleStrategy;
039    import org.apache.camel.spi.RouteContext;
040    import org.apache.camel.support.ServiceSupport;
041    import org.apache.camel.util.AsyncProcessorHelper;
042    import org.apache.camel.util.ObjectHelper;
043    import org.apache.camel.util.OrderedComparator;
044    import org.apache.camel.util.ServiceHelper;
045    import org.slf4j.Logger;
046    import org.slf4j.LoggerFactory;
047    
048    /**
049     * DefaultChannel is the default {@link Channel}.
050     * <p/>
051     * The current implementation is just a composite containing the interceptors and error handler
052     * that beforehand was added to the route graph directly.
053     * <br/>
054     * With this {@link Channel} we can in the future implement better strategies for routing the
055     * {@link Exchange} in the route graph, as we have a {@link Channel} between each and every node
056     * in the graph.
057     *
058     * @version 
059     */
060    public class DefaultChannel extends ServiceSupport implements ModelChannel {
061    
062        private static final transient Logger LOG = LoggerFactory.getLogger(DefaultChannel.class);
063    
064        private final List<InterceptStrategy> interceptors = new ArrayList<InterceptStrategy>();
065        private Processor errorHandler;
066        // the next processor (non wrapped)
067        private Processor nextProcessor;
068        // the real output to invoke that has been wrapped
069        private Processor output;
070        private ProcessorDefinition<?> definition;
071        private ProcessorDefinition<?> childDefinition;
072        private CamelContext camelContext;
073        private RouteContext routeContext;
074        private RouteContextProcessor routeContextProcessor;
075    
076        public List<Processor> next() {
077            List<Processor> answer = new ArrayList<Processor>(1);
078            answer.add(nextProcessor);
079            return answer;
080        }
081    
082        public boolean hasNext() {
083            return nextProcessor != null;
084        }
085    
086        public void setNextProcessor(Processor next) {
087            this.nextProcessor = next;
088        }
089    
090        public Processor getOutput() {
091            // the errorHandler is already decorated with interceptors
092            // so it contain the entire chain of processors, so we can safely use it directly as output
093            // if no error handler provided we use the output
094            // TODO: Camel 3.0 we should determine the output dynamically at runtime instead of having the
095            // the error handlers, interceptors, etc. woven in at design time
096            return errorHandler != null ? errorHandler : output;
097        }
098    
099        public void setOutput(Processor output) {
100            this.output = output;
101        }
102    
103        public Processor getNextProcessor() {
104            return nextProcessor;
105        }
106    
107        public boolean hasInterceptorStrategy(Class<?> type) {
108            for (InterceptStrategy strategy : interceptors) {
109                if (type.isInstance(strategy)) {
110                    return true;
111                }
112            }
113            return false;
114        }
115    
116        public void setErrorHandler(Processor errorHandler) {
117            this.errorHandler = errorHandler;
118        }
119    
120        public Processor getErrorHandler() {
121            return errorHandler;
122        }
123    
124        public void addInterceptStrategy(InterceptStrategy strategy) {
125            interceptors.add(strategy);
126        }
127    
128        public void addInterceptStrategies(List<InterceptStrategy> strategies) {
129            interceptors.addAll(strategies);
130        }
131    
132        public List<InterceptStrategy> getInterceptStrategies() {
133            return interceptors;
134        }
135    
136        public ProcessorDefinition<?> getProcessorDefinition() {
137            return definition;
138        }
139    
140        public void setChildDefinition(ProcessorDefinition<?> childDefinition) {
141            this.childDefinition = childDefinition;
142        }
143    
144        public RouteContext getRouteContext() {
145            return routeContext;
146        }
147    
148        @Override
149        protected void doStart() throws Exception {
150            // create route context processor to wrap output
151            routeContextProcessor = new RouteContextProcessor(routeContext, getOutput());
152            ServiceHelper.startServices(errorHandler, output, routeContextProcessor);
153        }
154    
155        @Override
156        protected void doStop() throws Exception {
157            ServiceHelper.stopServices(output, errorHandler, routeContextProcessor);
158        }
159    
160        public void initChannel(ProcessorDefinition<?> outputDefinition, RouteContext routeContext) throws Exception {
161            this.routeContext = routeContext;
162            this.definition = outputDefinition;
163            this.camelContext = routeContext.getCamelContext();
164    
165            Processor target = nextProcessor;
166            Processor next;
167    
168            // init CamelContextAware as early as possible on target
169            if (target instanceof CamelContextAware) {
170                ((CamelContextAware) target).setCamelContext(camelContext);
171            }
172    
173            // the definition to wrap should be the fine grained,
174            // so if a child is set then use it, if not then its the original output used
175            ProcessorDefinition<?> targetOutputDef = childDefinition != null ? childDefinition : outputDefinition;
176            LOG.debug("Initialize channel for target: '{}'", targetOutputDef);
177    
178            // fix parent/child relationship. This will be the case of the routes has been
179            // defined using XML DSL or end user may have manually assembled a route from the model.
180            // Background note: parent/child relationship is assembled on-the-fly when using Java DSL (fluent builders)
181            // where as when using XML DSL (JAXB) then it fixed after, but if people are using custom interceptors
182            // then we need to fix the parent/child relationship beforehand, and thus we can do it here
183            // ideally we need the design time route -> runtime route to be a 2-phase pass (scheduled work for Camel 3.0)
184            if (childDefinition != null && outputDefinition != childDefinition) {
185                childDefinition.setParent(outputDefinition);
186            }
187    
188            // first wrap the output with the managed strategy if any
189            InterceptStrategy managed = routeContext.getManagedInterceptStrategy();
190            if (managed != null) {
191                next = target == nextProcessor ? null : nextProcessor;
192                target = managed.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, next);
193            }
194    
195            // then wrap the output with the tracer
196            TraceInterceptor trace = (TraceInterceptor) getOrCreateTracer().wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, null);
197            // trace interceptor need to have a reference to route context so we at runtime can enable/disable tracing on-the-fly
198            trace.setRouteContext(routeContext);
199            target = trace;
200    
201            // sort interceptors according to ordered
202            Collections.sort(interceptors, new OrderedComparator());
203            // then reverse list so the first will be wrapped last, as it would then be first being invoked
204            Collections.reverse(interceptors);
205            // wrap the output with the configured interceptors
206            for (InterceptStrategy strategy : interceptors) {
207                next = target == nextProcessor ? null : nextProcessor;
208                // skip tracer as we did the specially beforehand and it could potentially be added as an interceptor strategy
209                if (strategy instanceof Tracer) {
210                    continue;
211                }
212                // skip stream caching as it must be wrapped as outer most, which we do later
213                if (strategy instanceof StreamCaching) {
214                    continue;
215                }
216                // use the fine grained definition (eg the child if available). Its always possible to get back to the parent
217                Processor wrapped = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, next);
218                if (!(wrapped instanceof AsyncProcessor)) {
219                    LOG.warn("Interceptor: " + strategy + " at: " + outputDefinition + " does not return an AsyncProcessor instance."
220                            + " This causes the asynchronous routing engine to not work as optimal as possible."
221                            + " See more details at the InterceptStrategy javadoc."
222                            + " Camel will use a bridge to adapt the interceptor to the asynchronous routing engine,"
223                            + " but its not the most optimal solution. Please consider changing your interceptor to comply.");
224    
225                    // use a bridge and wrap again which allows us to adapt and leverage the asynchronous routing engine anyway
226                    // however its not the most optimal solution, but we can still run.
227                    InterceptorToAsyncProcessorBridge bridge = new InterceptorToAsyncProcessorBridge(target);
228                    wrapped = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, bridge, next);
229                    bridge.setTarget(wrapped);
230                    wrapped = bridge;
231                }
232                target = wrapped;
233            }
234    
235            // sets the delegate to our wrapped output
236            output = target;
237        }
238    
239        @Override
240        public void postInitChannel(ProcessorDefinition<?> outputDefinition, RouteContext routeContext) throws Exception {
241            for (InterceptStrategy strategy : interceptors) {
242                // apply stream caching at the end as it should be outer most
243                if (strategy instanceof StreamCaching) {
244                    if (errorHandler != null) {
245                        errorHandler = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), outputDefinition, errorHandler, null);
246                    } else {
247                        output = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), outputDefinition, output, null);
248                    }
249                    break;
250                }
251            }
252        }
253    
254        private InterceptStrategy getOrCreateTracer() {
255            InterceptStrategy tracer = Tracer.getTracer(camelContext);
256            if (tracer == null) {
257                if (camelContext.getRegistry() != null) {
258                    // lookup in registry
259                    Map<String, Tracer> map = camelContext.getRegistry().lookupByType(Tracer.class);
260                    if (map.size() == 1) {
261                        tracer = map.values().iterator().next();
262                    }
263                }
264                if (tracer == null) {
265                    // fallback to use the default tracer
266                    tracer = camelContext.getDefaultTracer();
267    
268                    // configure and use any trace formatter if any exists
269                    Map<String, TraceFormatter> formatters = camelContext.getRegistry().lookupByType(TraceFormatter.class);
270                    if (formatters.size() == 1) {
271                        TraceFormatter formatter = formatters.values().iterator().next();
272                        if (tracer instanceof Tracer) {
273                            ((Tracer) tracer).setFormatter(formatter);
274                        }
275                    }
276                }
277            }
278    
279            // which we must manage as well
280            for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
281                if (tracer instanceof Service) {
282                    strategy.onServiceAdd(camelContext, (Service) tracer, null);
283                }
284            }
285    
286            return tracer;
287        }
288    
289        public void process(Exchange exchange) throws Exception {
290            AsyncProcessorHelper.process(this, exchange);
291        }
292    
293        public boolean process(final Exchange exchange, final AsyncCallback callback) {
294            Processor processor = getOutput();
295            if (processor == null || !continueProcessing(exchange)) {
296                // we should not continue routing so we are done
297                callback.done(true);
298                return true;
299            }
300    
301            // process the exchange using the route context processor
302            ObjectHelper.notNull(routeContextProcessor, "RouteContextProcessor", this);
303            return routeContextProcessor.process(exchange, callback);
304        }
305    
306        /**
307         * Strategy to determine if we should continue processing the {@link Exchange}.
308         */
309        protected boolean continueProcessing(Exchange exchange) {
310            Object stop = exchange.getProperty(Exchange.ROUTE_STOP);
311            if (stop != null) {
312                boolean doStop = exchange.getContext().getTypeConverter().convertTo(Boolean.class, stop);
313                if (doStop) {
314                    LOG.debug("Exchange is marked to stop routing: {}", exchange);
315                    return false;
316                }
317            }
318    
319            // determine if we can still run, or the camel context is forcing a shutdown
320            boolean forceShutdown = camelContext.getShutdownStrategy().forceShutdown(this);
321            if (forceShutdown) {
322                LOG.debug("Run not allowed as ShutdownStrategy is forcing shutting down, will reject executing exchange: {}", exchange);
323                if (exchange.getException() == null) {
324                    exchange.setException(new RejectedExecutionException());
325                }
326                return false;
327            }
328    
329            // yes we can continue
330            return true;
331        }
332    
333        @Override
334        public String toString() {
335            // just output the next processor as all the interceptors and error handler is just too verbose
336            return "Channel[" + nextProcessor + "]";
337        }
338    
339    }