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