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.processor.interceptor; 018 019import java.util.Date; 020import java.util.EventObject; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Set; 024import java.util.concurrent.ConcurrentHashMap; 025import java.util.concurrent.ConcurrentMap; 026import java.util.concurrent.CountDownLatch; 027import java.util.concurrent.TimeUnit; 028import java.util.concurrent.atomic.AtomicBoolean; 029import java.util.concurrent.atomic.AtomicLong; 030 031import org.apache.camel.CamelContext; 032import org.apache.camel.Exchange; 033import org.apache.camel.LoggingLevel; 034import org.apache.camel.NoTypeConversionAvailableException; 035import org.apache.camel.Predicate; 036import org.apache.camel.Processor; 037import org.apache.camel.api.management.mbean.BacklogTracerEventMessage; 038import org.apache.camel.impl.BreakpointSupport; 039import org.apache.camel.impl.DefaultDebugger; 040import org.apache.camel.management.event.ExchangeCompletedEvent; 041import org.apache.camel.model.ProcessorDefinition; 042import org.apache.camel.model.ProcessorDefinitionHelper; 043import org.apache.camel.spi.Condition; 044import org.apache.camel.spi.Debugger; 045import org.apache.camel.spi.InterceptStrategy; 046import org.apache.camel.support.ServiceSupport; 047import org.apache.camel.util.CamelLogger; 048import org.apache.camel.util.MessageHelper; 049import org.apache.camel.util.ObjectHelper; 050import org.apache.camel.util.ServiceHelper; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * A {@link org.apache.camel.spi.Debugger} that has easy debugging functionality which 056 * can be used from JMX with {@link org.apache.camel.api.management.mbean.ManagedBacklogDebuggerMBean}. 057 * <p/> 058 * This implementation allows to set breakpoints (with or without a condition) and inspect the {@link Exchange} 059 * dumped in XML in {@link BacklogTracerEventMessage} format. There is operations to resume suspended breakpoints 060 * to continue routing the {@link Exchange}. There is also step functionality so you can single step a given 061 * {@link Exchange}. 062 * <p/> 063 * This implementation will only break the first {@link Exchange} that arrives to a breakpoint. If Camel routes using 064 * concurrency then sub-sequent {@link Exchange} will continue to be routed, if there breakpoint already holds a 065 * suspended {@link Exchange}. 066 */ 067public class BacklogDebugger extends ServiceSupport implements InterceptStrategy { 068 069 private static final Logger LOG = LoggerFactory.getLogger(BacklogDebugger.class); 070 071 private long fallbackTimeout = 300; 072 private final CamelContext camelContext; 073 private LoggingLevel loggingLevel = LoggingLevel.INFO; 074 private final CamelLogger logger = new CamelLogger(LOG, loggingLevel); 075 private final AtomicBoolean enabled = new AtomicBoolean(); 076 private final AtomicLong debugCounter = new AtomicLong(0); 077 private final Debugger debugger; 078 private final ConcurrentMap<String, NodeBreakpoint> breakpoints = new ConcurrentHashMap<String, NodeBreakpoint>(); 079 private final ConcurrentMap<String, SuspendedExchange> suspendedBreakpoints = new ConcurrentHashMap<String, SuspendedExchange>(); 080 private final ConcurrentMap<String, BacklogTracerEventMessage> suspendedBreakpointMessages = new ConcurrentHashMap<String, BacklogTracerEventMessage>(); 081 private volatile String singleStepExchangeId; 082 private int bodyMaxChars = 128 * 1024; 083 private boolean bodyIncludeStreams; 084 private boolean bodyIncludeFiles = true; 085 086 /** 087 * A suspend {@link Exchange} at a breakpoint. 088 */ 089 private static final class SuspendedExchange { 090 private final Exchange exchange; 091 private final CountDownLatch latch; 092 093 /** 094 * @param exchange the suspend exchange 095 * @param latch the latch to use to continue routing the exchange 096 */ 097 private SuspendedExchange(Exchange exchange, CountDownLatch latch) { 098 this.exchange = exchange; 099 this.latch = latch; 100 } 101 102 public Exchange getExchange() { 103 return exchange; 104 } 105 106 public CountDownLatch getLatch() { 107 return latch; 108 } 109 } 110 111 public BacklogDebugger(CamelContext camelContext) { 112 this.camelContext = camelContext; 113 DefaultDebugger debugger = new DefaultDebugger(camelContext); 114 debugger.setUseTracer(false); 115 this.debugger = debugger; 116 } 117 118 @Override 119 @Deprecated 120 public Processor wrapProcessorInInterceptors(CamelContext context, ProcessorDefinition<?> definition, Processor target, Processor nextTarget) throws Exception { 121 throw new UnsupportedOperationException("Deprecated"); 122 } 123 124 /** 125 * A helper method to return the BacklogDebugger instance if one is enabled 126 * 127 * @return the backlog debugger or null if none can be found 128 */ 129 public static BacklogDebugger getBacklogDebugger(CamelContext context) { 130 List<InterceptStrategy> list = context.getInterceptStrategies(); 131 for (InterceptStrategy interceptStrategy : list) { 132 if (interceptStrategy instanceof BacklogDebugger) { 133 return (BacklogDebugger) interceptStrategy; 134 } 135 } 136 return null; 137 } 138 139 public Debugger getDebugger() { 140 return debugger; 141 } 142 143 public String getLoggingLevel() { 144 return loggingLevel.name(); 145 } 146 147 public void setLoggingLevel(String level) { 148 loggingLevel = LoggingLevel.valueOf(level); 149 logger.setLevel(loggingLevel); 150 } 151 152 public void enableDebugger() { 153 logger.log("Enabling debugger"); 154 try { 155 ServiceHelper.startService(debugger); 156 enabled.set(true); 157 } catch (Exception e) { 158 throw ObjectHelper.wrapRuntimeCamelException(e); 159 } 160 } 161 162 public void disableDebugger() { 163 logger.log("Disabling debugger"); 164 try { 165 enabled.set(false); 166 ServiceHelper.stopService(debugger); 167 } catch (Exception e) { 168 // ignore 169 } 170 clearBreakpoints(); 171 } 172 173 public boolean isEnabled() { 174 return enabled.get(); 175 } 176 177 public boolean hasBreakpoint(String nodeId) { 178 return breakpoints.containsKey(nodeId); 179 } 180 181 public boolean isSingleStepMode() { 182 return singleStepExchangeId != null; 183 } 184 185 public void addBreakpoint(String nodeId) { 186 NodeBreakpoint breakpoint = breakpoints.get(nodeId); 187 if (breakpoint == null) { 188 logger.log("Adding breakpoint " + nodeId); 189 breakpoint = new NodeBreakpoint(nodeId, null); 190 breakpoints.put(nodeId, breakpoint); 191 debugger.addBreakpoint(breakpoint, breakpoint); 192 } else { 193 breakpoint.setCondition(null); 194 } 195 } 196 197 public void addConditionalBreakpoint(String nodeId, String language, String predicate) { 198 Predicate condition = camelContext.resolveLanguage(language).createPredicate(predicate); 199 NodeBreakpoint breakpoint = breakpoints.get(nodeId); 200 if (breakpoint == null) { 201 logger.log("Adding conditional breakpoint " + nodeId + " [" + predicate + "]"); 202 breakpoint = new NodeBreakpoint(nodeId, condition); 203 breakpoints.put(nodeId, breakpoint); 204 debugger.addBreakpoint(breakpoint, breakpoint); 205 } else if (breakpoint.getCondition() == null) { 206 logger.log("Updating to conditional breakpoint " + nodeId + " [" + predicate + "]"); 207 debugger.removeBreakpoint(breakpoint); 208 breakpoints.put(nodeId, breakpoint); 209 debugger.addBreakpoint(breakpoint, breakpoint); 210 } else if (breakpoint.getCondition() != null) { 211 logger.log("Updating conditional breakpoint " + nodeId + " [" + predicate + "]"); 212 breakpoint.setCondition(condition); 213 } 214 } 215 216 public void removeBreakpoint(String nodeId) { 217 logger.log("Removing breakpoint " + nodeId); 218 // when removing a break point then ensure latches is cleared and counted down so we wont have hanging threads 219 suspendedBreakpointMessages.remove(nodeId); 220 SuspendedExchange se = suspendedBreakpoints.remove(nodeId); 221 NodeBreakpoint breakpoint = breakpoints.remove(nodeId); 222 if (breakpoint != null) { 223 debugger.removeBreakpoint(breakpoint); 224 } 225 if (se != null) { 226 se.getLatch().countDown(); 227 } 228 } 229 230 public void removeAllBreakpoints() { 231 // stop single stepping 232 singleStepExchangeId = null; 233 234 for (String nodeId : getSuspendedBreakpointNodeIds()) { 235 removeBreakpoint(nodeId); 236 } 237 } 238 239 public Set<String> getBreakpoints() { 240 return new LinkedHashSet<String>(breakpoints.keySet()); 241 } 242 243 public void resumeBreakpoint(String nodeId) { 244 resumeBreakpoint(nodeId, false); 245 } 246 247 private void resumeBreakpoint(String nodeId, boolean stepMode) { 248 logger.log("Resume breakpoint " + nodeId); 249 250 if (!stepMode) { 251 if (singleStepExchangeId != null) { 252 debugger.stopSingleStepExchange(singleStepExchangeId); 253 singleStepExchangeId = null; 254 } 255 } 256 257 // remember to remove the dumped message as its no longer in need 258 suspendedBreakpointMessages.remove(nodeId); 259 SuspendedExchange se = suspendedBreakpoints.remove(nodeId); 260 if (se != null) { 261 se.getLatch().countDown(); 262 } 263 } 264 265 public void setMessageBodyOnBreakpoint(String nodeId, Object body) { 266 SuspendedExchange se = suspendedBreakpoints.get(nodeId); 267 if (se != null) { 268 boolean remove = body == null; 269 if (remove) { 270 removeMessageBodyOnBreakpoint(nodeId); 271 } else { 272 Class<?> oldType; 273 if (se.getExchange().hasOut()) { 274 oldType = se.getExchange().getOut().getBody() != null ? se.getExchange().getOut().getBody().getClass() : null; 275 } else { 276 oldType = se.getExchange().getIn().getBody() != null ? se.getExchange().getIn().getBody().getClass() : null; 277 } 278 setMessageBodyOnBreakpoint(nodeId, body, oldType); 279 } 280 } 281 } 282 283 public void setMessageBodyOnBreakpoint(String nodeId, Object body, Class<?> type) { 284 SuspendedExchange se = suspendedBreakpoints.get(nodeId); 285 if (se != null) { 286 boolean remove = body == null; 287 if (remove) { 288 removeMessageBodyOnBreakpoint(nodeId); 289 } else { 290 logger.log("Breakpoint at node " + nodeId + " is updating message body on exchangeId: " + se.getExchange().getExchangeId() + " with new body: " + body); 291 if (se.getExchange().hasOut()) { 292 // preserve type 293 if (type != null) { 294 se.getExchange().getOut().setBody(body, type); 295 } else { 296 se.getExchange().getOut().setBody(body); 297 } 298 } else { 299 if (type != null) { 300 se.getExchange().getIn().setBody(body, type); 301 } else { 302 se.getExchange().getIn().setBody(body); 303 } 304 } 305 } 306 } 307 } 308 309 public void removeMessageBodyOnBreakpoint(String nodeId) { 310 SuspendedExchange se = suspendedBreakpoints.get(nodeId); 311 if (se != null) { 312 logger.log("Breakpoint at node " + nodeId + " is removing message body on exchangeId: " + se.getExchange().getExchangeId()); 313 if (se.getExchange().hasOut()) { 314 se.getExchange().getOut().setBody(null); 315 } else { 316 se.getExchange().getIn().setBody(null); 317 } 318 } 319 } 320 321 public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value) throws NoTypeConversionAvailableException { 322 SuspendedExchange se = suspendedBreakpoints.get(nodeId); 323 if (se != null) { 324 Class<?> oldType; 325 if (se.getExchange().hasOut()) { 326 oldType = se.getExchange().getOut().getHeader(headerName) != null ? se.getExchange().getOut().getHeader(headerName).getClass() : null; 327 } else { 328 oldType = se.getExchange().getIn().getHeader(headerName) != null ? se.getExchange().getIn().getHeader(headerName).getClass() : null; 329 } 330 setMessageHeaderOnBreakpoint(nodeId, headerName, value, oldType); 331 } 332 } 333 334 public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value, Class<?> type) throws NoTypeConversionAvailableException { 335 SuspendedExchange se = suspendedBreakpoints.get(nodeId); 336 if (se != null) { 337 logger.log("Breakpoint at node " + nodeId + " is updating message header on exchangeId: " + se.getExchange().getExchangeId() + " with header: " + headerName + " and value: " + value); 338 if (se.getExchange().hasOut()) { 339 if (type != null) { 340 Object convertedValue = se.getExchange().getContext().getTypeConverter().mandatoryConvertTo(type, se.getExchange(), value); 341 se.getExchange().getOut().setHeader(headerName, convertedValue); 342 } else { 343 se.getExchange().getOut().setHeader(headerName, value); 344 } 345 } else { 346 if (type != null) { 347 Object convertedValue = se.getExchange().getContext().getTypeConverter().mandatoryConvertTo(type, se.getExchange(), value); 348 se.getExchange().getIn().setHeader(headerName, convertedValue); 349 } else { 350 se.getExchange().getIn().setHeader(headerName, value); 351 } 352 } 353 } 354 } 355 356 public long getFallbackTimeout() { 357 return fallbackTimeout; 358 } 359 360 public void setFallbackTimeout(long fallbackTimeout) { 361 this.fallbackTimeout = fallbackTimeout; 362 } 363 364 public void removeMessageHeaderOnBreakpoint(String nodeId, String headerName) { 365 SuspendedExchange se = suspendedBreakpoints.get(nodeId); 366 if (se != null) { 367 logger.log("Breakpoint at node " + nodeId + " is removing message header on exchangeId: " + se.getExchange().getExchangeId() + " with header: " + headerName); 368 if (se.getExchange().hasOut()) { 369 se.getExchange().getOut().removeHeader(headerName); 370 } else { 371 se.getExchange().getIn().removeHeader(headerName); 372 } 373 } 374 } 375 376 public void resumeAll() { 377 logger.log("Resume all"); 378 // stop single stepping 379 singleStepExchangeId = null; 380 381 for (String node : getSuspendedBreakpointNodeIds()) { 382 // remember to remove the dumped message as its no longer in need 383 suspendedBreakpointMessages.remove(node); 384 SuspendedExchange se = suspendedBreakpoints.remove(node); 385 if (se != null) { 386 se.getLatch().countDown(); 387 } 388 } 389 } 390 391 public void stepBreakpoint(String nodeId) { 392 // if we are already in single step mode, then infer stepping 393 if (isSingleStepMode()) { 394 logger.log("stepBreakpoint " + nodeId + " is already in single step mode, so stepping instead."); 395 step(); 396 } 397 398 logger.log("Step breakpoint " + nodeId); 399 // we want to step current exchange to next 400 BacklogTracerEventMessage msg = suspendedBreakpointMessages.get(nodeId); 401 NodeBreakpoint breakpoint = breakpoints.get(nodeId); 402 if (msg != null && breakpoint != null) { 403 singleStepExchangeId = msg.getExchangeId(); 404 if (debugger.startSingleStepExchange(singleStepExchangeId, new StepBreakpoint())) { 405 // now resume 406 resumeBreakpoint(nodeId, true); 407 } 408 } 409 } 410 411 public void step() { 412 for (String node : getSuspendedBreakpointNodeIds()) { 413 // remember to remove the dumped message as its no longer in need 414 suspendedBreakpointMessages.remove(node); 415 SuspendedExchange se = suspendedBreakpoints.remove(node); 416 if (se != null) { 417 se.getLatch().countDown(); 418 } 419 } 420 } 421 422 public Set<String> getSuspendedBreakpointNodeIds() { 423 return new LinkedHashSet<String>(suspendedBreakpoints.keySet()); 424 } 425 426 /** 427 * Gets the exchanged suspended at the given breakpoint id or null if there is none at that id. 428 * 429 * @param id - node id for the breakpoint 430 * @return The suspended exchange or null if there isn't one suspended at the given breakpoint. 431 */ 432 public Exchange getSuspendedExchange(String id) { 433 SuspendedExchange suspendedExchange = suspendedBreakpoints.get(id); 434 return suspendedExchange != null ? suspendedExchange.getExchange() : null; 435 } 436 437 public void disableBreakpoint(String nodeId) { 438 logger.log("Disable breakpoint " + nodeId); 439 NodeBreakpoint breakpoint = breakpoints.get(nodeId); 440 if (breakpoint != null) { 441 breakpoint.suspend(); 442 } 443 } 444 445 public void enableBreakpoint(String nodeId) { 446 logger.log("Enable breakpoint " + nodeId); 447 NodeBreakpoint breakpoint = breakpoints.get(nodeId); 448 if (breakpoint != null) { 449 breakpoint.activate(); 450 } 451 } 452 453 public int getBodyMaxChars() { 454 return bodyMaxChars; 455 } 456 457 public void setBodyMaxChars(int bodyMaxChars) { 458 this.bodyMaxChars = bodyMaxChars; 459 } 460 461 public boolean isBodyIncludeStreams() { 462 return bodyIncludeStreams; 463 } 464 465 public void setBodyIncludeStreams(boolean bodyIncludeStreams) { 466 this.bodyIncludeStreams = bodyIncludeStreams; 467 } 468 469 public boolean isBodyIncludeFiles() { 470 return bodyIncludeFiles; 471 } 472 473 public void setBodyIncludeFiles(boolean bodyIncludeFiles) { 474 this.bodyIncludeFiles = bodyIncludeFiles; 475 } 476 477 public String dumpTracedMessagesAsXml(String nodeId) { 478 logger.log("Dump trace message from breakpoint " + nodeId); 479 BacklogTracerEventMessage msg = suspendedBreakpointMessages.get(nodeId); 480 if (msg != null) { 481 return msg.toXml(0); 482 } else { 483 return null; 484 } 485 } 486 487 public long getDebugCounter() { 488 return debugCounter.get(); 489 } 490 491 public void resetDebugCounter() { 492 logger.log("Reset debug counter"); 493 debugCounter.set(0); 494 } 495 496 public boolean beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) { 497 return debugger.beforeProcess(exchange, processor, definition); 498 } 499 500 public boolean afterProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition, long timeTaken) { 501 // noop 502 return false; 503 } 504 505 protected void doStart() throws Exception { 506 // noop 507 } 508 509 protected void doStop() throws Exception { 510 if (enabled.get()) { 511 disableDebugger(); 512 } 513 clearBreakpoints(); 514 } 515 516 private void clearBreakpoints() { 517 // make sure to clear state and latches is counted down so we wont have hanging threads 518 breakpoints.clear(); 519 for (SuspendedExchange se : suspendedBreakpoints.values()) { 520 se.getLatch().countDown(); 521 } 522 suspendedBreakpoints.clear(); 523 suspendedBreakpointMessages.clear(); 524 } 525 526 /** 527 * Represents a {@link org.apache.camel.spi.Breakpoint} that has a {@link Condition} on a specific node id. 528 */ 529 private final class NodeBreakpoint extends BreakpointSupport implements Condition { 530 531 private final String nodeId; 532 private Predicate condition; 533 534 private NodeBreakpoint(String nodeId, Predicate condition) { 535 this.nodeId = nodeId; 536 this.condition = condition; 537 } 538 539 public Predicate getCondition() { 540 return condition; 541 } 542 543 public void setCondition(Predicate predicate) { 544 this.condition = predicate; 545 } 546 547 @Override 548 public void beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) { 549 // store a copy of the message so we can see that from the debugger 550 Date timestamp = new Date(); 551 String toNode = definition.getId(); 552 String routeId = ProcessorDefinitionHelper.getRouteId(definition); 553 String exchangeId = exchange.getExchangeId(); 554 String messageAsXml = MessageHelper.dumpAsXml(exchange.getIn(), true, 2, isBodyIncludeStreams(), isBodyIncludeFiles(), getBodyMaxChars()); 555 long uid = debugCounter.incrementAndGet(); 556 557 BacklogTracerEventMessage msg = new DefaultBacklogTracerEventMessage(uid, timestamp, routeId, toNode, exchangeId, messageAsXml); 558 suspendedBreakpointMessages.put(nodeId, msg); 559 560 // suspend at this breakpoint 561 final SuspendedExchange se = suspendedBreakpoints.get(nodeId); 562 if (se != null) { 563 // now wait until we should continue 564 logger.log("NodeBreakpoint at node " + toNode + " is waiting to continue for exchangeId: " + exchangeId); 565 try { 566 boolean hit = se.getLatch().await(fallbackTimeout, TimeUnit.SECONDS); 567 if (!hit) { 568 logger.log("NodeBreakpoint at node " + toNode + " timed out and is continued exchangeId: " + exchangeId, LoggingLevel.WARN); 569 } else { 570 logger.log("NodeBreakpoint at node " + toNode + " is continued exchangeId: " + exchangeId); 571 } 572 } catch (InterruptedException e) { 573 // ignore 574 } 575 } 576 } 577 578 @Override 579 public boolean matchProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) { 580 // must match node 581 if (!nodeId.equals(definition.getId())) { 582 return false; 583 } 584 585 // if condition then must match 586 if (condition != null && !condition.matches(exchange)) { 587 return false; 588 } 589 590 // we only want to break one exchange at a time, so if there is already a suspended breakpoint then do not match 591 SuspendedExchange se = new SuspendedExchange(exchange, new CountDownLatch(1)); 592 boolean existing = suspendedBreakpoints.putIfAbsent(nodeId, se) != null; 593 return !existing; 594 } 595 596 @Override 597 public boolean matchEvent(Exchange exchange, EventObject event) { 598 return false; 599 } 600 } 601 602 /** 603 * Represents a {@link org.apache.camel.spi.Breakpoint} that is used during single step mode. 604 */ 605 private final class StepBreakpoint extends BreakpointSupport implements Condition { 606 607 @Override 608 public void beforeProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) { 609 // store a copy of the message so we can see that from the debugger 610 Date timestamp = new Date(); 611 String toNode = definition.getId(); 612 String routeId = ProcessorDefinitionHelper.getRouteId(definition); 613 String exchangeId = exchange.getExchangeId(); 614 String messageAsXml = MessageHelper.dumpAsXml(exchange.getIn(), true, 2, isBodyIncludeStreams(), isBodyIncludeFiles(), getBodyMaxChars()); 615 long uid = debugCounter.incrementAndGet(); 616 617 BacklogTracerEventMessage msg = new DefaultBacklogTracerEventMessage(uid, timestamp, routeId, toNode, exchangeId, messageAsXml); 618 suspendedBreakpointMessages.put(toNode, msg); 619 620 // suspend at this breakpoint 621 SuspendedExchange se = new SuspendedExchange(exchange, new CountDownLatch(1)); 622 suspendedBreakpoints.put(toNode, se); 623 624 // now wait until we should continue 625 logger.log("StepBreakpoint at node " + toNode + " is waiting to continue for exchangeId: " + exchange.getExchangeId()); 626 try { 627 boolean hit = se.getLatch().await(fallbackTimeout, TimeUnit.SECONDS); 628 if (!hit) { 629 logger.log("StepBreakpoint at node " + toNode + " timed out and is continued exchangeId: " + exchange.getExchangeId(), LoggingLevel.WARN); 630 } else { 631 logger.log("StepBreakpoint at node " + toNode + " is continued exchangeId: " + exchange.getExchangeId()); 632 } 633 } catch (InterruptedException e) { 634 // ignore 635 } 636 } 637 638 @Override 639 public boolean matchProcess(Exchange exchange, Processor processor, ProcessorDefinition<?> definition) { 640 return true; 641 } 642 643 @Override 644 public boolean matchEvent(Exchange exchange, EventObject event) { 645 return event instanceof ExchangeCompletedEvent; 646 } 647 648 @Override 649 public void onEvent(Exchange exchange, EventObject event, ProcessorDefinition<?> definition) { 650 // when the exchange is complete, we need to turn off single step mode if we were debug stepping the exchange 651 if (event instanceof ExchangeCompletedEvent) { 652 String completedId = ((ExchangeCompletedEvent) event).getExchange().getExchangeId(); 653 654 if (singleStepExchangeId != null && singleStepExchangeId.equals(completedId)) { 655 logger.log("ExchangeId: " + completedId + " is completed, so exiting single step mode."); 656 singleStepExchangeId = null; 657 } 658 } 659 } 660 } 661 662}