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.Iterator;
023    import java.util.List;
024    import java.util.Stack;
025    import java.util.concurrent.CountDownLatch;
026    import java.util.concurrent.TimeUnit;
027    
028    import org.apache.camel.CamelContext;
029    import org.apache.camel.Exchange;
030    import org.apache.camel.Expression;
031    import org.apache.camel.Predicate;
032    import org.apache.camel.Producer;
033    import org.apache.camel.component.mock.MockEndpoint;
034    import org.apache.camel.management.EventNotifierSupport;
035    import org.apache.camel.management.event.ExchangeCompletedEvent;
036    import org.apache.camel.management.event.ExchangeCreatedEvent;
037    import org.apache.camel.management.event.ExchangeFailureEvent;
038    import org.apache.camel.spi.EventNotifier;
039    import org.apache.camel.util.EndpointHelper;
040    import org.apache.camel.util.ObjectHelper;
041    import org.apache.camel.util.ServiceHelper;
042    
043    /**
044     * A builder to build an expression based on {@link org.apache.camel.spi.EventNotifier} notifications
045     * about {@link Exchange} being routed.
046     * <p/>
047     * This builder can be used for testing purposes where you want to know when a test is supposed to be done.
048     * The idea is that you can build an expression that explains when the test is done. For example when Camel
049     * have finished routing 5 messages. You can then in your test await for this condition to occur.
050     *
051     * @version $Revision: 900466 $
052     */
053    public class NotifyBuilder {
054    
055        // notifier to hook into Camel to listen for events
056        private final EventNotifier eventNotifier = new ExchangeNotifier();
057    
058        // the predicates build with this builder
059        private final List<EventPredicateHolder> predicates = new ArrayList<EventPredicateHolder>();
060    
061        // latch to be used to signal predicates matches
062        private final CountDownLatch latch = new CountDownLatch(1);
063    
064        // the current state while building an event predicate where we use a stack and the operation
065        private final Stack<EventPredicate> stack = new Stack<EventPredicate>();
066        private EventOperation operation;
067    
068        // computed value whether all the predicates matched
069        private boolean matches;
070    
071        /**
072         * Creates a new builder.
073         *
074         * @param context the Camel context
075         */
076        public NotifyBuilder(CamelContext context) {
077            try {
078                ServiceHelper.startService(eventNotifier);
079            } catch (Exception e) {
080                throw ObjectHelper.wrapRuntimeCamelException(e);
081            }
082            context.getManagementStrategy().addEventNotifier(eventNotifier);
083        }
084    
085        /**
086         * Optionally a <tt>from</tt> endpoint which means that this expression should only be based
087         * on {@link Exchange} which is originated from the particular endpoint(s).
088         *
089         * @param endpointUri uri of endpoint or pattern (see the EndpointHelper javadoc)
090         * @return the builder
091         * @see org.apache.camel.util.EndpointHelper#matchEndpoint(String, String)
092         */
093        public NotifyBuilder from(final String endpointUri) {
094            stack.push(new EventPredicateSupport() {
095    
096                @Override
097                public boolean onExchange(Exchange exchange) {
098                    // filter non matching exchanges
099                    return EndpointHelper.matchEndpoint(exchange.getFromEndpoint().getEndpointUri(), endpointUri);
100                }
101    
102                public boolean matches() {
103                    return true;
104                }
105    
106                @Override
107                public String toString() {
108                    return "from(" + endpointUri + ")";
109                }
110            });
111            return this;
112        }
113    
114        /**
115         * Optionally a filter to only allow matching {@link Exchange} to be used for matching.
116         *
117         * @param predicate the predicate to use for the filter
118         * @return the builder
119         */
120        public NotifyBuilder filter(final Predicate predicate) {
121            stack.push(new EventPredicateSupport() {
122    
123                @Override
124                public boolean onExchange(Exchange exchange) {
125                    // filter non matching exchanges
126                    return predicate.matches(exchange);
127                }
128    
129                public boolean matches() {
130                    return true;
131                }
132    
133                @Override
134                public String toString() {
135                    return "filter(" + predicate + ")";
136                }
137            });
138            return this;
139        }
140    
141        /**
142         * Optionally a filter to only allow matching {@link Exchange} to be used for matching.
143         *
144         * @return the builder
145         */
146        public ExpressionClauseSupport<NotifyBuilder> filter() {
147            final ExpressionClauseSupport<NotifyBuilder> clause = new ExpressionClauseSupport<NotifyBuilder>(this);
148            stack.push(new EventPredicateSupport() {
149    
150                @Override
151                public boolean onExchange(Exchange exchange) {
152                    // filter non matching exchanges
153                    Expression exp = clause.createExpression(exchange.getContext());
154                    return exp.evaluate(exchange, Boolean.class);
155                }
156    
157                public boolean matches() {
158                    return true;
159                }
160    
161                @Override
162                public String toString() {
163                    return "filter(" + clause + ")";
164                }
165            });
166            return clause;
167        }
168    
169        /**
170         * Sets a condition when <tt>number</tt> of {@link Exchange} has been received.
171         * <p/>
172         * The number matching is <i>at least</i> based which means that if more messages received
173         * it will match also.
174         *
175         * @param number at least number of messages
176         * @return the builder
177         */
178        public NotifyBuilder whenReceived(final int number) {
179            stack.push(new EventPredicateSupport() {
180                private int current;
181    
182                @Override
183                public boolean onExchangeCreated(Exchange exchange) {
184                    current++;
185                    return true;
186                }
187    
188                public boolean matches() {
189                    return current >= number;
190                }
191    
192                @Override
193                public String toString() {
194                    return "whenReceived(" + number + ")";
195                }
196            });
197            return this;
198        }
199    
200        /**
201         * Sets a condition when <tt>number</tt> of {@link Exchange} is done being processed.
202         * <p/>
203         * The number matching is <i>at least</i> based which means that if more messages received
204         * it will match also.
205         * <p/>
206         * The difference between <i>done</i> and <i>completed</i> is that done can also include failed
207         * messages, where as completed is only successful processed messages.
208         *
209         * @param number at least number of messages
210         * @return the builder
211         */
212        public NotifyBuilder whenDone(final int number) {
213            stack.add(new EventPredicateSupport() {
214                private int current;
215    
216                @Override
217                public boolean onExchangeCompleted(Exchange exchange) {
218                    current++;
219                    return true;
220                }
221    
222                @Override
223                public boolean onExchangeFailure(Exchange exchange) {
224                    current++;
225                    return true;
226                }
227    
228                public boolean matches() {
229                    return current >= number;
230                }
231    
232                @Override
233                public String toString() {
234                    return "whenDone(" + number + ")";
235                }
236            });
237            return this;
238        }
239    
240        /**
241         * Sets a condition when <tt>number</tt> of {@link Exchange} has been completed.
242         * <p/>
243         * The number matching is <i>at least</i> based which means that if more messages received
244         * it will match also.
245         * <p/>
246         * The difference between <i>done</i> and <i>completed</i> is that done can also include failed
247         * messages, where as completed is only successful processed messages.
248         *
249         * @param number at least number of messages
250         * @return the builder
251         */
252        public NotifyBuilder whenCompleted(final int number) {
253            stack.add(new EventPredicateSupport() {
254                private int current;
255    
256                @Override
257                public boolean onExchangeCompleted(Exchange exchange) {
258                    current++;
259                    return true;
260                }
261    
262                public boolean matches() {
263                    return current >= number;
264                }
265    
266                @Override
267                public String toString() {
268                    return "whenCompleted(" + number + ")";
269                }
270            });
271            return this;
272        }
273    
274        /**
275         * Sets a condition when <tt>number</tt> of {@link Exchange} has failed.
276         * <p/>
277         * The number matching is <i>at least</i> based which means that if more messages received
278         * it will match also.
279         *
280         * @param number at least number of messages
281         * @return the builder
282         */
283        public NotifyBuilder whenFailed(final int number) {
284            stack.add(new EventPredicateSupport() {
285                private int current;
286    
287                @Override
288                public boolean onExchangeFailure(Exchange exchange) {
289                    current++;
290                    return true;
291                }
292    
293                public boolean matches() {
294                    return current >= number;
295                }
296    
297                @Override
298                public String toString() {
299                    return "whenFailed(" + number + ")";
300                }
301            });
302            return this;
303        }
304    
305        /**
306         * Sets a condition when <tt>number</tt> of {@link Exchange} is done being processed.
307         * <p/>
308         * messages, where as completed is only successful processed messages.
309         *
310         * @param number exactly number of messages
311         * @return the builder
312         */
313        public NotifyBuilder whenExactlyDone(final int number) {
314            stack.add(new EventPredicateSupport() {
315                private int current;
316    
317                @Override
318                public boolean onExchangeCompleted(Exchange exchange) {
319                    current++;
320                    return true;
321                }
322    
323                @Override
324                public boolean onExchangeFailure(Exchange exchange) {
325                    current++;
326                    return true;
327                }
328    
329                public boolean matches() {
330                    return current == number;
331                }
332    
333                @Override
334                public String toString() {
335                    return "whenExactlyDone(" + number + ")";
336                }
337            });
338            return this;
339        }
340    
341        /**
342         * Sets a condition when <tt>number</tt> of {@link Exchange} has been completed.
343         * <p/>
344         * The difference between <i>done</i> and <i>completed</i> is that done can also include failed
345         * messages, where as completed is only successful processed messages.
346         *
347         * @param number exactly number of messages
348         * @return the builder
349         */
350        public NotifyBuilder whenExactlyCompleted(final int number) {
351            stack.add(new EventPredicateSupport() {
352                private int current;
353    
354                @Override
355                public boolean onExchangeCompleted(Exchange exchange) {
356                    current++;
357                    return true;
358                }
359    
360                public boolean matches() {
361                    return current == number;
362                }
363    
364                @Override
365                public String toString() {
366                    return "whenExactlyCompleted(" + number + ")";
367                }
368            });
369            return this;
370        }
371    
372        /**
373         * Sets a condition when <tt>number</tt> of {@link Exchange} has failed.
374         *
375         * @param number exactly number of messages
376         * @return the builder
377         */
378        public NotifyBuilder whenExactlyFailed(final int number) {
379            stack.add(new EventPredicateSupport() {
380                private int current;
381    
382                @Override
383                public boolean onExchangeFailure(Exchange exchange) {
384                    current++;
385                    return true;
386                }
387    
388                public boolean matches() {
389                    return current == number;
390                }
391    
392                @Override
393                public String toString() {
394                    return "whenExactlyFailed(" + number + ")";
395                }
396            });
397            return this;
398        }
399    
400        /**
401         * Sets a condition that <b>any received</b> {@link Exchange} should match the {@link Predicate}
402         *
403         * @param predicate the predicate
404         * @return the builder
405         */
406        public NotifyBuilder whenAnyReceivedMatches(final Predicate predicate) {
407            return doWhenAnyMatches(predicate, true);
408        }
409    
410        /**
411         * Sets a condition that <b>any done</b> {@link Exchange} should match the {@link Predicate}
412         *
413         * @param predicate the predicate
414         * @return the builder
415         */
416        public NotifyBuilder whenAnyDoneMatches(final Predicate predicate) {
417            return doWhenAnyMatches(predicate, false);
418        }
419    
420        private NotifyBuilder doWhenAnyMatches(final Predicate predicate, final boolean received) {
421            stack.push(new EventPredicateSupport() {
422                private boolean matches;
423    
424                @Override
425                public boolean onExchangeCompleted(Exchange exchange) {
426                    if (!received && !matches) {
427                        matches = predicate.matches(exchange);
428                    }
429                    return true;
430                }
431    
432                @Override
433                public boolean onExchangeFailure(Exchange exchange) {
434                    if (!received && !matches) {
435                        matches = predicate.matches(exchange);
436                    }
437                    return true;
438                }
439    
440                @Override
441                public boolean onExchangeCreated(Exchange exchange) {
442                    if (received && !matches) {
443                        matches = predicate.matches(exchange);
444                    }
445                    return true;
446                }
447    
448                public boolean matches() {
449                    return matches;
450                }
451    
452                @Override
453                public String toString() {
454                    if (received) {
455                        return "whenAnyReceivedMatches(" + predicate + ")";
456                    } else {
457                        return "whenAnyDoneMatches(" + predicate + ")";
458                    }
459                }
460            });
461            return this;
462        }
463    
464        /**
465         * Sets a condition that <b>all received</b> {@link Exchange} should match the {@link Predicate}
466         *
467         * @param predicate the predicate
468         * @return the builder
469         */
470        public NotifyBuilder whenAllReceivedMatches(final Predicate predicate) {
471            return doWhenAllMatches(predicate, true);
472        }
473    
474        /**
475         * Sets a condition that <b>all done</b> {@link Exchange} should match the {@link Predicate}
476         *
477         * @param predicate the predicate
478         * @return the builder
479         */
480        public NotifyBuilder whenAllDoneMatches(final Predicate predicate) {
481            return doWhenAllMatches(predicate, false);
482        }
483    
484        private NotifyBuilder doWhenAllMatches(final Predicate predicate, final boolean received) {
485            stack.push(new EventPredicateSupport() {
486                private boolean matches = true;
487    
488                @Override
489                public boolean onExchangeCompleted(Exchange exchange) {
490                    if (!received && matches) {
491                        matches = predicate.matches(exchange);
492                    }
493                    return true;
494                }
495    
496                @Override
497                public boolean onExchangeFailure(Exchange exchange) {
498                    if (!received && matches) {
499                        matches = predicate.matches(exchange);
500                    }
501                    return true;
502                }
503    
504                @Override
505                public boolean onExchangeCreated(Exchange exchange) {
506                    if (received && matches) {
507                        matches = predicate.matches(exchange);
508                    }
509                    return true;
510                }
511    
512                public boolean matches() {
513                    return matches;
514                }
515    
516                @Override
517                public String toString() {
518                    if (received) {
519                        return "whenAllReceivedMatches(" + predicate + ")";
520                    } else {
521                        return "whenAllDoneMatches(" + predicate + ")";
522                    }
523                }
524            });
525            return this;
526        }
527    
528        /**
529         * Sets a condition when the provided mock is satisfied based on {@link Exchange}
530         * being sent to it when they are <b>done</b>.
531         * <p/>
532         * The idea is that you can use Mock for setting fine grained expectations
533         * and then use that together with this builder. The mock provided does <b>NOT</b>
534         * have to already exist in the route. You can just create a new pseudo mock
535         * and this builder will send the done {@link Exchange} to it. So its like
536         * adding the mock to the end of your route(s).
537         *
538         * @param mock the mock
539         * @return the builder
540         */
541        public NotifyBuilder whenDoneSatisfied(final MockEndpoint mock) {
542            return doWhenSatisfied(mock, false);
543        }
544    
545        /**
546         * Sets a condition when the provided mock is satisfied based on {@link Exchange}
547         * being sent to it when they are <b>received</b>.
548         * <p/>
549         * The idea is that you can use Mock for setting fine grained expectations
550         * and then use that together with this builder. The mock provided does <b>NOT</b>
551         * have to already exist in the route. You can just create a new pseudo mock
552         * and this builder will send the done {@link Exchange} to it. So its like
553         * adding the mock to the end of your route(s).
554         *
555         * @param mock the mock
556         * @return the builder
557         */
558        public NotifyBuilder whenReceivedSatisfied(final MockEndpoint mock) {
559            return doWhenSatisfied(mock, true);
560        }
561    
562        private NotifyBuilder doWhenSatisfied(final MockEndpoint mock, final boolean received) {
563            stack.push(new EventPredicateSupport() {
564    
565                private Producer producer;
566    
567                @Override
568                public boolean onExchangeCreated(Exchange exchange) {
569                    if (received) {
570                        sendToMock(exchange);
571                    }
572                    return true;
573                }
574    
575                @Override
576                public boolean onExchangeFailure(Exchange exchange) {
577                    if (!received) {
578                        sendToMock(exchange);
579                    }
580                    return true;
581                }
582    
583                @Override
584                public boolean onExchangeCompleted(Exchange exchange) {
585                    if (!received) {
586                        sendToMock(exchange);
587                    }
588                    return true;
589                }
590    
591                private void sendToMock(Exchange exchange) {
592                    // send the exchange when its completed to the mock
593                    try {
594                        if (producer == null) {
595                            producer = mock.createProducer();
596                        }
597                        producer.process(exchange);
598                    } catch (Exception e) {
599                        throw ObjectHelper.wrapRuntimeCamelException(e);
600                    }
601                }
602    
603                public boolean matches() {
604                    try {
605                        return mock.await(0, TimeUnit.SECONDS);
606                    } catch (InterruptedException e) {
607                        throw ObjectHelper.wrapRuntimeCamelException(e);
608                    }
609                }
610    
611                @Override
612                public String toString() {
613                    if (received) {
614                        return "whenReceivedSatisfied(" + mock + ")";
615                    } else {
616                        return "whenDoneSatisfied(" + mock + ")";
617                    }
618                }
619            });
620            return this;
621        }
622    
623        /**
624         * Sets a condition when the provided mock is <b>not</b> satisfied based on {@link Exchange}
625         * being sent to it when they are <b>received</b>.
626         * <p/>
627         * The idea is that you can use Mock for setting fine grained expectations
628         * and then use that together with this builder. The mock provided does <b>NOT</b>
629         * have to already exist in the route. You can just create a new pseudo mock
630         * and this builder will send the done {@link Exchange} to it. So its like
631         * adding the mock to the end of your route(s).
632         *
633         * @param mock the mock
634         * @return the builder
635         */
636        public NotifyBuilder whenReceivedNotSatisfied(final MockEndpoint mock) {
637            return doWhenNotSatisfied(mock, true);
638        }
639    
640        /**
641         * Sets a condition when the provided mock is <b>not</b> satisfied based on {@link Exchange}
642         * being sent to it when they are <b>done</b>.
643         * <p/>
644         * The idea is that you can use Mock for setting fine grained expectations
645         * and then use that together with this builder. The mock provided does <b>NOT</b>
646         * have to already exist in the route. You can just create a new pseudo mock
647         * and this builder will send the done {@link Exchange} to it. So its like
648         * adding the mock to the end of your route(s).
649         *
650         * @param mock the mock
651         * @return the builder
652         */
653        public NotifyBuilder whenDoneNotSatisfied(final MockEndpoint mock) {
654            return doWhenNotSatisfied(mock, false);
655        }
656    
657        private NotifyBuilder doWhenNotSatisfied(final MockEndpoint mock, final boolean received) {
658            stack.push(new EventPredicateSupport() {
659    
660                private Producer producer;
661    
662                @Override
663                public boolean onExchangeCreated(Exchange exchange) {
664                    if (received) {
665                        sendToMock(exchange);
666                    }
667                    return true;
668                }
669    
670                @Override
671                public boolean onExchangeFailure(Exchange exchange) {
672                    if (!received) {
673                        sendToMock(exchange);
674                    }
675                    return true;
676                }
677    
678                @Override
679                public boolean onExchangeCompleted(Exchange exchange) {
680                    if (!received) {
681                        sendToMock(exchange);
682                    }
683                    return true;
684                }
685    
686                private void sendToMock(Exchange exchange) {
687                    // send the exchange when its completed to the mock
688                    try {
689                        if (producer == null) {
690                            producer = mock.createProducer();
691                        }
692                        producer.process(exchange);
693                    } catch (Exception e) {
694                        throw ObjectHelper.wrapRuntimeCamelException(e);
695                    }
696                }
697    
698                public boolean matches() {
699                    try {
700                        return !mock.await(0, TimeUnit.SECONDS);
701                    } catch (InterruptedException e) {
702                        throw ObjectHelper.wrapRuntimeCamelException(e);
703                    }
704                }
705    
706                @Override
707                public String toString() {
708                    if (received) {
709                        return "whenReceivedNotSatisfied(" + mock + ")";
710                    } else {
711                        return "whenDoneNotSatisfied(" + mock + ")";
712                    }
713                }
714            });
715            return this;
716        }
717    
718        /**
719         * Sets a condition that the bodies is expected to be <b>received</b> in the order as well.
720         * <p/>
721         * This condition will discard any additional messages. If you need a more strict condition
722         * then use {@link #whenExactBodiesReceived(Object...)}
723         *
724         * @param bodies the expected bodies
725         * @return the builder
726         * @see #whenExactBodiesReceived(Object...)
727         */
728        public NotifyBuilder whenBodiesReceived(Object... bodies) {
729            List<Object> bodyList = new ArrayList<Object>();
730            bodyList.addAll(Arrays.asList(bodies));
731            return doWhenBodies(bodyList, true, false);
732        }
733    
734        /**
735         * Sets a condition that the bodies is expected to be <b>done</b> in the order as well.
736         * <p/>
737         * This condition will discard any additional messages. If you need a more strict condition
738         * then use {@link #whenExactBodiesDone(Object...)}
739         *
740         * @param bodies the expected bodies
741         * @return the builder
742         * @see #whenExactBodiesDone(Object...)
743         */
744        public NotifyBuilder whenBodiesDone(Object... bodies) {
745            List<Object> bodyList = new ArrayList<Object>();
746            bodyList.addAll(Arrays.asList(bodies));
747            return doWhenBodies(bodyList, false, false);
748        }
749    
750        /**
751         * Sets a condition that the bodies is expected to be <b>received</b> in the order as well.
752         * <p/>
753         * This condition is strict which means that it only expect that exact number of bodies
754         *
755         * @param bodies the expected bodies
756         * @return the builder
757         * @see #whenBodiesReceived(Object...)
758         */
759        public NotifyBuilder whenExactBodiesReceived(Object... bodies) {
760            List<Object> bodyList = new ArrayList<Object>();
761            bodyList.addAll(Arrays.asList(bodies));
762            return doWhenBodies(bodyList, true, true);
763        }
764    
765        /**
766         * Sets a condition that the bodies is expected to be <b>done</b> in the order as well.
767         * <p/>
768         * This condition is strict which means that it only expect that exact number of bodies
769         *
770         * @param bodies the expected bodies
771         * @return the builder
772         * @see #whenExactBodiesDone(Object...)
773         */
774        public NotifyBuilder whenExactBodiesDone(Object... bodies) {
775            List<Object> bodyList = new ArrayList<Object>();
776            bodyList.addAll(Arrays.asList(bodies));
777            return doWhenBodies(bodyList, false, true);
778        }
779    
780        private NotifyBuilder doWhenBodies(final List bodies, final boolean received, final boolean exact) {
781            stack.push(new EventPredicateSupport() {
782                private boolean matches;
783                private int current;
784    
785                @Override
786                public boolean onExchangeCreated(Exchange exchange) {
787                    if (received) {
788                        matchBody(exchange);
789                    }
790                    return true;
791                }
792    
793                @Override
794                public boolean onExchangeFailure(Exchange exchange) {
795                    if (!received) {
796                        matchBody(exchange);
797                    }
798                    return true;
799                }
800    
801                @Override
802                public boolean onExchangeCompleted(Exchange exchange) {
803                    if (!received) {
804                        matchBody(exchange);
805                    }
806                    return true;
807                }
808    
809                private void matchBody(Exchange exchange) {
810                    current++;
811    
812                    if (current > bodies.size()) {
813                        // out of bounds
814                        return;
815                    }
816    
817                    Object actual = exchange.getIn().getBody();
818                    Object expected = bodies.get(current - 1);
819                    matches = ObjectHelper.equal(expected, actual);
820                }
821    
822                public boolean matches() {
823                    if (exact) {
824                        return matches && current == bodies.size();
825                    } else {
826                        return matches && current >= bodies.size();
827                    }
828                }
829    
830                @Override
831                public String toString() {
832                    if (received) {
833                        return "" + (exact ? "whenExactBodiesReceived(" : "whenBodiesReceived(") + bodies + ")";
834                    } else {
835                        return "" + (exact ? "whenExactBodiesDone(" : "whenBodiesDone(") + bodies + ")";
836                    }
837                }
838            });
839            return this;
840        }
841    
842    
843        /**
844         * Prepares to append an additional expression using the <i>and</i> operator.
845         *
846         * @return the builder
847         */
848        public NotifyBuilder and() {
849            doCreate(EventOperation.and);
850            return this;
851        }
852    
853        /**
854         * Prepares to append an additional expression using the <i>or</i> operator.
855         *
856         * @return the builder
857         */
858        public NotifyBuilder or() {
859            doCreate(EventOperation.or);
860            return this;
861        }
862    
863        /**
864         * Prepares to append an additional expression using the <i>not</i> operator.
865         *
866         * @return the builder
867         */
868        public NotifyBuilder not() {
869            doCreate(EventOperation.not);
870            return this;
871        }
872    
873        /**
874         * Creates the expression this builder should use for matching.
875         * <p/>
876         * You must call this method when you are finished building the expressions.
877         *
878         * @return the created builder ready for matching
879         */
880        public NotifyBuilder create() {
881            doCreate(EventOperation.and);
882            return this;
883        }
884    
885        /**
886         * Does all the expression match?
887         * <p/>
888         * This operation will return immediately which means it can be used for testing at this very moment.
889         *
890         * @return <tt>true</tt> if matching, <tt>false</tt> otherwise
891         */
892        public boolean matches() {
893            return matches;
894        }
895    
896        /**
897         * Does all the expression match?
898         * <p/>
899         * This operation will wait until the match is <tt>true</tt> or otherwise a timeout occur
900         * which means <tt>false</tt> will be returned.
901         *
902         * @param timeout  the timeout value
903         * @param timeUnit the time unit
904         * @return <tt>true</tt> if matching, <tt>false</tt> otherwise due to timeout
905         */
906        public boolean matches(long timeout, TimeUnit timeUnit) {
907            try {
908                latch.await(timeout, timeUnit);
909            } catch (InterruptedException e) {
910                throw ObjectHelper.wrapRuntimeCamelException(e);
911            }
912            return matches();
913        }
914    
915        @Override
916        public String toString() {
917            StringBuilder sb = new StringBuilder();
918            for (Iterator<EventPredicateHolder> it = predicates.iterator(); it.hasNext();) {
919                if (sb.length() > 0) {
920                    sb.append(".");
921                }
922                sb.append(it.next().toString());
923            }
924            // a crude way of skipping the first invisible operation
925            return ObjectHelper.after(sb.toString(), "().");
926        }
927    
928        private void doCreate(EventOperation newOperation) {
929            // init operation depending on the newOperation
930            if (operation == null) {
931                // if the first new operation is an or then this operation must be an or as well
932                // otherwise it should be and based
933                operation = newOperation == EventOperation.or ? EventOperation.or : EventOperation.and;
934            }
935    
936            // we have some
937            if (!stack.isEmpty()) {
938                CompoundEventPredicate compound = new CompoundEventPredicate(stack);
939                stack.clear();
940                predicates.add(new EventPredicateHolder(operation, compound));
941            }
942    
943            operation = newOperation;
944        }
945    
946        /**
947         * Notifier which hooks into Camel to listen for {@link Exchange} relevant events for this builder
948         */
949        private final class ExchangeNotifier extends EventNotifierSupport {
950    
951            public void notify(EventObject event) throws Exception {
952                if (event instanceof ExchangeCreatedEvent) {
953                    onExchangeCreated((ExchangeCreatedEvent) event);
954                } else if (event instanceof ExchangeCompletedEvent) {
955                    onExchangeCompleted((ExchangeCompletedEvent) event);
956                } else if (event instanceof ExchangeFailureEvent) {
957                    onExchangeFailure((ExchangeFailureEvent) event);
958                }
959    
960                // now compute whether we matched
961                computeMatches();
962            }
963    
964            public boolean isEnabled(EventObject event) {
965                return true;
966            }
967    
968            private void onExchangeCreated(ExchangeCreatedEvent event) {
969                for (EventPredicateHolder predicate : predicates) {
970                    predicate.getPredicate().onExchangeCreated(event.getExchange());
971                }
972            }
973    
974            private void onExchangeCompleted(ExchangeCompletedEvent event) {
975                for (EventPredicateHolder predicate : predicates) {
976                    predicate.getPredicate().onExchangeCompleted(event.getExchange());
977                }
978            }
979    
980            private void onExchangeFailure(ExchangeFailureEvent event) {
981                for (EventPredicateHolder predicate : predicates) {
982                    predicate.getPredicate().onExchangeFailure(event.getExchange());
983                }
984            }
985    
986            private synchronized void computeMatches() {
987                // use a temporary answer until we have computed the value to assign
988                Boolean answer = null;
989    
990                for (EventPredicateHolder holder : predicates) {
991                    EventOperation operation = holder.getOperation();
992                    if (EventOperation.and == operation) {
993                        if (holder.getPredicate().matches()) {
994                            answer = true;
995                        } else {
996                            answer = false;
997                            // and break out since its an AND so it must match
998                            break;
999                        }
1000                    } else if (EventOperation.or == operation) {
1001                        if (holder.getPredicate().matches()) {
1002                            answer = true;
1003                        }
1004                    } else if (EventOperation.not == operation) {
1005                        if (holder.getPredicate().matches()) {
1006                            answer = false;
1007                            // and break out since its a NOT so it must not match
1008                            break;
1009                        } else {
1010                            answer = true;
1011                        }
1012                    }
1013                }
1014    
1015                // if we did compute a value then assign that
1016                if (answer != null) {
1017                    matches = answer;
1018                    if (matches) {
1019                        // signal completion
1020                        latch.countDown();
1021                    }
1022                }
1023            }
1024    
1025            @Override
1026            protected void doStart() throws Exception {
1027                // we only care about Exchange events
1028                setIgnoreCamelContextEvents(true);
1029                setIgnoreRouteEvents(true);
1030                setIgnoreServiceEvents(true);
1031            }
1032    
1033            @Override
1034            protected void doStop() throws Exception {
1035            }
1036        }
1037    
1038        private enum EventOperation {
1039            and, or, not;
1040        }
1041    
1042        private interface EventPredicate {
1043    
1044            /**
1045             * Evaluates whether the predicate matched or not.
1046             *
1047             * @return <tt>true</tt> if matched, <tt>false</tt> otherwise
1048             */
1049            boolean matches();
1050    
1051            /**
1052             * Callback for {@link Exchange} lifecycle
1053             *
1054             * @param exchange the exchange
1055             * @return <tt>true</tt> to allow continue evaluating, <tt>false</tt> to stop immediately
1056             */
1057            boolean onExchangeCreated(Exchange exchange);
1058    
1059            /**
1060             * Callback for {@link Exchange} lifecycle
1061             *
1062             * @param exchange the exchange
1063             * @return <tt>true</tt> to allow continue evaluating, <tt>false</tt> to stop immediately
1064             */
1065            boolean onExchangeCompleted(Exchange exchange);
1066    
1067            /**
1068             * Callback for {@link Exchange} lifecycle
1069             *
1070             * @param exchange the exchange
1071             * @return <tt>true</tt> to allow continue evaluating, <tt>false</tt> to stop immediately
1072             */
1073            boolean onExchangeFailure(Exchange exchange);
1074        }
1075    
1076        private abstract class EventPredicateSupport implements EventPredicate {
1077    
1078            public boolean onExchangeCreated(Exchange exchange) {
1079                return onExchange(exchange);
1080            }
1081    
1082            public boolean onExchangeCompleted(Exchange exchange) {
1083                return onExchange(exchange);
1084            }
1085    
1086            public boolean onExchangeFailure(Exchange exchange) {
1087                return onExchange(exchange);
1088            }
1089    
1090            public boolean onExchange(Exchange exchange) {
1091                return true;
1092            }
1093        }
1094    
1095        /**
1096         * To hold an operation and predicate
1097         */
1098        private final class EventPredicateHolder {
1099            private final EventOperation operation;
1100            private final EventPredicate predicate;
1101    
1102            private EventPredicateHolder(EventOperation operation, EventPredicate predicate) {
1103                this.operation = operation;
1104                this.predicate = predicate;
1105            }
1106    
1107            public EventOperation getOperation() {
1108                return operation;
1109            }
1110    
1111            public EventPredicate getPredicate() {
1112                return predicate;
1113            }
1114    
1115            @Override
1116            public String toString() {
1117                return operation.name() + "()." + predicate;
1118            }
1119        }
1120    
1121        /**
1122         * To hold multiple predicates which are part of same expression
1123         */
1124        private final class CompoundEventPredicate implements EventPredicate {
1125    
1126            private List<EventPredicate> predicates = new ArrayList<EventPredicate>();
1127    
1128            private CompoundEventPredicate(Stack<EventPredicate> predicates) {
1129                this.predicates.addAll(predicates);
1130            }
1131    
1132            public boolean matches() {
1133                for (EventPredicate predicate : predicates) {
1134                    if (!predicate.matches()) {
1135                        return false;
1136                    }
1137                }
1138                return true;
1139            }
1140    
1141            public boolean onExchangeCreated(Exchange exchange) {
1142                for (EventPredicate predicate : predicates) {
1143                    if (!predicate.onExchangeCreated(exchange)) {
1144                        return false;
1145                    }
1146                }
1147                return true;
1148            }
1149    
1150            public boolean onExchangeCompleted(Exchange exchange) {
1151                for (EventPredicate predicate : predicates) {
1152                    if (!predicate.onExchangeCompleted(exchange)) {
1153                        return false;
1154                    }
1155                }
1156                return true;
1157            }
1158    
1159            public boolean onExchangeFailure(Exchange exchange) {
1160                for (EventPredicate predicate : predicates) {
1161                    if (!predicate.onExchangeFailure(exchange)) {
1162                        return false;
1163                    }
1164                }
1165                return true;
1166            }
1167    
1168            @Override
1169            public String toString() {
1170                StringBuilder sb = new StringBuilder();
1171                for (Iterator<EventPredicate> it = predicates.iterator(); it.hasNext();) {
1172                    if (sb.length() > 0) {
1173                        sb.append(".");
1174                    }
1175                    sb.append(it.next().toString());
1176                }
1177                return sb.toString();
1178            }
1179        }
1180    
1181    }