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