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.impl;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.EventObject;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.CopyOnWriteArrayList;
027
028import org.apache.camel.CamelContext;
029import org.apache.camel.CamelContextAware;
030import org.apache.camel.Exchange;
031import org.apache.camel.LoggingLevel;
032import org.apache.camel.Processor;
033import org.apache.camel.RouteNode;
034import org.apache.camel.management.event.AbstractExchangeEvent;
035import org.apache.camel.management.event.ExchangeCompletedEvent;
036import org.apache.camel.management.event.ExchangeCreatedEvent;
037import org.apache.camel.model.ProcessorDefinition;
038import org.apache.camel.processor.interceptor.Tracer;
039import org.apache.camel.spi.Breakpoint;
040import org.apache.camel.spi.Condition;
041import org.apache.camel.spi.Debugger;
042import org.apache.camel.spi.EventNotifier;
043import org.apache.camel.support.EventNotifierSupport;
044import org.apache.camel.util.ObjectHelper;
045import org.apache.camel.util.ServiceHelper;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049/**
050 * The default implementation of the {@link Debugger}.
051 *
052 * @version 
053 */
054public 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}