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 }