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