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<BreakpointConditions>(); 061 private final int maxConcurrentSingleSteps = 1; 062 private final Map<String, Breakpoint> singleSteps = new HashMap<String, Breakpoint>(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<Breakpoint>(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}