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