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.builder; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022import java.util.concurrent.ConcurrentHashMap; 023import java.util.concurrent.ConcurrentMap; 024import java.util.concurrent.CountDownLatch; 025import java.util.concurrent.TimeUnit; 026import java.util.concurrent.atomic.AtomicBoolean; 027import java.util.concurrent.atomic.AtomicInteger; 028 029import org.apache.camel.CamelContext; 030import org.apache.camel.Endpoint; 031import org.apache.camel.Exchange; 032import org.apache.camel.Expression; 033import org.apache.camel.Predicate; 034import org.apache.camel.RuntimeCamelException; 035import org.apache.camel.spi.CamelEvent; 036import org.apache.camel.spi.CamelEvent.ExchangeCompletedEvent; 037import org.apache.camel.spi.CamelEvent.ExchangeCreatedEvent; 038import org.apache.camel.spi.CamelEvent.ExchangeFailedEvent; 039import org.apache.camel.spi.CamelEvent.ExchangeSentEvent; 040import org.apache.camel.spi.NotifyBuilderMatcher; 041import org.apache.camel.support.EndpointHelper; 042import org.apache.camel.support.EventNotifierSupport; 043import org.apache.camel.support.ExchangeHelper; 044import org.apache.camel.support.PatternHelper; 045import org.apache.camel.support.service.ServiceHelper; 046import org.apache.camel.util.ObjectHelper; 047import org.apache.camel.util.StringHelper; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051/** 052 * A builder to build an expression based on 053 * {@link org.apache.camel.spi.EventNotifier} notifications about 054 * {@link Exchange} being routed. 055 * <p/> 056 * This builder can be used for testing purposes where you want to know when a 057 * test is supposed to be done. The idea is that you can build an expression 058 * that explains when the test is done. For example when Camel have finished 059 * routing 5 messages. You can then in your test await for this condition to 060 * occur. 061 */ 062public class NotifyBuilder { 063 064 private static final Logger LOG = LoggerFactory.getLogger(NotifyBuilder.class); 065 066 private final CamelContext context; 067 068 // notifier to hook into Camel to listen for events 069 private final EventNotifierSupport eventNotifier; 070 071 // the predicates build with this builder 072 private final List<EventPredicateHolder> predicates = new ArrayList<>(); 073 074 // latch to be used to signal predicates matches 075 private CountDownLatch latch = new CountDownLatch(1); 076 077 // the current state while building an event predicate where we use a stack 078 // and the operation 079 private final List<EventPredicate> stack = new ArrayList<>(); 080 private EventOperation operation; 081 private boolean created; 082 // keep state of how many wereSentTo we have added 083 private int wereSentToIndex; 084 // default wait time 085 private long waitTime = 10000L; 086 087 // computed value whether all the predicates matched 088 private volatile boolean matches; 089 090 /** 091 * Creates a new builder. 092 * 093 * @param context the Camel context 094 */ 095 public NotifyBuilder(CamelContext context) { 096 this.context = context; 097 eventNotifier = new ExchangeNotifier(); 098 try { 099 context.addService(eventNotifier, false); 100 eventNotifier.start(); 101 } catch (Exception e) { 102 throw RuntimeCamelException.wrapRuntimeCamelException(e); 103 } 104 context.getManagementStrategy().addEventNotifier(eventNotifier); 105 } 106 107 /** 108 * Optionally a <tt>from</tt> endpoint which means that this expression 109 * should only be based on {@link Exchange} which is originated from the 110 * particular endpoint(s). 111 * 112 * @param endpointUri uri of endpoint or pattern (see the EndpointHelper 113 * javadoc) 114 * @return the builder 115 * @see EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String, 116 * String) 117 */ 118 public NotifyBuilder from(final String endpointUri) { 119 stack.add(new EventPredicateSupport() { 120 121 @Override 122 public boolean isAbstract() { 123 // is abstract as its a filter 124 return true; 125 } 126 127 @Override 128 public boolean onExchange(Exchange exchange) { 129 // filter non matching exchanges 130 if (exchange.getFromEndpoint() != null) { 131 return EndpointHelper.matchEndpoint(context, exchange.getFromEndpoint().getEndpointUri(), endpointUri); 132 } else { 133 return false; 134 } 135 } 136 137 public boolean matches() { 138 // should be true as we use the onExchange to filter 139 return true; 140 } 141 142 @Override 143 public String toString() { 144 return "from(" + endpointUri + ")"; 145 } 146 }); 147 return this; 148 } 149 150 /** 151 * Optionally a <tt>from</tt> route which means that this expression should 152 * only be based on {@link Exchange} which is originated from the particular 153 * route(s). 154 * 155 * @param routeId id of route or pattern (see the EndpointHelper javadoc) 156 * @return the builder 157 * @see EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String, 158 * String) 159 */ 160 public NotifyBuilder fromRoute(final String routeId) { 161 stack.add(new EventPredicateSupport() { 162 163 @Override 164 public boolean isAbstract() { 165 // is abstract as its a filter 166 return true; 167 } 168 169 @Override 170 public boolean onExchange(Exchange exchange) { 171 String id = EndpointHelper.getRouteIdFromEndpoint(exchange.getFromEndpoint()); 172 173 if (id == null) { 174 id = exchange.getFromRouteId(); 175 } 176 177 // filter non matching exchanges 178 return PatternHelper.matchPattern(id, routeId); 179 } 180 181 public boolean matches() { 182 // should be true as we use the onExchange to filter 183 return true; 184 } 185 186 @Override 187 public String toString() { 188 return "fromRoute(" + routeId + ")"; 189 } 190 }); 191 return this; 192 } 193 194 /** 195 * Optionally a <tt>from</tt> current route which means that this expression 196 * should only be based on {@link Exchange} which is the current route(s). 197 * 198 * @param routeId id of route or pattern (see the EndpointHelper javadoc) 199 * @return the builder 200 * @see EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String, 201 * String) 202 */ 203 public NotifyBuilder fromCurrentRoute(final String routeId) { 204 stack.add(new EventPredicateSupport() { 205 206 @Override 207 public boolean isAbstract() { 208 // is abstract as its a filter 209 return true; 210 } 211 212 @Override 213 public boolean onExchangeSent(Exchange exchange, Endpoint endpoint, long timeTaken) { 214 String id = ExchangeHelper.getAtRouteId(exchange); 215 if (id != null) { 216 return PatternHelper.matchPattern(id, routeId); 217 } else { 218 return false; 219 } 220 } 221 222 public boolean matches() { 223 // should be true as we use the onExchange to filter 224 return true; 225 } 226 227 @Override 228 public String toString() { 229 return "fromCurrentRoute(" + routeId + ")"; 230 } 231 }); 232 return this; 233 } 234 235 private NotifyBuilder fromRoutesOnly() { 236 // internal and should always be in top of stack 237 stack.add(0, new EventPredicateSupport() { 238 239 @Override 240 public boolean isAbstract() { 241 // is abstract as its a filter 242 return true; 243 } 244 245 @Override 246 public boolean onExchange(Exchange exchange) { 247 // always accept direct endpoints as they are a special case as 248 // it will create the UoW beforehand 249 // and just continue to route that on the consumer side, which 250 // causes the EventNotifier not to 251 // emit events when the consumer received the exchange, as its 252 // already done. For example by 253 // ProducerTemplate which creates the UoW before producing 254 // messages. 255 if (exchange.getFromEndpoint() != null && exchange.getFromEndpoint().getEndpointUri().startsWith("direct:")) { 256 return true; 257 } 258 return PatternHelper.matchPattern(exchange.getFromRouteId(), "*"); 259 } 260 261 public boolean matches() { 262 // should be true as we use the onExchange to filter 263 return true; 264 } 265 266 @Override 267 public String toString() { 268 // we dont want any to string output as this is an internal 269 // predicate to match only from routes 270 return ""; 271 } 272 }); 273 return this; 274 } 275 276 /** 277 * Optionally a filter to only allow matching {@link Exchange} to be used 278 * for matching. 279 * 280 * @param predicate the predicate to use for the filter 281 * @return the builder 282 */ 283 public NotifyBuilder filter(final Predicate predicate) { 284 stack.add(new EventPredicateSupport() { 285 286 @Override 287 public boolean isAbstract() { 288 // is abstract as its a filter 289 return true; 290 } 291 292 @Override 293 public boolean onExchange(Exchange exchange) { 294 // filter non matching exchanges 295 return predicate.matches(exchange); 296 } 297 298 public boolean matches() { 299 // should be true as we use the onExchange to filter 300 return true; 301 } 302 303 @Override 304 public String toString() { 305 return "filter(" + predicate + ")"; 306 } 307 }); 308 return this; 309 } 310 311 /** 312 * Optionally a filter to only allow matching {@link Exchange} to be used 313 * for matching. 314 * 315 * @return the builder 316 */ 317 public ExpressionClauseSupport<NotifyBuilder> filter() { 318 final ExpressionClauseSupport<NotifyBuilder> clause = new ExpressionClauseSupport<>(this); 319 stack.add(new EventPredicateSupport() { 320 321 @Override 322 public boolean isAbstract() { 323 // is abstract as its a filter 324 return true; 325 } 326 327 @Override 328 public boolean onExchange(Exchange exchange) { 329 // filter non matching exchanges 330 Expression exp = clause.createExpression(exchange.getContext()); 331 return exp.evaluate(exchange, Boolean.class); 332 } 333 334 public boolean matches() { 335 // should be true as we use the onExchange to filter 336 return true; 337 } 338 339 @Override 340 public String toString() { 341 return "filter(" + clause + ")"; 342 } 343 }); 344 return clause; 345 } 346 347 /** 348 * Optionally a <tt>sent to</tt> endpoint which means that this expression 349 * should only be based on {@link Exchange} which has been sent to the given 350 * endpoint uri. 351 * <p/> 352 * Notice the {@link Exchange} may have been sent to other endpoints as 353 * well. This condition will match if the {@link Exchange} has been sent at 354 * least once to the given endpoint. 355 * 356 * @param endpointUri uri of endpoint or pattern (see the EndpointHelper 357 * javadoc) 358 * @return the builder 359 * @see EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String, 360 * String) 361 */ 362 public NotifyBuilder wereSentTo(final String endpointUri) { 363 // insert in start of stack but after the previous wereSentTo 364 stack.add(wereSentToIndex++, new EventPredicateSupport() { 365 private ConcurrentMap<String, String> sentTo = new ConcurrentHashMap<>(); 366 367 @Override 368 public boolean isAbstract() { 369 // is abstract as its a filter 370 return true; 371 } 372 373 @Override 374 public boolean onExchangeSent(Exchange exchange, Endpoint endpoint, long timeTaken) { 375 if (EndpointHelper.matchEndpoint(context, endpoint.getEndpointUri(), endpointUri)) { 376 sentTo.put(exchange.getExchangeId(), exchange.getExchangeId()); 377 } 378 return onExchange(exchange); 379 } 380 381 @Override 382 public boolean onExchange(Exchange exchange) { 383 // filter only when sentTo 384 String sent = sentTo.get(exchange.getExchangeId()); 385 return sent != null; 386 } 387 388 public boolean matches() { 389 // should be true as we use the onExchange to filter 390 return true; 391 } 392 393 @Override 394 public void reset() { 395 sentTo.clear(); 396 } 397 398 @Override 399 public String toString() { 400 return "wereSentTo(" + endpointUri + ")"; 401 } 402 }); 403 return this; 404 } 405 406 /** 407 * Sets a condition when <tt>number</tt> of {@link Exchange} has been 408 * received. 409 * <p/> 410 * The number matching is <i>at least</i> based which means that if more 411 * messages received it will match also. 412 * 413 * @param number at least number of messages 414 * @return the builder 415 */ 416 public NotifyBuilder whenReceived(final int number) { 417 stack.add(new EventPredicateSupport() { 418 private AtomicInteger current = new AtomicInteger(); 419 420 @Override 421 public boolean onExchangeCreated(Exchange exchange) { 422 current.incrementAndGet(); 423 return true; 424 } 425 426 public boolean matches() { 427 return current.get() >= number; 428 } 429 430 @Override 431 public void reset() { 432 current.set(0); 433 } 434 435 @Override 436 public String toString() { 437 return "whenReceived(" + number + ")"; 438 } 439 }); 440 return this; 441 } 442 443 /** 444 * Sets a condition when <tt>number</tt> of {@link Exchange} is done being 445 * processed. 446 * <p/> 447 * The number matching is <i>at least</i> based which means that if more 448 * messages received it will match also. 449 * <p/> 450 * The difference between <i>done</i> and <i>completed</i> is that done can 451 * also include failed messages, where as completed is only successful 452 * processed messages. 453 * 454 * @param number at least number of messages 455 * @return the builder 456 */ 457 public NotifyBuilder whenDone(final int number) { 458 stack.add(new EventPredicateSupport() { 459 private final AtomicInteger current = new AtomicInteger(); 460 461 @Override 462 public boolean onExchangeCompleted(Exchange exchange) { 463 current.incrementAndGet(); 464 return true; 465 } 466 467 @Override 468 public boolean onExchangeFailed(Exchange exchange) { 469 current.incrementAndGet(); 470 return true; 471 } 472 473 public boolean matches() { 474 return current.get() >= number; 475 } 476 477 @Override 478 public void reset() { 479 current.set(0); 480 } 481 482 @Override 483 public String toString() { 484 return "whenDone(" + number + ")"; 485 } 486 }); 487 return this; 488 } 489 490 /** 491 * Sets a condition when tne <tt>n'th</tt> (by index) {@link Exchange} is 492 * done being processed. 493 * <p/> 494 * The difference between <i>done</i> and <i>completed</i> is that done can 495 * also include failed messages, where as completed is only successful 496 * processed messages. 497 * 498 * @param index the message by index to be done 499 * @return the builder 500 */ 501 public NotifyBuilder whenDoneByIndex(final int index) { 502 stack.add(new EventPredicateSupport() { 503 private AtomicInteger current = new AtomicInteger(); 504 private String id; 505 private AtomicBoolean done = new AtomicBoolean(); 506 507 @Override 508 public boolean onExchangeCreated(Exchange exchange) { 509 if (current.get() == index) { 510 id = exchange.getExchangeId(); 511 } 512 current.incrementAndGet(); 513 return true; 514 } 515 516 @Override 517 public boolean onExchangeCompleted(Exchange exchange) { 518 if (exchange.getExchangeId().equals(id)) { 519 done.set(true); 520 } 521 return true; 522 } 523 524 @Override 525 public boolean onExchangeFailed(Exchange exchange) { 526 if (exchange.getExchangeId().equals(id)) { 527 done.set(true); 528 } 529 return true; 530 } 531 532 public boolean matches() { 533 return done.get(); 534 } 535 536 @Override 537 public void reset() { 538 current.set(0); 539 id = null; 540 done.set(false); 541 } 542 543 @Override 544 public String toString() { 545 return "whenDoneByIndex(" + index + ")"; 546 } 547 }); 548 return this; 549 } 550 551 /** 552 * Sets a condition when <tt>number</tt> of {@link Exchange} has been 553 * completed. 554 * <p/> 555 * The number matching is <i>at least</i> based which means that if more 556 * messages received it will match also. 557 * <p/> 558 * The difference between <i>done</i> and <i>completed</i> is that done can 559 * also include failed messages, where as completed is only successful 560 * processed messages. 561 * 562 * @param number at least number of messages 563 * @return the builder 564 */ 565 public NotifyBuilder whenCompleted(final int number) { 566 stack.add(new EventPredicateSupport() { 567 private AtomicInteger current = new AtomicInteger(); 568 569 @Override 570 public boolean onExchangeCompleted(Exchange exchange) { 571 current.incrementAndGet(); 572 return true; 573 } 574 575 public boolean matches() { 576 return current.get() >= number; 577 } 578 579 @Override 580 public void reset() { 581 current.set(0); 582 } 583 584 @Override 585 public String toString() { 586 return "whenCompleted(" + number + ")"; 587 } 588 }); 589 return this; 590 } 591 592 /** 593 * Sets a condition when <tt>number</tt> of {@link Exchange} has failed. 594 * <p/> 595 * The number matching is <i>at least</i> based which means that if more 596 * messages received it will match also. 597 * 598 * @param number at least number of messages 599 * @return the builder 600 */ 601 public NotifyBuilder whenFailed(final int number) { 602 stack.add(new EventPredicateSupport() { 603 private AtomicInteger current = new AtomicInteger(); 604 605 @Override 606 public boolean onExchangeFailed(Exchange exchange) { 607 current.incrementAndGet(); 608 return true; 609 } 610 611 public boolean matches() { 612 return current.get() >= number; 613 } 614 615 @Override 616 public void reset() { 617 current.set(0); 618 } 619 620 @Override 621 public String toString() { 622 return "whenFailed(" + number + ")"; 623 } 624 }); 625 return this; 626 } 627 628 /** 629 * Sets a condition when <tt>number</tt> of {@link Exchange} is done being 630 * processed. 631 * <p/> 632 * messages, where as completed is only successful processed messages. 633 * 634 * @param number exactly number of messages 635 * @return the builder 636 */ 637 public NotifyBuilder whenExactlyDone(final int number) { 638 stack.add(new EventPredicateSupport() { 639 private AtomicInteger current = new AtomicInteger(); 640 641 @Override 642 public boolean onExchangeCompleted(Exchange exchange) { 643 current.incrementAndGet(); 644 return true; 645 } 646 647 @Override 648 public boolean onExchangeFailed(Exchange exchange) { 649 current.incrementAndGet(); 650 return true; 651 } 652 653 public boolean matches() { 654 return current.get() == number; 655 } 656 657 @Override 658 public void reset() { 659 current.set(0); 660 } 661 662 @Override 663 public String toString() { 664 return "whenExactlyDone(" + number + ")"; 665 } 666 }); 667 return this; 668 } 669 670 /** 671 * Sets a condition when <tt>number</tt> of {@link Exchange} has been 672 * completed. 673 * <p/> 674 * The difference between <i>done</i> and <i>completed</i> is that done can 675 * also include failed messages, where as completed is only successful 676 * processed messages. 677 * 678 * @param number exactly number of messages 679 * @return the builder 680 */ 681 public NotifyBuilder whenExactlyCompleted(final int number) { 682 stack.add(new EventPredicateSupport() { 683 private AtomicInteger current = new AtomicInteger(); 684 685 @Override 686 public boolean onExchangeCompleted(Exchange exchange) { 687 current.incrementAndGet(); 688 return true; 689 } 690 691 public boolean matches() { 692 return current.get() == number; 693 } 694 695 @Override 696 public void reset() { 697 current.set(0); 698 } 699 700 @Override 701 public String toString() { 702 return "whenExactlyCompleted(" + number + ")"; 703 } 704 }); 705 return this; 706 } 707 708 /** 709 * Sets a condition when <tt>number</tt> of {@link Exchange} has failed. 710 * 711 * @param number exactly number of messages 712 * @return the builder 713 */ 714 public NotifyBuilder whenExactlyFailed(final int number) { 715 stack.add(new EventPredicateSupport() { 716 private AtomicInteger current = new AtomicInteger(); 717 718 @Override 719 public boolean onExchangeFailed(Exchange exchange) { 720 current.incrementAndGet(); 721 return true; 722 } 723 724 public boolean matches() { 725 return current.get() == number; 726 } 727 728 @Override 729 public void reset() { 730 current.set(0); 731 } 732 733 @Override 734 public String toString() { 735 return "whenExactlyFailed(" + number + ")"; 736 } 737 }); 738 return this; 739 } 740 741 /** 742 * Sets a condition that <b>any received</b> {@link Exchange} should match 743 * the {@link Predicate} 744 * 745 * @param predicate the predicate 746 * @return the builder 747 */ 748 public NotifyBuilder whenAnyReceivedMatches(final Predicate predicate) { 749 return doWhenAnyMatches(predicate, true); 750 } 751 752 /** 753 * Sets a condition that <b>any done</b> {@link Exchange} should match the 754 * {@link Predicate} 755 * 756 * @param predicate the predicate 757 * @return the builder 758 */ 759 public NotifyBuilder whenAnyDoneMatches(final Predicate predicate) { 760 return doWhenAnyMatches(predicate, false); 761 } 762 763 private NotifyBuilder doWhenAnyMatches(final Predicate predicate, final boolean received) { 764 stack.add(new EventPredicateSupport() { 765 private final AtomicBoolean matches = new AtomicBoolean(); 766 767 @Override 768 public boolean onExchangeCompleted(Exchange exchange) { 769 if (!received && !matches.get()) { 770 matches.set(predicate.matches(exchange)); 771 } 772 return true; 773 } 774 775 @Override 776 public boolean onExchangeFailed(Exchange exchange) { 777 if (!received && !matches.get()) { 778 matches.set(predicate.matches(exchange)); 779 } 780 return true; 781 } 782 783 @Override 784 public boolean onExchangeCreated(Exchange exchange) { 785 if (received && !matches.get()) { 786 matches.set(predicate.matches(exchange)); 787 } 788 return true; 789 } 790 791 public boolean matches() { 792 return matches.get(); 793 } 794 795 @Override 796 public void reset() { 797 matches.set(false); 798 } 799 800 @Override 801 public String toString() { 802 if (received) { 803 return "whenAnyReceivedMatches(" + predicate + ")"; 804 } else { 805 return "whenAnyDoneMatches(" + predicate + ")"; 806 } 807 } 808 }); 809 return this; 810 } 811 812 /** 813 * Sets a condition that <b>all received</b> {@link Exchange} should match 814 * the {@link Predicate} 815 * 816 * @param predicate the predicate 817 * @return the builder 818 */ 819 public NotifyBuilder whenAllReceivedMatches(final Predicate predicate) { 820 return doWhenAllMatches(predicate, true); 821 } 822 823 /** 824 * Sets a condition that <b>all done</b> {@link Exchange} should match the 825 * {@link Predicate} 826 * 827 * @param predicate the predicate 828 * @return the builder 829 */ 830 public NotifyBuilder whenAllDoneMatches(final Predicate predicate) { 831 return doWhenAllMatches(predicate, false); 832 } 833 834 private NotifyBuilder doWhenAllMatches(final Predicate predicate, final boolean received) { 835 stack.add(new EventPredicateSupport() { 836 private final AtomicBoolean matches = new AtomicBoolean(true); 837 838 @Override 839 public boolean onExchangeCompleted(Exchange exchange) { 840 if (!received && matches.get()) { 841 matches.set(predicate.matches(exchange)); 842 } 843 return true; 844 } 845 846 @Override 847 public boolean onExchangeFailed(Exchange exchange) { 848 if (!received && matches.get()) { 849 matches.set(predicate.matches(exchange)); 850 } 851 return true; 852 } 853 854 @Override 855 public boolean onExchangeCreated(Exchange exchange) { 856 if (received && matches.get()) { 857 matches.set(predicate.matches(exchange)); 858 } 859 return true; 860 } 861 862 public boolean matches() { 863 return matches.get(); 864 } 865 866 @Override 867 public void reset() { 868 matches.set(true); 869 } 870 871 @Override 872 public String toString() { 873 if (received) { 874 return "whenAllReceivedMatches(" + predicate + ")"; 875 } else { 876 return "whenAllDoneMatches(" + predicate + ")"; 877 } 878 } 879 }); 880 return this; 881 } 882 883 /** 884 * Sets a condition that the bodies is expected to be <b>received</b> in the 885 * order as well. 886 * <p/> 887 * This condition will discard any additional messages. If you need a more 888 * strict condition then use {@link #whenExactBodiesReceived(Object...)} 889 * 890 * @param bodies the expected bodies 891 * @return the builder 892 * @see #whenExactBodiesReceived(Object...) 893 */ 894 public NotifyBuilder whenBodiesReceived(Object... bodies) { 895 List<Object> bodyList = new ArrayList<>(); 896 bodyList.addAll(Arrays.asList(bodies)); 897 return doWhenBodies(bodyList, true, false); 898 } 899 900 /** 901 * Sets a condition that the bodies is expected to be <b>done</b> in the 902 * order as well. 903 * <p/> 904 * This condition will discard any additional messages. If you need a more 905 * strict condition then use {@link #whenExactBodiesDone(Object...)} 906 * 907 * @param bodies the expected bodies 908 * @return the builder 909 * @see #whenExactBodiesDone(Object...) 910 */ 911 public NotifyBuilder whenBodiesDone(Object... bodies) { 912 List<Object> bodyList = new ArrayList<>(); 913 bodyList.addAll(Arrays.asList(bodies)); 914 return doWhenBodies(bodyList, false, false); 915 } 916 917 /** 918 * Sets a condition that the bodies is expected to be <b>received</b> in the 919 * order as well. 920 * <p/> 921 * This condition is strict which means that it only expect that exact 922 * number of bodies 923 * 924 * @param bodies the expected bodies 925 * @return the builder 926 * @see #whenBodiesReceived(Object...) 927 */ 928 public NotifyBuilder whenExactBodiesReceived(Object... bodies) { 929 List<Object> bodyList = new ArrayList<>(); 930 bodyList.addAll(Arrays.asList(bodies)); 931 return doWhenBodies(bodyList, true, true); 932 } 933 934 /** 935 * Sets a condition that the bodies is expected to be <b>done</b> in the 936 * order as well. 937 * <p/> 938 * This condition is strict which means that it only expect that exact 939 * number of bodies 940 * 941 * @param bodies the expected bodies 942 * @return the builder 943 * @see #whenExactBodiesDone(Object...) 944 */ 945 public NotifyBuilder whenExactBodiesDone(Object... bodies) { 946 List<Object> bodyList = new ArrayList<>(); 947 bodyList.addAll(Arrays.asList(bodies)); 948 return doWhenBodies(bodyList, false, true); 949 } 950 951 private NotifyBuilder doWhenBodies(final List<?> bodies, final boolean received, final boolean exact) { 952 stack.add(new EventPredicateSupport() { 953 private volatile boolean matches; 954 private final AtomicInteger current = new AtomicInteger(); 955 956 @Override 957 public boolean onExchangeCreated(Exchange exchange) { 958 if (received) { 959 matchBody(exchange); 960 } 961 return true; 962 } 963 964 @Override 965 public boolean onExchangeFailed(Exchange exchange) { 966 if (!received) { 967 matchBody(exchange); 968 } 969 return true; 970 } 971 972 @Override 973 public boolean onExchangeCompleted(Exchange exchange) { 974 if (!received) { 975 matchBody(exchange); 976 } 977 return true; 978 } 979 980 private void matchBody(Exchange exchange) { 981 if (current.incrementAndGet() > bodies.size()) { 982 // out of bounds 983 return; 984 } 985 986 Object actual = exchange.getIn().getBody(); 987 Object expected = bodies.get(current.get() - 1); 988 matches = ObjectHelper.equal(expected, actual); 989 } 990 991 public boolean matches() { 992 if (exact) { 993 return matches && current.get() == bodies.size(); 994 } else { 995 return matches && current.get() >= bodies.size(); 996 } 997 } 998 999 @Override 1000 public void reset() { 1001 matches = false; 1002 current.set(0); 1003 } 1004 1005 @Override 1006 public String toString() { 1007 if (received) { 1008 return "" + (exact ? "whenExactBodiesReceived(" : "whenBodiesReceived(") + bodies + ")"; 1009 } else { 1010 return "" + (exact ? "whenExactBodiesDone(" : "whenBodiesDone(") + bodies + ")"; 1011 } 1012 } 1013 }); 1014 return this; 1015 } 1016 1017 /** 1018 * Sets a condition when the provided matcher (such as mock endpoint) is 1019 * satisfied based on {@link Exchange} being sent to it when they are 1020 * <b>done</b>. 1021 * <p/> 1022 * The idea is that you can use mock endpoints (or other matchers) for 1023 * setting fine grained expectations and then use that together with this 1024 * builder. The mock provided does <b>NOT</b> have to already exist in the 1025 * route. You can just create a new pseudo mock and this builder will send 1026 * the done {@link Exchange} to it. So its like adding the mock to the end 1027 * of your route(s). 1028 * 1029 * @param matcher the matcher such as mock endpoint 1030 * @return the builder 1031 */ 1032 public NotifyBuilder whenDoneSatisfied(final NotifyBuilderMatcher matcher) { 1033 return doWhenSatisfied(matcher, false); 1034 } 1035 1036 /** 1037 * Sets a condition when the provided matcher (such as mock endpoint) is 1038 * satisfied based on {@link Exchange} being sent to it when they are 1039 * <b>received</b>. 1040 * <p/> 1041 * The idea is that you can use mock endpoints (or other matchers) for 1042 * setting fine grained expectations and then use that together with this 1043 * builder. The mock provided does <b>NOT</b> have to already exist in the 1044 * route. You can just create a new pseudo mock and this builder will send 1045 * the done {@link Exchange} to it. So its like adding the mock to the end 1046 * of your route(s). 1047 * 1048 * @param matcher the matcher such as mock endpoint 1049 * @return the builder 1050 */ 1051 public NotifyBuilder whenReceivedSatisfied(final NotifyBuilderMatcher matcher) { 1052 return doWhenSatisfied(matcher, true); 1053 } 1054 1055 private NotifyBuilder doWhenSatisfied(final NotifyBuilderMatcher matcher, final boolean received) { 1056 stack.add(new EventPredicateSupport() { 1057 1058 @Override 1059 public boolean onExchangeCreated(Exchange exchange) { 1060 if (received) { 1061 matcher.notifyBuilderOnExchange(exchange); 1062 } 1063 return true; 1064 } 1065 1066 @Override 1067 public boolean onExchangeFailed(Exchange exchange) { 1068 if (!received) { 1069 matcher.notifyBuilderOnExchange(exchange); 1070 } 1071 return true; 1072 } 1073 1074 @Override 1075 public boolean onExchangeCompleted(Exchange exchange) { 1076 if (!received) { 1077 matcher.notifyBuilderOnExchange(exchange); 1078 } 1079 return true; 1080 } 1081 1082 public boolean matches() { 1083 return matcher.notifyBuilderMatches(); 1084 } 1085 1086 @Override 1087 public void reset() { 1088 matcher.notifyBuilderReset(); 1089 } 1090 1091 @Override 1092 public String toString() { 1093 if (received) { 1094 return "whenReceivedSatisfied(" + matcher + ")"; 1095 } else { 1096 return "whenDoneSatisfied(" + matcher + ")"; 1097 } 1098 } 1099 }); 1100 return this; 1101 } 1102 1103 /** 1104 * Sets a condition when the provided matcher (such as mock endpoint) is 1105 * <b>not</b> satisfied based on {@link Exchange} being sent to it when they 1106 * are <b>received</b>. 1107 * <p/> 1108 * The idea is that you can use mock endpoints (or other matchers) for 1109 * setting fine grained expectations and then use that together with this 1110 * builder. The mock provided does <b>NOT</b> have to already exist in the 1111 * route. You can just create a new pseudo mock and this builder will send 1112 * the done {@link Exchange} to it. So its like adding the mock to the end 1113 * of your route(s). 1114 * 1115 * @param matcher the matcher such as mock endpoint 1116 * @return the builder 1117 */ 1118 public NotifyBuilder whenReceivedNotSatisfied(final NotifyBuilderMatcher matcher) { 1119 return doWhenNotSatisfied(matcher, true); 1120 } 1121 1122 /** 1123 * Sets a condition when the provided matcher (such as mock endpoint) is 1124 * <b>not</b> satisfied based on {@link Exchange} being sent to it when they 1125 * are <b>done</b>. 1126 * <p/> 1127 * The idea is that you can use mock endpoints (or other matchers) for 1128 * setting fine grained expectations and then use that together with this 1129 * builder. The mock provided does <b>NOT</b> have to already exist in the 1130 * route. You can just create a new pseudo mock and this builder will send 1131 * the done {@link Exchange} to it. So its like adding the mock to the end 1132 * of your route(s). 1133 * 1134 * @param matcher the matcher such as mock endpoint 1135 * @return the builder 1136 */ 1137 public NotifyBuilder whenDoneNotSatisfied(final NotifyBuilderMatcher matcher) { 1138 return doWhenNotSatisfied(matcher, false); 1139 } 1140 1141 private NotifyBuilder doWhenNotSatisfied(final NotifyBuilderMatcher mock, final boolean received) { 1142 stack.add(new EventPredicateSupport() { 1143 1144 @Override 1145 public boolean onExchangeCreated(Exchange exchange) { 1146 if (received) { 1147 mock.notifyBuilderOnExchange(exchange); 1148 } 1149 return true; 1150 } 1151 1152 @Override 1153 public boolean onExchangeFailed(Exchange exchange) { 1154 if (!received) { 1155 mock.notifyBuilderOnExchange(exchange); 1156 } 1157 return true; 1158 } 1159 1160 @Override 1161 public boolean onExchangeCompleted(Exchange exchange) { 1162 if (!received) { 1163 mock.notifyBuilderOnExchange(exchange); 1164 } 1165 return true; 1166 } 1167 1168 public boolean matches() { 1169 return !mock.notifyBuilderMatches(); 1170 } 1171 1172 @Override 1173 public void reset() { 1174 mock.notifyBuilderReset(); 1175 } 1176 1177 @Override 1178 public String toString() { 1179 if (received) { 1180 return "whenReceivedNotSatisfied(" + mock + ")"; 1181 } else { 1182 return "whenDoneNotSatisfied(" + mock + ")"; 1183 } 1184 } 1185 }); 1186 return this; 1187 } 1188 1189 /** 1190 * Prepares to append an additional expression using the <i>and</i> 1191 * operator. 1192 * 1193 * @return the builder 1194 */ 1195 public NotifyBuilder and() { 1196 doCreate(EventOperation.and); 1197 return this; 1198 } 1199 1200 /** 1201 * Prepares to append an additional expression using the <i>or</i> operator. 1202 * 1203 * @return the builder 1204 */ 1205 public NotifyBuilder or() { 1206 doCreate(EventOperation.or); 1207 return this; 1208 } 1209 1210 /** 1211 * Prepares to append an additional expression using the <i>not</i> 1212 * operator. 1213 * 1214 * @return the builder 1215 */ 1216 public NotifyBuilder not() { 1217 doCreate(EventOperation.not); 1218 return this; 1219 } 1220 1221 /** 1222 * Specifies the wait time in millis to use in the 1223 * {@link #matchesWaitTime()} method. 1224 */ 1225 public NotifyBuilder waitTime(long waitTime) { 1226 this.waitTime = waitTime; 1227 return this; 1228 } 1229 1230 /** 1231 * Creates the expression this builder should use for matching. 1232 * <p/> 1233 * You must call this method when you are finished building the expressions. 1234 * 1235 * @return the created builder ready for matching 1236 */ 1237 public NotifyBuilder create() { 1238 doCreate(EventOperation.and); 1239 if (eventNotifier.isStopped()) { 1240 throw new IllegalStateException("A destroyed NotifyBuilder cannot be re-created."); 1241 } 1242 created = true; 1243 return this; 1244 } 1245 1246 /** 1247 * De-registers this builder from its {@link CamelContext}. 1248 * <p/> 1249 * Once destroyed, this instance will not function again. 1250 */ 1251 public void destroy() { 1252 context.getManagementStrategy().removeEventNotifier(eventNotifier); 1253 try { 1254 ServiceHelper.stopService(eventNotifier); 1255 } catch (Exception e) { 1256 throw RuntimeCamelException.wrapRuntimeCamelException(e); 1257 } 1258 created = false; 1259 } 1260 1261 /** 1262 * Does all the expression match? 1263 * <p/> 1264 * This operation will return immediately which means it can be used for 1265 * testing at this very moment. 1266 * 1267 * @return <tt>true</tt> if matching, <tt>false</tt> otherwise 1268 */ 1269 public boolean matches() { 1270 if (!created) { 1271 throw new IllegalStateException("NotifyBuilder has not been created. Invoke the create() method before matching."); 1272 } 1273 return matches; 1274 } 1275 1276 /** 1277 * Does all the expression match? 1278 * <p/> 1279 * This operation will wait until the match is <tt>true</tt> or otherwise a 1280 * timeout occur which means <tt>false</tt> will be returned. 1281 * 1282 * @param timeout the timeout value 1283 * @param timeUnit the time unit 1284 * @return <tt>true</tt> if matching, <tt>false</tt> otherwise due to 1285 * timeout 1286 */ 1287 public boolean matches(long timeout, TimeUnit timeUnit) { 1288 if (!created) { 1289 throw new IllegalStateException("NotifyBuilder has not been created. Invoke the create() method before matching."); 1290 } 1291 try { 1292 latch.await(timeout, timeUnit); 1293 } catch (InterruptedException e) { 1294 throw RuntimeCamelException.wrapRuntimeCamelException(e); 1295 } 1296 return matches(); 1297 } 1298 1299 /** 1300 * Does all the expressions match? 1301 * <p/> 1302 * This operation will wait until the match is <tt>true</tt> or otherwise a 1303 * timeout occur which means <tt>false</tt> will be returned. 1304 * <p/> 1305 * The timeout value is by default 10 seconds. 1306 * 1307 * @return <tt>true</tt> if matching, <tt>false</tt> otherwise due to 1308 * timeout 1309 * @deprecated use {@link #matchesWaitTime()} instead 1310 */ 1311 @Deprecated 1312 public boolean matchesMockWaitTime() { 1313 return matchesWaitTime(); 1314 } 1315 1316 /** 1317 * Does all the expressions match? 1318 * <p/> 1319 * This operation will wait until the match is <tt>true</tt> or otherwise a 1320 * timeout occur which means <tt>false</tt> will be returned. 1321 * <p/> 1322 * The timeout value is by default 10 seconds. 1323 * 1324 * @return <tt>true</tt> if matching, <tt>false</tt> otherwise due to 1325 * timeout 1326 */ 1327 public boolean matchesWaitTime() { 1328 if (!created) { 1329 throw new IllegalStateException("NotifyBuilder has not been created. Invoke the create() method before matching."); 1330 } 1331 1332 return matches(waitTime, TimeUnit.MILLISECONDS); 1333 } 1334 1335 /** 1336 * Resets the notifier. 1337 */ 1338 public void reset() { 1339 for (EventPredicateHolder predicate : predicates) { 1340 predicate.reset(); 1341 } 1342 latch = new CountDownLatch(1); 1343 matches = false; 1344 } 1345 1346 @Override 1347 public String toString() { 1348 StringBuilder sb = new StringBuilder(); 1349 for (EventPredicateHolder eventPredicateHolder : predicates) { 1350 if (sb.length() > 0) { 1351 sb.append("."); 1352 } 1353 sb.append(eventPredicateHolder.toString()); 1354 } 1355 // a crude way of skipping the first invisible operation 1356 return StringHelper.after(sb.toString(), "()."); 1357 } 1358 1359 private void doCreate(EventOperation newOperation) { 1360 // init operation depending on the newOperation 1361 if (operation == null) { 1362 // if the first new operation is an or then this operation must be 1363 // an or as well 1364 // otherwise it should be and based 1365 operation = newOperation == EventOperation.or ? EventOperation.or : EventOperation.and; 1366 } 1367 1368 // we have some predicates 1369 if (!stack.isEmpty()) { 1370 // we only want to match from routes, so skip for example events 1371 // which is triggered by producer templates etc. 1372 fromRoutesOnly(); 1373 1374 // the stack must have at least one non abstract 1375 boolean found = false; 1376 for (EventPredicate predicate : stack) { 1377 if (!predicate.isAbstract()) { 1378 found = true; 1379 break; 1380 } 1381 } 1382 if (!found) { 1383 throw new IllegalArgumentException("NotifyBuilder must contain at least one non-abstract predicate (such as whenDone)"); 1384 } 1385 1386 CompoundEventPredicate compound = new CompoundEventPredicate(stack); 1387 stack.clear(); 1388 predicates.add(new EventPredicateHolder(operation, compound)); 1389 } 1390 1391 operation = newOperation; 1392 // reset wereSentTo index position as this its a new group 1393 wereSentToIndex = 0; 1394 } 1395 1396 /** 1397 * Notifier which hooks into Camel to listen for {@link Exchange} relevant 1398 * events for this builder 1399 */ 1400 private final class ExchangeNotifier extends EventNotifierSupport { 1401 1402 @Override 1403 public void notify(CamelEvent event) throws Exception { 1404 if (event instanceof ExchangeCreatedEvent) { 1405 onExchangeCreated((ExchangeCreatedEvent)event); 1406 } else if (event instanceof ExchangeCompletedEvent) { 1407 onExchangeCompleted((ExchangeCompletedEvent)event); 1408 } else if (event instanceof ExchangeFailedEvent) { 1409 onExchangeFailed((ExchangeFailedEvent)event); 1410 } else if (event instanceof ExchangeSentEvent) { 1411 onExchangeSent((ExchangeSentEvent)event); 1412 } 1413 1414 // now compute whether we matched 1415 computeMatches(); 1416 } 1417 1418 @Override 1419 public boolean isEnabled(CamelEvent event) { 1420 return true; 1421 } 1422 1423 private void onExchangeCreated(ExchangeCreatedEvent event) { 1424 for (EventPredicateHolder predicate : predicates) { 1425 predicate.getPredicate().onExchangeCreated(event.getExchange()); 1426 } 1427 } 1428 1429 private void onExchangeCompleted(ExchangeCompletedEvent event) { 1430 for (EventPredicateHolder predicate : predicates) { 1431 predicate.getPredicate().onExchangeCompleted(event.getExchange()); 1432 } 1433 } 1434 1435 private void onExchangeFailed(ExchangeFailedEvent event) { 1436 for (EventPredicateHolder predicate : predicates) { 1437 predicate.getPredicate().onExchangeFailed(event.getExchange()); 1438 } 1439 } 1440 1441 private void onExchangeSent(ExchangeSentEvent event) { 1442 for (EventPredicateHolder predicate : predicates) { 1443 predicate.getPredicate().onExchangeSent(event.getExchange(), event.getEndpoint(), event.getTimeTaken()); 1444 } 1445 } 1446 1447 private synchronized void computeMatches() { 1448 // use a temporary answer until we have computed the value to assign 1449 Boolean answer = null; 1450 1451 for (EventPredicateHolder holder : predicates) { 1452 EventOperation operation = holder.getOperation(); 1453 if (EventOperation.and == operation) { 1454 if (holder.getPredicate().matches()) { 1455 answer = true; 1456 } else { 1457 answer = false; 1458 // and break out since its an AND so it must match 1459 break; 1460 } 1461 } else if (EventOperation.or == operation) { 1462 if (holder.getPredicate().matches()) { 1463 answer = true; 1464 } 1465 } else if (EventOperation.not == operation) { 1466 if (holder.getPredicate().matches()) { 1467 answer = false; 1468 // and break out since its a NOT so it must not match 1469 break; 1470 } else { 1471 answer = true; 1472 } 1473 } 1474 } 1475 1476 // if we did compute a value then assign that 1477 if (answer != null) { 1478 matches = answer; 1479 if (matches) { 1480 // signal completion 1481 latch.countDown(); 1482 } 1483 } 1484 } 1485 1486 @Override 1487 protected void doStart() throws Exception { 1488 // we only care about Exchange events 1489 setIgnoreCamelContextEvents(true); 1490 setIgnoreRouteEvents(true); 1491 setIgnoreServiceEvents(true); 1492 } 1493 } 1494 1495 private enum EventOperation { 1496 and, or, not 1497 } 1498 1499 private interface EventPredicate { 1500 1501 /** 1502 * Evaluates whether the predicate matched or not. 1503 * 1504 * @return <tt>true</tt> if matched, <tt>false</tt> otherwise 1505 */ 1506 boolean matches(); 1507 1508 /** 1509 * Resets the predicate 1510 */ 1511 void reset(); 1512 1513 /** 1514 * Whether the predicate is abstract 1515 */ 1516 boolean isAbstract(); 1517 1518 /** 1519 * Callback for {@link Exchange} lifecycle 1520 * 1521 * @param exchange the exchange 1522 * @return <tt>true</tt> to allow continue evaluating, <tt>false</tt> to 1523 * stop immediately 1524 */ 1525 boolean onExchangeCreated(Exchange exchange); 1526 1527 /** 1528 * Callback for {@link Exchange} lifecycle 1529 * 1530 * @param exchange the exchange 1531 * @return <tt>true</tt> to allow continue evaluating, <tt>false</tt> to 1532 * stop immediately 1533 */ 1534 boolean onExchangeCompleted(Exchange exchange); 1535 1536 /** 1537 * Callback for {@link Exchange} lifecycle 1538 * 1539 * @param exchange the exchange 1540 * @return <tt>true</tt> to allow continue evaluating, <tt>false</tt> to 1541 * stop immediately 1542 */ 1543 boolean onExchangeFailed(Exchange exchange); 1544 1545 /** 1546 * Callback for {@link Exchange} lifecycle 1547 * 1548 * @param exchange the exchange 1549 * @param endpoint the endpoint sent to 1550 * @param timeTaken time taken in millis to send the to endpoint 1551 * @return <tt>true</tt> to allow continue evaluating, <tt>false</tt> to 1552 * stop immediately 1553 */ 1554 boolean onExchangeSent(Exchange exchange, Endpoint endpoint, long timeTaken); 1555 } 1556 1557 private abstract class EventPredicateSupport implements EventPredicate { 1558 1559 @Override 1560 public boolean isAbstract() { 1561 return false; 1562 } 1563 1564 @Override 1565 public void reset() { 1566 // noop 1567 } 1568 1569 @Override 1570 public boolean onExchangeCreated(Exchange exchange) { 1571 return onExchange(exchange); 1572 } 1573 1574 @Override 1575 public boolean onExchangeCompleted(Exchange exchange) { 1576 return onExchange(exchange); 1577 } 1578 1579 @Override 1580 public boolean onExchangeFailed(Exchange exchange) { 1581 return onExchange(exchange); 1582 } 1583 1584 @Override 1585 public boolean onExchangeSent(Exchange exchange, Endpoint endpoint, long timeTaken) { 1586 // no need to invoke onExchange as this is a special case when the 1587 // Exchange 1588 // was sent to a specific endpoint 1589 return true; 1590 } 1591 1592 public boolean onExchange(Exchange exchange) { 1593 return true; 1594 } 1595 } 1596 1597 /** 1598 * To hold an operation and predicate 1599 */ 1600 private final class EventPredicateHolder { 1601 private final EventOperation operation; 1602 private final EventPredicate predicate; 1603 1604 private EventPredicateHolder(EventOperation operation, EventPredicate predicate) { 1605 this.operation = operation; 1606 this.predicate = predicate; 1607 } 1608 1609 public EventOperation getOperation() { 1610 return operation; 1611 } 1612 1613 public EventPredicate getPredicate() { 1614 return predicate; 1615 } 1616 1617 public void reset() { 1618 predicate.reset(); 1619 } 1620 1621 @Override 1622 public String toString() { 1623 return operation.name() + "()." + predicate; 1624 } 1625 } 1626 1627 /** 1628 * To hold multiple predicates which are part of same expression 1629 */ 1630 private final class CompoundEventPredicate implements EventPredicate { 1631 1632 private List<EventPredicate> predicates = new ArrayList<>(); 1633 1634 private CompoundEventPredicate(List<EventPredicate> predicates) { 1635 this.predicates.addAll(predicates); 1636 } 1637 1638 @Override 1639 public boolean isAbstract() { 1640 return false; 1641 } 1642 1643 @Override 1644 public boolean matches() { 1645 for (EventPredicate predicate : predicates) { 1646 boolean answer = predicate.matches(); 1647 LOG.trace("matches() {} -> {}", predicate, answer); 1648 if (!answer) { 1649 // break at first false 1650 return false; 1651 } 1652 } 1653 return true; 1654 } 1655 1656 @Override 1657 public void reset() { 1658 for (EventPredicate predicate : predicates) { 1659 LOG.trace("reset() {}", predicate); 1660 predicate.reset(); 1661 } 1662 } 1663 1664 @Override 1665 public boolean onExchangeCreated(Exchange exchange) { 1666 for (EventPredicate predicate : predicates) { 1667 boolean answer = predicate.onExchangeCreated(exchange); 1668 LOG.trace("onExchangeCreated() {} -> {}", predicate, answer); 1669 if (!answer) { 1670 // break at first false 1671 return false; 1672 } 1673 } 1674 return true; 1675 } 1676 1677 @Override 1678 public boolean onExchangeCompleted(Exchange exchange) { 1679 for (EventPredicate predicate : predicates) { 1680 boolean answer = predicate.onExchangeCompleted(exchange); 1681 LOG.trace("onExchangeCompleted() {} -> {}", predicate, answer); 1682 if (!answer) { 1683 // break at first false 1684 return false; 1685 } 1686 } 1687 return true; 1688 } 1689 1690 @Override 1691 public boolean onExchangeFailed(Exchange exchange) { 1692 for (EventPredicate predicate : predicates) { 1693 boolean answer = predicate.onExchangeFailed(exchange); 1694 LOG.trace("onExchangeFailed() {} -> {}", predicate, answer); 1695 if (!answer) { 1696 // break at first false 1697 return false; 1698 } 1699 } 1700 return true; 1701 } 1702 1703 @Override 1704 public boolean onExchangeSent(Exchange exchange, Endpoint endpoint, long timeTaken) { 1705 for (EventPredicate predicate : predicates) { 1706 boolean answer = predicate.onExchangeSent(exchange, endpoint, timeTaken); 1707 LOG.trace("onExchangeSent() {} {} -> {}", endpoint, predicate, answer); 1708 if (!answer) { 1709 // break at first false 1710 return false; 1711 } 1712 } 1713 return true; 1714 } 1715 1716 @Override 1717 public String toString() { 1718 StringBuilder sb = new StringBuilder(); 1719 for (EventPredicate eventPredicate : predicates) { 1720 if (sb.length() > 0) { 1721 sb.append("."); 1722 } 1723 sb.append(eventPredicate.toString()); 1724 } 1725 return sb.toString(); 1726 } 1727 } 1728 1729}