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