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