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.impl;
018    
019    import java.util.ArrayList;
020    import java.util.Arrays;
021    import java.util.Collections;
022    import java.util.EventObject;
023    import java.util.HashMap;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.concurrent.CopyOnWriteArrayList;
027    
028    import org.apache.camel.CamelContext;
029    import org.apache.camel.CamelContextAware;
030    import org.apache.camel.Exchange;
031    import org.apache.camel.LoggingLevel;
032    import org.apache.camel.Processor;
033    import org.apache.camel.RouteNode;
034    import org.apache.camel.management.event.AbstractExchangeEvent;
035    import org.apache.camel.management.event.ExchangeCompletedEvent;
036    import org.apache.camel.management.event.ExchangeCreatedEvent;
037    import org.apache.camel.model.ProcessorDefinition;
038    import org.apache.camel.processor.interceptor.Tracer;
039    import org.apache.camel.spi.Breakpoint;
040    import org.apache.camel.spi.Condition;
041    import org.apache.camel.spi.Debugger;
042    import org.apache.camel.spi.EventNotifier;
043    import org.apache.camel.support.EventNotifierSupport;
044    import org.apache.camel.util.ObjectHelper;
045    import org.apache.camel.util.ServiceHelper;
046    import org.slf4j.Logger;
047    import org.slf4j.LoggerFactory;
048    
049    /**
050     * The default implementation of the {@link Debugger}.
051     *
052     * @version 
053     */
054    public class DefaultDebugger implements Debugger, CamelContextAware {
055    
056        private static final Logger LOG = LoggerFactory.getLogger(DefaultDebugger.class);
057        private final EventNotifier debugEventNotifier = new DebugEventNotifier();
058        private final List<BreakpointConditions> breakpoints = new CopyOnWriteArrayList<BreakpointConditions>();
059        private final int maxConcurrentSingleSteps = 1;
060        private final Map<String, Breakpoint> singleSteps = new HashMap<String, Breakpoint>(maxConcurrentSingleSteps);
061        private CamelContext camelContext;
062        private boolean useTracer = true;
063    
064        /**
065         * Holder class for breakpoint and the associated conditions
066         */
067        private static final class BreakpointConditions {
068            private final Breakpoint breakpoint;
069            private final List<Condition> conditions;
070    
071            private BreakpointConditions(Breakpoint breakpoint) {
072                this(breakpoint, new ArrayList<Condition>());
073            }
074    
075            private BreakpointConditions(Breakpoint breakpoint, List<Condition> conditions) {
076                this.breakpoint = breakpoint;
077                this.conditions = conditions;
078            }
079    
080            public Breakpoint getBreakpoint() {
081                return breakpoint;
082            }
083    
084            public List<Condition> getConditions() {
085                return conditions;
086            }
087        }
088    
089        public DefaultDebugger() {
090        }
091    
092        public DefaultDebugger(CamelContext camelContext) {
093            this.camelContext = camelContext;
094        }
095    
096        @Override
097        public CamelContext getCamelContext() {
098            return camelContext;
099        }
100    
101        @Override
102        public void setCamelContext(CamelContext camelContext) {
103            this.camelContext = camelContext;
104        }
105    
106        public boolean isUseTracer() {
107            return useTracer;
108        }
109    
110        public void setUseTracer(boolean useTracer) {
111            this.useTracer = useTracer;
112        }
113    
114        @Override
115        public void addBreakpoint(Breakpoint breakpoint) {
116            breakpoints.add(new BreakpointConditions(breakpoint));
117        }
118    
119        @Override
120        public void addBreakpoint(Breakpoint breakpoint, Condition... conditions) {
121            breakpoints.add(new BreakpointConditions(breakpoint, Arrays.asList(conditions)));
122        }
123    
124        @Override
125        public void addSingleStepBreakpoint(final Breakpoint breakpoint) {
126            addSingleStepBreakpoint(breakpoint, new Condition[]{});
127        }
128    
129        @Override
130        public void addSingleStepBreakpoint(final Breakpoint breakpoint, Condition... conditions) {
131            // wrap the breakpoint into single step breakpoint so we can automatic enable/disable the single step mode
132            Breakpoint singlestep = new Breakpoint() {
133                @Override
134                public State getState() {
135                    return breakpoint.getState();
136                }
137    
138                @Override
139                public void suspend() {
140                    breakpoint.suspend();
141                }
142    
143                @Override
144                public void activate() {
145                    breakpoint.activate();
146                }
147    
148                @Override
149                public void beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) {
150                    breakpoint.beforeProcess(exchange, processor, definition);
151                }
152    
153                @Override
154                public void afterProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition, long timeTaken) {
155                    breakpoint.afterProcess(exchange, processor, definition, timeTaken);
156                }
157    
158                @Override
159                public void onEvent(Exchange exchange, EventObject event, ProcessorDefinition<?> definition) {
160                    if (event instanceof ExchangeCreatedEvent) {
161                        exchange.getContext().getDebugger().startSingleStepExchange(exchange.getExchangeId(), this);
162                    } else if (event instanceof ExchangeCompletedEvent) {
163                        exchange.getContext().getDebugger().stopSingleStepExchange(exchange.getExchangeId());
164                    }
165                    breakpoint.onEvent(exchange, event, definition);
166                }
167    
168                @Override
169                public String toString() {
170                    return breakpoint.toString();
171                }
172            };
173    
174            addBreakpoint(singlestep, conditions);
175        }
176    
177        @Override
178        public void removeBreakpoint(Breakpoint breakpoint) {
179            for (BreakpointConditions condition : breakpoints) {
180                if (condition.getBreakpoint().equals(breakpoint)) {
181                    breakpoints.remove(condition);
182                }
183            }
184        }
185    
186        @Override
187        public void suspendAllBreakpoints() {
188            for (BreakpointConditions breakpoint : breakpoints) {
189                breakpoint.getBreakpoint().suspend();
190            }
191        }
192    
193        @Override
194        public void activateAllBreakpoints() {
195            for (BreakpointConditions breakpoint : breakpoints) {
196                breakpoint.getBreakpoint().activate();
197            }
198        }
199    
200        @Override
201        public List<Breakpoint> getBreakpoints() {
202            List<Breakpoint> answer = new ArrayList<Breakpoint>(breakpoints.size());
203            for (BreakpointConditions e : breakpoints) {
204                answer.add(e.getBreakpoint());
205            }
206            return Collections.unmodifiableList(answer);
207        }
208    
209        @Override
210        public boolean startSingleStepExchange(String exchangeId, Breakpoint breakpoint) {
211            // can we accept single stepping the given exchange?
212            if (singleSteps.size() >= maxConcurrentSingleSteps) {
213                return false;
214            }
215    
216            singleSteps.put(exchangeId, breakpoint);
217            return true;
218        }
219    
220        @Override
221        public void stopSingleStepExchange(String exchangeId) {
222            singleSteps.remove(exchangeId);
223        }
224    
225        @Override
226        public boolean beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) {
227            // is the exchange in single step mode?
228            Breakpoint singleStep = singleSteps.get(exchange.getExchangeId());
229            if (singleStep != null) {
230                onBeforeProcess(exchange, processor, definition, singleStep);
231                return true;
232            }
233    
234            // does any of the breakpoints apply?
235            boolean match = false;
236            for (BreakpointConditions breakpoint : breakpoints) {
237                // breakpoint must be active
238                if (Breakpoint.State.Active.equals(breakpoint.getBreakpoint().getState())) {
239                    if (matchConditions(exchange, processor, definition, breakpoint)) {
240                        match = true;
241                        onBeforeProcess(exchange, processor, definition, breakpoint.getBreakpoint());
242                    }
243                }
244            }
245    
246            return match;
247        }
248    
249        @Override
250        public boolean afterProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition, long timeTaken) {
251            // is the exchange in single step mode?
252            Breakpoint singleStep = singleSteps.get(exchange.getExchangeId());
253            if (singleStep != null) {
254                onAfterProcess(exchange, processor, definition, timeTaken, singleStep);
255                return true;
256            }
257    
258            // does any of the breakpoints apply?
259            boolean match = false;
260            for (BreakpointConditions breakpoint : breakpoints) {
261                // breakpoint must be active
262                if (Breakpoint.State.Active.equals(breakpoint.getBreakpoint().getState())) {
263                    if (matchConditions(exchange, processor, definition, breakpoint)) {
264                        match = true;
265                        onAfterProcess(exchange, processor, definition, timeTaken, breakpoint.getBreakpoint());
266                    }
267                }
268            }
269    
270            return match;
271        }
272    
273        @Override
274        public boolean onEvent(Exchange exchange, EventObject event) {
275            // is the exchange in single step mode?
276            Breakpoint singleStep = singleSteps.get(exchange.getExchangeId());
277            if (singleStep != null) {
278                onEvent(exchange, event, singleStep);
279                return true;
280            }
281    
282            // does any of the breakpoints apply?
283            boolean match = false;
284            for (BreakpointConditions breakpoint : breakpoints) {
285                // breakpoint must be active
286                if (Breakpoint.State.Active.equals(breakpoint.getBreakpoint().getState())) {
287                    if (matchConditions(exchange, event, breakpoint)) {
288                        match = true;
289                        onEvent(exchange, event, breakpoint.getBreakpoint());
290                    }
291                }
292            }
293    
294            return match;
295        }
296    
297        protected void onBeforeProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition, Breakpoint breakpoint) {
298            try {
299                breakpoint.beforeProcess(exchange, processor, definition);
300            } catch (Throwable e) {
301                LOG.warn("Exception occurred in breakpoint: " + breakpoint + ". This exception will be ignored.", e);
302            }
303        }
304    
305        protected void onAfterProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition, long timeTaken, Breakpoint breakpoint) {
306            try {
307                breakpoint.afterProcess(exchange, processor, definition, timeTaken);
308            } catch (Throwable e) {
309                LOG.warn("Exception occurred in breakpoint: " + breakpoint + ". This exception will be ignored.", e);
310            }
311        }
312    
313        protected void onEvent(Exchange exchange, EventObject event, Breakpoint breakpoint) {
314            ProcessorDefinition<?> definition = null;
315    
316            // try to get the last known definition
317            if (exchange.getUnitOfWork() != null && exchange.getUnitOfWork().getTracedRouteNodes() != null) {
318                RouteNode node = exchange.getUnitOfWork().getTracedRouteNodes().getLastNode();
319                if (node != null) {
320                    definition = node.getProcessorDefinition();
321                }
322            }
323    
324            try {
325                breakpoint.onEvent(exchange, event, definition);
326            } catch (Throwable e) {
327                LOG.warn("Exception occurred in breakpoint: " + breakpoint + ". This exception will be ignored.", e);
328            }
329        }
330    
331        private boolean matchConditions(Exchange exchange, Processor processor, ProcessorDefinition<?> definition, BreakpointConditions breakpoint) {
332            for (Condition condition : breakpoint.getConditions()) {
333                if (!condition.matchProcess(exchange, processor, definition)) {
334                    return false;
335                }
336            }
337    
338            return true;
339        }
340    
341        private boolean matchConditions(Exchange exchange, EventObject event, BreakpointConditions breakpoint) {
342            for (Condition condition : breakpoint.getConditions()) {
343                if (!condition.matchEvent(exchange, event)) {
344                    return false;
345                }
346            }
347    
348            return true;
349        }
350    
351        @Override
352        public void start() throws Exception {
353            ObjectHelper.notNull(camelContext, "CamelContext", this);
354    
355            // register our event notifier
356            ServiceHelper.startService(debugEventNotifier);
357            camelContext.getManagementStrategy().addEventNotifier(debugEventNotifier);
358    
359            if (isUseTracer()) {
360                Tracer tracer = Tracer.getTracer(camelContext);
361                if (tracer == null) {
362                    // tracer is disabled so enable it silently so we can leverage it to trace the Exchanges for us
363                    tracer = Tracer.createTracer(camelContext);
364                    tracer.setLogLevel(LoggingLevel.OFF);
365                    camelContext.addService(tracer);
366                    camelContext.addInterceptStrategy(tracer);
367                }
368                // make sure tracer is enabled so the debugger can leverage the tracer for debugging purposes
369                tracer.setEnabled(true);
370            }
371        }
372    
373        @Override
374        public void stop() throws Exception {
375            breakpoints.clear();
376            singleSteps.clear();
377            ServiceHelper.stopServices(debugEventNotifier);
378        }
379    
380        @Override
381        public String toString() {
382            return "DefaultDebugger";
383        }
384    
385        private final class DebugEventNotifier extends EventNotifierSupport {
386    
387            private DebugEventNotifier() {
388                setIgnoreCamelContextEvents(true);
389                setIgnoreServiceEvents(true);
390            }
391    
392            @Override
393            public void notify(EventObject event) throws Exception {
394                AbstractExchangeEvent aee = (AbstractExchangeEvent) event;
395                Exchange exchange = aee.getExchange();
396                onEvent(exchange, event);
397    
398                if (event instanceof ExchangeCompletedEvent) {
399                    // fail safe to ensure we remove single steps when the Exchange is complete
400                    singleSteps.remove(exchange.getExchangeId());
401                }
402            }
403    
404            @Override
405            public boolean isEnabled(EventObject event) {
406                return event instanceof AbstractExchangeEvent;
407            }
408    
409            @Override
410            protected void doStart() throws Exception {
411                // noop
412            }
413    
414            @Override
415            protected void doStop() throws Exception {
416                // noop
417            }
418        }
419    
420    }