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;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.Map;
022    
023    import org.apache.camel.CamelContext;
024    import org.apache.camel.Channel;
025    import org.apache.camel.Exchange;
026    import org.apache.camel.Processor;
027    import org.apache.camel.Service;
028    import org.apache.camel.impl.ServiceSupport;
029    import org.apache.camel.model.ProcessorDefinition;
030    import org.apache.camel.processor.interceptor.TraceFormatter;
031    import org.apache.camel.processor.interceptor.TraceInterceptor;
032    import org.apache.camel.processor.interceptor.Tracer;
033    import org.apache.camel.spi.InterceptStrategy;
034    import org.apache.camel.spi.LifecycleStrategy;
035    import org.apache.camel.spi.RouteContext;
036    import org.apache.camel.util.ServiceHelper;
037    import org.apache.commons.logging.Log;
038    import org.apache.commons.logging.LogFactory;
039    
040    /**
041     * DefaultChannel is the default {@link Channel}.
042     * <p/>
043     * The current implementation is just a composite containing the interceptors and error handler
044     * that beforehand was added to the route graph directly.
045     * <br/>
046     * With this {@link Channel} we can in the future implement better strategies for routing the
047     * {@link Exchange} in the route graph, as we have a {@link Channel} between each and every node
048     * in the graph.
049     *
050     * @version $Revision: 899628 $
051     */
052    public class DefaultChannel extends ServiceSupport implements Processor, Channel {
053    
054        private static final transient Log LOG = LogFactory.getLog(DefaultChannel.class);
055    
056        private final List<InterceptStrategy> interceptors = new ArrayList<InterceptStrategy>();
057        private Processor errorHandler;
058        // the next processor (non wrapped)
059        private Processor nextProcessor;
060        // the real output to invoke that has been wrapped
061        private Processor output;
062        private ProcessorDefinition<?> definition;
063        private ProcessorDefinition<?> childDefinition;
064        private CamelContext camelContext;
065        private RouteContext routeContext;
066    
067        public List<Processor> next() {
068            List<Processor> answer = new ArrayList<Processor>(1);
069            answer.add(nextProcessor);
070            return answer;
071        }
072    
073        public boolean hasNext() {
074            return nextProcessor != null;
075        }
076    
077        public void setNextProcessor(Processor next) {
078            this.nextProcessor = next;
079        }
080    
081        public Processor getOutput() {
082            // the errorHandler is already decorated with interceptors
083            // so it contain the entire chain of processors, so we can safely use it directly as output
084            // if no error handler provided we use the output
085            return errorHandler != null ? errorHandler : output;
086        }
087    
088        public void setOutput(Processor output) {
089            this.output = output;
090        }
091    
092        public Processor getNextProcessor() {
093            return nextProcessor;
094        }
095    
096        public boolean hasInterceptorStrategy(Class<?> type) {
097            for (InterceptStrategy strategy : interceptors) {
098                if (type.isInstance(strategy)) {
099                    return true;
100                }
101            }
102            return false;
103        }
104    
105        public void setErrorHandler(Processor errorHandler) {
106            this.errorHandler = errorHandler;
107        }
108    
109        public Processor getErrorHandler() {
110            return errorHandler;
111        }
112    
113        public void addInterceptStrategy(InterceptStrategy strategy) {
114            interceptors.add(strategy);
115        }
116    
117        public void addInterceptStrategies(List<InterceptStrategy> strategies) {
118            interceptors.addAll(strategies);
119        }
120    
121        public List<InterceptStrategy> getInterceptStrategies() {
122            return interceptors;
123        }
124    
125        public ProcessorDefinition<?> getProcessorDefinition() {
126            return definition;
127        }
128    
129        public void setChildDefinition(ProcessorDefinition<?> childDefinition) {
130            this.childDefinition = childDefinition;
131        }
132    
133        public RouteContext getRouteContext() {
134            return routeContext;
135        }
136    
137        @Override
138        protected void doStart() throws Exception {
139            ServiceHelper.startServices(errorHandler, output);
140        }
141    
142        @Override
143        protected void doStop() throws Exception {
144            ServiceHelper.stopServices(output, errorHandler);
145        }
146    
147        public void initChannel(ProcessorDefinition<?> outputDefinition, RouteContext routeContext) throws Exception {
148            this.routeContext = routeContext;
149            this.definition = outputDefinition;
150            this.camelContext = routeContext.getCamelContext();
151    
152            Processor target = nextProcessor;
153            Processor next;
154    
155            // first wrap the output with the managed strategy if any
156            InterceptStrategy managed = routeContext.getManagedInterceptStrategy();
157            if (managed != null) {
158                next = target == nextProcessor ? null : nextProcessor;
159                target = managed.wrapProcessorInInterceptors(routeContext.getCamelContext(), outputDefinition, target, next);
160            }
161    
162            // then wrap the output with the tracer
163            // the tracer should have the fine grained definition so if a child is set then use it, if not then its the original output used
164            ProcessorDefinition<?> traceDef = childDefinition != null ? childDefinition : outputDefinition;
165            TraceInterceptor trace = (TraceInterceptor) getOrCreateTracer().wrapProcessorInInterceptors(routeContext.getCamelContext(), traceDef, target, null);
166            // trace interceptor need to have a reference to route context so we at runtime can enable/disable tracing on-the-fly
167            trace.setRouteContext(routeContext);
168            target = trace;
169    
170            // wrap the output with the configured interceptors
171            for (InterceptStrategy strategy : interceptors) {
172                next = target == nextProcessor ? null : nextProcessor;
173                // skip tracer as we did the specially beforehand and it could potentially be added as an interceptor strategy
174                if (strategy instanceof Tracer) {
175                    continue;
176                }
177                target = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), outputDefinition, target, next);
178            }
179    
180            // sets the delegate to our wrapped output
181            output = target;
182        }
183    
184        private InterceptStrategy getOrCreateTracer() {
185            InterceptStrategy tracer = Tracer.getTracer(camelContext);
186            if (tracer == null) {
187                if (camelContext.getRegistry() != null) {
188                    // lookup in registry
189                    Map<String, Tracer> map = camelContext.getRegistry().lookupByType(Tracer.class);
190                    if (map.size() == 1) {
191                        tracer = map.values().iterator().next();
192                    }
193                }
194                if (tracer == null) {
195                    // fallback to use the default tracer
196                    tracer = camelContext.getDefaultTracer();
197    
198                    // configure and use any trace formatter if any exists
199                    Map<String, TraceFormatter> formatters = camelContext.getRegistry().lookupByType(TraceFormatter.class);
200                    if (formatters.size() == 1) {
201                        TraceFormatter formatter = formatters.values().iterator().next();
202                        if (tracer instanceof Tracer) {
203                            ((Tracer) tracer).setFormatter(formatter);
204                        }
205                    }
206                }
207            }
208    
209            // which we must manage as well
210            for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
211                if (tracer instanceof Service) {
212                    strategy.onServiceAdd(camelContext, (Service) tracer, null);
213                }
214            }
215    
216            return tracer;
217        }
218    
219        public void process(Exchange exchange) throws Exception {
220            // push the current route context
221            if (exchange.getUnitOfWork() != null) {
222                exchange.getUnitOfWork().pushRouteContext(routeContext);
223            }
224    
225            Processor processor = getOutput();
226            try {
227                if (processor != null && continueProcessing(exchange)) {
228                    processor.process(exchange);
229                }
230            } finally {
231                // pop the route context we just used
232                if (exchange.getUnitOfWork() != null) {
233                    exchange.getUnitOfWork().popRouteContext();
234                }
235            }
236        }
237    
238        /**
239         * Strategy to determine if we should continue processing the {@link Exchange}.
240         */
241        protected boolean continueProcessing(Exchange exchange) {
242            Object stop = exchange.getProperty(Exchange.ROUTE_STOP);
243            if (stop != null) {
244                boolean doStop = exchange.getContext().getTypeConverter().convertTo(Boolean.class, stop);
245                if (doStop) {
246                    if (LOG.isDebugEnabled()) {
247                        LOG.debug("Exchange is marked to stop routing: " + exchange);
248                    }
249                    return false;
250                }
251            }
252            return true;
253        }
254    
255        @Override
256        public String toString() {
257            // just output the next processor as all the interceptors and error handler is just too verbose
258            return "Channel[" + nextProcessor + "]";
259        }
260    
261    }