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