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