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