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.component.mock;
018    
019    import java.io.File;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.Collection;
023    import java.util.HashMap;
024    import java.util.HashSet;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Set;
028    import java.util.concurrent.CopyOnWriteArrayList;
029    import java.util.concurrent.CountDownLatch;
030    import java.util.concurrent.TimeUnit;
031    
032    import org.apache.camel.CamelContext;
033    import org.apache.camel.Component;
034    import org.apache.camel.Consumer;
035    import org.apache.camel.Endpoint;
036    import org.apache.camel.Exchange;
037    import org.apache.camel.Expression;
038    import org.apache.camel.Message;
039    import org.apache.camel.Predicate;
040    import org.apache.camel.Processor;
041    import org.apache.camel.Producer;
042    import org.apache.camel.builder.ExpressionClause;
043    import org.apache.camel.impl.DefaultEndpoint;
044    import org.apache.camel.impl.DefaultProducer;
045    import org.apache.camel.spi.BrowsableEndpoint;
046    import org.apache.camel.util.CamelContextHelper;
047    import org.apache.camel.util.ExpressionComparator;
048    import org.apache.camel.util.FileUtil;
049    import org.apache.camel.util.ObjectHelper;
050    import org.apache.commons.logging.Log;
051    import org.apache.commons.logging.LogFactory;
052    
053    /**
054     * A Mock endpoint which provides a literate, fluent API for testing routes
055     * using a <a href="http://jmock.org/">JMock style</a> API.
056     *
057     * @version $Revision: 906378 $
058     */
059    public class MockEndpoint extends DefaultEndpoint implements BrowsableEndpoint {
060        private static final transient Log LOG = LogFactory.getLog(MockEndpoint.class);
061        private int expectedCount;
062        private int counter;
063        private Processor defaultProcessor;
064        private Map<Integer, Processor> processors;
065        private List<Exchange> receivedExchanges;
066        private List<Throwable> failures;
067        private List<Runnable> tests;
068        private CountDownLatch latch;
069        private long sleepForEmptyTest;
070        private long resultWaitTime;
071        private long resultMinimumWaitTime;
072        private int expectedMinimumCount;
073        private List<Object> expectedBodyValues;
074        private List<Object> actualBodyValues;
075        private String headerName;
076        private Object headerValue;
077        private Object actualHeader;
078        private String propertyName;
079        private Object propertyValue;
080        private Object actualProperty;
081        private Processor reporter;
082    
083        public MockEndpoint(String endpointUri, Component component) {
084            super(endpointUri, component);
085            init();
086        }
087    
088        public MockEndpoint(String endpointUri) {
089            super(endpointUri);
090            init();
091        }
092    
093        public MockEndpoint() {
094            this(null);
095        }
096    
097        /**
098         * A helper method to resolve the mock endpoint of the given URI on the given context
099         *
100         * @param context the camel context to try resolve the mock endpoint from
101         * @param uri the uri of the endpoint to resolve
102         * @return the endpoint
103         */
104        public static MockEndpoint resolve(CamelContext context, String uri) {
105            return CamelContextHelper.getMandatoryEndpoint(context, uri, MockEndpoint.class);
106        }
107    
108        public static void assertWait(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
109            long start = System.currentTimeMillis();
110            long left = unit.toMillis(timeout);
111            long end = start + left;
112            for (MockEndpoint endpoint : endpoints) {
113                if (!endpoint.await(left, TimeUnit.MILLISECONDS)) {
114                    throw new AssertionError("Timeout waiting for endpoints to receive enough messages. " + endpoint.getEndpointUri() + " timed out.");
115                }
116                left = end - System.currentTimeMillis();
117                if (left <= 0) {
118                    left = 0;
119                }
120            }
121        }
122    
123        public static void assertIsSatisfied(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
124            assertWait(timeout, unit, endpoints);
125            for (MockEndpoint endpoint : endpoints) {
126                endpoint.assertIsSatisfied();
127            }
128        }
129    
130        public static void assertIsSatisfied(MockEndpoint... endpoints) throws InterruptedException {
131            for (MockEndpoint endpoint : endpoints) {
132                endpoint.assertIsSatisfied();
133            }
134        }
135    
136    
137        /**
138         * Asserts that all the expectations on any {@link MockEndpoint} instances registered
139         * in the given context are valid
140         *
141         * @param context the camel context used to find all the available endpoints to be asserted
142         */
143        public static void assertIsSatisfied(CamelContext context) throws InterruptedException {
144            ObjectHelper.notNull(context, "camelContext");
145            Collection<Endpoint> endpoints = context.getEndpoints();
146            for (Endpoint endpoint : endpoints) {
147                if (endpoint instanceof MockEndpoint) {
148                    MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
149                    mockEndpoint.assertIsSatisfied();
150                }
151            }
152        }
153    
154        public static void expectsMessageCount(int count, MockEndpoint... endpoints) throws InterruptedException {
155            for (MockEndpoint endpoint : endpoints) {
156                endpoint.setExpectedMessageCount(count);
157            }
158        }
159    
160        public List<Exchange> getExchanges() {
161            return getReceivedExchanges();
162        }
163    
164        public Consumer createConsumer(Processor processor) throws Exception {
165            throw new UnsupportedOperationException("You cannot consume from this endpoint");
166        }
167    
168        public Producer createProducer() throws Exception {
169            return new DefaultProducer(this) {
170                public void process(Exchange exchange) {
171                    onExchange(exchange);
172                }
173            };
174        }
175    
176        public void reset() {
177            init();
178        }
179    
180    
181        // Testing API
182        // -------------------------------------------------------------------------
183    
184        /**
185         * Set the processor that will be invoked when the index
186         * message is received.
187         */
188        public void whenExchangeReceived(int index, Processor processor) {
189            this.processors.put(index, processor);
190        }
191    
192        /**
193         * Set the processor that will be invoked when the some message
194         * is received.
195         *
196         * This processor could be overwritten by
197         * {@link #whenExchangeReceived(int, Processor)} method.
198         */
199        public void whenAnyExchangeReceived(Processor processor) {
200            this.defaultProcessor = processor;
201        }
202    
203        /**
204         * Validates that all the available expectations on this endpoint are
205         * satisfied; or throw an exception
206         */
207        public void assertIsSatisfied() throws InterruptedException {
208            assertIsSatisfied(sleepForEmptyTest);
209        }
210    
211        /**
212         * Validates that all the available expectations on this endpoint are
213         * satisfied; or throw an exception
214         *
215         * @param timeoutForEmptyEndpoints the timeout in milliseconds that we
216         *                should wait for the test to be true
217         */
218        public void assertIsSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
219            LOG.info("Asserting: " + this + " is satisfied");
220            if (expectedCount == 0) {
221                if (timeoutForEmptyEndpoints > 0) {
222                    LOG.debug("Sleeping for: " + timeoutForEmptyEndpoints + " millis to check there really are no messages received");
223                    Thread.sleep(timeoutForEmptyEndpoints);
224                }
225                assertEquals("Received message count", expectedCount, getReceivedCounter());
226            } else if (expectedCount > 0) {
227                if (expectedCount != getReceivedCounter()) {
228                    waitForCompleteLatch();
229                }
230                assertEquals("Received message count", expectedCount, getReceivedCounter());
231            } else if (expectedMinimumCount > 0 && getReceivedCounter() < expectedMinimumCount) {
232                waitForCompleteLatch();
233            }
234    
235            if (expectedMinimumCount >= 0) {
236                int receivedCounter = getReceivedCounter();
237                assertTrue("Received message count " + receivedCounter + ", expected at least " + expectedMinimumCount, expectedMinimumCount <= receivedCounter);
238            }
239    
240            for (Runnable test : tests) {
241                test.run();
242            }
243    
244            for (Throwable failure : failures) {
245                if (failure != null) {
246                    LOG.error("Caught on " + getEndpointUri() + " Exception: " + failure, failure);
247                    fail("Failed due to caught exception: " + failure);
248                }
249            }
250        }
251    
252        /**
253         * Validates that the assertions fail on this endpoint
254         */
255        public void assertIsNotSatisfied() throws InterruptedException {
256            boolean failed = false;
257            try {
258                assertIsSatisfied();
259                // did not throw expected error... fail!
260                failed = true;
261            } catch (AssertionError e) {
262                LOG.info("Caught expected failure: " + e);
263            }
264            if (failed) {
265                // fail() throws the AssertionError to indicate the test failed. 
266                fail("Expected assertion failure but test succeeded!");
267            }
268        }
269    
270        /**
271         * Validates that the assertions fail on this endpoint
272    
273         * @param timeoutForEmptyEndpoints the timeout in milliseconds that we
274         *        should wait for the test to be true
275         */
276        public void assertIsNotSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
277            boolean failed = false;
278            try {
279                assertIsSatisfied(timeoutForEmptyEndpoints);
280                // did not throw expected error... fail!
281                failed = true;
282            } catch (AssertionError e) {
283                LOG.info("Caught expected failure: " + e);
284            }
285            if (failed) { 
286                // fail() throws the AssertionError to indicate the test failed. 
287                fail("Expected assertion failure but test succeeded!");
288            }
289        }    
290        
291        /**
292         * Specifies the expected number of message exchanges that should be
293         * received by this endpoint
294         *
295         * @param expectedCount the number of message exchanges that should be
296         *                expected by this endpoint
297         */
298        public void expectedMessageCount(int expectedCount) {
299            setExpectedMessageCount(expectedCount);
300        }
301    
302        /**
303         * Specifies the minimum number of expected message exchanges that should be
304         * received by this endpoint
305         *
306         * @param expectedCount the number of message exchanges that should be
307         *                expected by this endpoint
308         */
309        public void expectedMinimumMessageCount(int expectedCount) {
310            setMinimumExpectedMessageCount(expectedCount);
311        }
312    
313        /**
314         * Sets an expectation that the given header name & value are received by this endpoint
315         */
316        public void expectedHeaderReceived(final String name, final Object value) {
317            this.headerName = name;
318            this.headerValue = value;
319    
320            expects(new Runnable() {
321                public void run() {
322                    assertTrue("No header with name " + headerName + " found.", actualHeader != null);
323    
324                    Object actualValue;
325                    if (actualHeader instanceof Expression) {
326                        actualValue = ((Expression)actualHeader).evaluate(mostRecentExchange(), headerValue.getClass());
327                    } else if (actualHeader instanceof Predicate) {
328                        actualValue = ((Predicate)actualHeader).matches(mostRecentExchange());
329                    } else {                    
330                        actualValue = getCamelContext().getTypeConverter().convertTo(headerValue.getClass(), actualHeader);
331                        assertTrue("There is no type conversion possible from " + actualHeader.getClass().getName() 
332                                + " to " + headerValue.getClass().getName(), actualValue != null);
333                    }
334                    assertEquals("Header with name " + headerName, headerValue, actualValue);
335                }
336            });
337        }
338    
339        private Exchange mostRecentExchange() {
340            return receivedExchanges.get(receivedExchanges.size() - 1);
341        }
342        
343        /**
344         * Sets an expectation that the given property name & value are received by this endpoint
345         */
346        public void expectedPropertyReceived(final String name, final Object value) {
347            this.propertyName = name;
348            this.propertyValue = value;
349    
350            expects(new Runnable() {
351                public void run() {
352                    assertTrue("No property with name " + propertyName + " found.", actualProperty != null);
353    
354                    Object actualValue = getCamelContext().getTypeConverter().convertTo(actualProperty.getClass(), propertyValue);
355                    assertEquals("Property with name " + propertyName, actualValue, actualProperty);
356                }
357            });
358        }
359    
360        /**
361         * Adds an expectation that the given body values are received by this
362         * endpoint in the specified order
363         */
364        @SuppressWarnings("unchecked")
365        public void expectedBodiesReceived(final List bodies) {
366            expectedMessageCount(bodies.size());
367            this.expectedBodyValues = bodies;
368            this.actualBodyValues = new ArrayList<Object>();
369    
370            expects(new Runnable() {
371                public void run() {
372                    for (int i = 0; i < expectedBodyValues.size(); i++) {
373                        Exchange exchange = getReceivedExchanges().get(i);
374                        assertTrue("No exchange received for counter: " + i, exchange != null);
375    
376                        Object expectedBody = expectedBodyValues.get(i);
377                        Object actualBody = null;
378                        if (i < actualBodyValues.size()) {
379                            actualBody = actualBodyValues.get(i);
380                        }
381    
382                        // TODO: coerce types before assertEquals
383                        assertEquals("Body of message: " + i, expectedBody, actualBody);
384                    }
385                }
386            });
387        }
388    
389        /**
390         * Sets an expectation that the given body values are received by this endpoint
391         */
392        public void expectedBodiesReceived(Object... bodies) {
393            List<Object> bodyList = new ArrayList<Object>();
394            bodyList.addAll(Arrays.asList(bodies));
395            expectedBodiesReceived(bodyList);
396        }
397    
398        /**
399         * Adds an expectation that the given body value are received by this endpoint
400         */
401        public ExpressionClause<?> expectedBodyReceived() {
402            final ExpressionClause<?> clause = new ExpressionClause<MockEndpoint>(this);
403    
404            expectedMessageCount(1);
405    
406            expects(new Runnable() {
407                public void run() {
408                    Exchange exchange = getReceivedExchanges().get(0);
409                    assertTrue("No exchange received for counter: " + 0, exchange != null);
410    
411                    Object actualBody = exchange.getIn().getBody();
412                    Object expectedBody = clause.evaluate(exchange, Object.class);
413    
414                    assertEquals("Body of message: " + 0, expectedBody, actualBody);
415                }
416            });
417    
418            return clause;
419        }
420    
421        /**
422         * Adds an expectation that the given body values are received by this
423         * endpoint in any order
424         */
425        @SuppressWarnings("unchecked")
426        public void expectedBodiesReceivedInAnyOrder(final List bodies) {
427            expectedMessageCount(bodies.size());
428            this.expectedBodyValues = bodies;
429            this.actualBodyValues = new ArrayList<Object>();
430    
431            expects(new Runnable() {
432                public void run() {
433                    Set<Object> actualBodyValuesSet = new HashSet<Object>(actualBodyValues);
434                    for (int i = 0; i < expectedBodyValues.size(); i++) {
435                        Exchange exchange = getReceivedExchanges().get(i);
436                        assertTrue("No exchange received for counter: " + i, exchange != null);
437    
438                        Object expectedBody = expectedBodyValues.get(i);
439                        assertTrue("Message with body " + expectedBody
440                                + " was expected but not found in " + actualBodyValuesSet,
441                                actualBodyValuesSet.remove(expectedBody));
442                    }
443                }
444            });
445        }
446    
447        /**
448         * Adds an expectation that the given body values are received by this
449         * endpoint in any order
450         */
451        public void expectedBodiesReceivedInAnyOrder(Object... bodies) {
452            List<Object> bodyList = new ArrayList<Object>();
453            bodyList.addAll(Arrays.asList(bodies));
454            expectedBodiesReceivedInAnyOrder(bodyList);
455        }
456    
457        /**
458         * Adds an expectation that a file exists with the given name
459         *
460         * @param name name of file, will cater for / and \ on different OS platforms
461         */
462        public void expectedFileExists(final String name) {
463            expectedFileExists(name, null);
464        }
465    
466        /**
467         * Adds an expectation that a file exists with the given name
468         * <p/>
469         * Will wait at most 5 seconds while checking for the existence of the file.
470         *
471         * @param name name of file, will cater for / and \ on different OS platforms
472         * @param content content of file to compare, can be <tt>null</tt> to not compare content
473         */
474        public void expectedFileExists(final String name, final String content) {
475            final File file = new File(FileUtil.normalizePath(name)).getAbsoluteFile();
476    
477            expects(new Runnable() {
478                public void run() {
479                    // wait at most 5 seconds for the file to exists
480                    final long timeout = System.currentTimeMillis() + 5000;
481    
482                    boolean stop = false;
483                    while (!stop && !file.exists()) {
484                        try {
485                            Thread.sleep(50);
486                        } catch (InterruptedException e) {
487                            // ignore
488                        }
489                        stop = System.currentTimeMillis() > timeout;
490                    }
491    
492                    assertTrue("The file should exists: " + name, file.exists());
493    
494                    if (content != null) {
495                        String body = getCamelContext().getTypeConverter().convertTo(String.class, file);
496                        assertEquals("Content of file: " + name, content, body);
497                    }
498                }
499            });
500        }
501    
502        /**
503         * Adds an expectation that messages received should have ascending values
504         * of the given expression such as a user generated counter value
505         */
506        public void expectsAscending(final Expression expression) {
507            expects(new Runnable() {
508                public void run() {
509                    assertMessagesAscending(expression);
510                }
511            });
512        }
513    
514        /**
515         * Adds an expectation that messages received should have ascending values
516         * of the given expression such as a user generated counter value
517         */
518        public ExpressionClause<?> expectsAscending() {
519            final ExpressionClause<?> clause = new ExpressionClause<MockEndpoint>(this);
520            expects(new Runnable() {
521                public void run() {
522                    assertMessagesAscending(clause.getExpressionValue());
523                }
524            });
525            return clause;
526        }
527    
528        /**
529         * Adds an expectation that messages received should have descending values
530         * of the given expression such as a user generated counter value
531         */
532        public void expectsDescending(final Expression expression) {
533            expects(new Runnable() {
534                public void run() {
535                    assertMessagesDescending(expression);
536                }
537            });
538        }
539    
540        /**
541         * Adds an expectation that messages received should have descending values
542         * of the given expression such as a user generated counter value
543         */
544        public ExpressionClause<?> expectsDescending() {
545            final ExpressionClause<?> clause = new ExpressionClause<MockEndpoint>(this);
546            expects(new Runnable() {
547                public void run() {
548                    assertMessagesDescending(clause.getExpressionValue());
549                }
550            });
551            return clause;
552        }
553    
554        /**
555         * Adds an expectation that no duplicate messages should be received using
556         * the expression to determine the message ID
557         *
558         * @param expression the expression used to create a unique message ID for
559         *                message comparison (which could just be the message
560         *                payload if the payload can be tested for uniqueness using
561         *                {@link Object#equals(Object)} and
562         *                {@link Object#hashCode()}
563         */
564        public void expectsNoDuplicates(final Expression expression) {
565            expects(new Runnable() {
566                public void run() {
567                    assertNoDuplicates(expression);
568                }
569            });
570        }
571    
572        /**
573         * Adds an expectation that no duplicate messages should be received using
574         * the expression to determine the message ID
575         */
576        public ExpressionClause<?> expectsNoDuplicates() {
577            final ExpressionClause<?> clause = new ExpressionClause<MockEndpoint>(this);
578            expects(new Runnable() {
579                public void run() {
580                    assertNoDuplicates(clause.getExpressionValue());
581                }
582            });
583            return clause;
584        }
585    
586        /**
587         * Asserts that the messages have ascending values of the given expression
588         */
589        public void assertMessagesAscending(Expression expression) {
590            assertMessagesSorted(expression, true);
591        }
592    
593        /**
594         * Asserts that the messages have descending values of the given expression
595         */
596        public void assertMessagesDescending(Expression expression) {
597            assertMessagesSorted(expression, false);
598        }
599    
600        protected void assertMessagesSorted(Expression expression, boolean ascending) {
601            String type = ascending ? "ascending" : "descending";
602            ExpressionComparator comparator = new ExpressionComparator(expression);
603            List<Exchange> list = getReceivedExchanges();
604            for (int i = 1; i < list.size(); i++) {
605                int j = i - 1;
606                Exchange e1 = list.get(j);
607                Exchange e2 = list.get(i);
608                int result = comparator.compare(e1, e2);
609                if (result == 0) {
610                    fail("Messages not " + type + ". Messages" + j + " and " + i + " are equal with value: "
611                        + expression.evaluate(e1, Object.class) + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2);
612                } else {
613                    if (!ascending) {
614                        result = result * -1;
615                    }
616                    if (result > 0) {
617                        fail("Messages not " + type + ". Message " + j + " has value: " + expression.evaluate(e1, Object.class)
618                            + " and message " + i + " has value: " + expression.evaluate(e2, Object.class) + " for expression: "
619                            + expression + ". Exchanges: " + e1 + " and " + e2);
620                    }
621                }
622            }
623        }
624    
625        public void assertNoDuplicates(Expression expression) {
626            Map<Object, Exchange> map = new HashMap<Object, Exchange>();
627            List<Exchange> list = getReceivedExchanges();
628            for (int i = 0; i < list.size(); i++) {
629                Exchange e2 = list.get(i);
630                Object key = expression.evaluate(e2, Object.class);
631                Exchange e1 = map.get(key);
632                if (e1 != null) {
633                    fail("Duplicate message found on message " + i + " has value: " + key + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2);
634                } else {
635                    map.put(key, e2);
636                }
637            }
638        }
639    
640        /**
641         * Adds the expectation which will be invoked when enough messages are received
642         */
643        public void expects(Runnable runnable) {
644            tests.add(runnable);
645        }
646    
647        /**
648         * Adds an assertion to the given message index
649         *
650         * @param messageIndex the number of the message
651         * @return the assertion clause
652         */
653        public AssertionClause message(final int messageIndex) {
654            final AssertionClause clause = new AssertionClause() {
655                public void run() {
656                    applyAssertionOn(MockEndpoint.this, messageIndex, assertExchangeReceived(messageIndex));
657                }
658            };
659            expects(clause);
660            return clause;
661        }
662    
663        /**
664         * Adds an assertion to all the received messages
665         *
666         * @return the assertion clause
667         */
668        public AssertionClause allMessages() {
669            final AssertionClause clause = new AssertionClause() {
670                public void run() {
671                    List<Exchange> list = getReceivedExchanges();
672                    int index = 0;
673                    for (Exchange exchange : list) {
674                        applyAssertionOn(MockEndpoint.this, index++, exchange);
675                    }
676                }
677            };
678            expects(clause);
679            return clause;
680        }
681    
682        /**
683         * Asserts that the given index of message is received (starting at zero)
684         */
685        public Exchange assertExchangeReceived(int index) {
686            int count = getReceivedCounter();
687            assertTrue("Not enough messages received. Was: " + count, count > index);
688            return getReceivedExchanges().get(index);
689        }
690    
691        // Properties
692        // -------------------------------------------------------------------------
693        public List<Throwable> getFailures() {
694            return failures;
695        }
696    
697        public int getReceivedCounter() {
698            return receivedExchanges.size();
699        }
700    
701        public List<Exchange> getReceivedExchanges() {
702            return receivedExchanges;
703        }
704    
705        public int getExpectedCount() {
706            return expectedCount;
707        }
708    
709        public long getSleepForEmptyTest() {
710            return sleepForEmptyTest;
711        }
712    
713        /**
714         * Allows a sleep to be specified to wait to check that this endpoint really
715         * is empty when {@link #expectedMessageCount(int)} is called with zero
716         *
717         * @param sleepForEmptyTest the milliseconds to sleep for to determine that
718         *                this endpoint really is empty
719         */
720        public void setSleepForEmptyTest(long sleepForEmptyTest) {
721            this.sleepForEmptyTest = sleepForEmptyTest;
722        }
723    
724        public long getResultWaitTime() {
725            return resultWaitTime;
726        }
727    
728        /**
729         * Sets the maximum amount of time (in millis) the {@link #assertIsSatisfied()} will
730         * wait on a latch until it is satisfied
731         */
732        public void setResultWaitTime(long resultWaitTime) {
733            this.resultWaitTime = resultWaitTime;
734        }
735    
736        /**
737         * Sets the minimum expected amount of time (in millis) the {@link #assertIsSatisfied()} will
738         * wait on a latch until it is satisfied
739         */
740        public void setMinimumResultWaitTime(long resultMinimumWaitTime) {
741            this.resultMinimumWaitTime = resultMinimumWaitTime;
742        }
743    
744        /**
745         * Specifies the expected number of message exchanges that should be
746         * received by this endpoint
747         *
748         * @param expectedCount the number of message exchanges that should be
749         *                expected by this endpoint
750         */
751        public void setExpectedMessageCount(int expectedCount) {
752            this.expectedCount = expectedCount;
753            if (expectedCount <= 0) {
754                latch = null;
755            } else {
756                latch = new CountDownLatch(expectedCount);
757            }
758        }
759    
760        /**
761         * Specifies the minimum number of expected message exchanges that should be
762         * received by this endpoint
763         *
764         * @param expectedCount the number of message exchanges that should be
765         *                expected by this endpoint
766         */
767        public void setMinimumExpectedMessageCount(int expectedCount) {
768            this.expectedMinimumCount = expectedCount;
769            if (expectedCount <= 0) {
770                latch = null;
771            } else {
772                latch = new CountDownLatch(expectedMinimumCount);
773            }
774        }
775    
776        public Processor getReporter() {
777            return reporter;
778        }
779    
780        /**
781         * Allows a processor to added to the endpoint to report on progress of the test
782         */
783        public void setReporter(Processor reporter) {
784            this.reporter = reporter;
785        }
786    
787        // Implementation methods
788        // -------------------------------------------------------------------------
789        private void init() {
790            expectedCount = -1;
791            counter = 0;
792            processors = new HashMap<Integer, Processor>();
793            receivedExchanges = new CopyOnWriteArrayList<Exchange>();
794            failures = new CopyOnWriteArrayList<Throwable>();
795            tests = new CopyOnWriteArrayList<Runnable>();
796            latch = null;
797            sleepForEmptyTest = 0;
798            resultWaitTime = 0;
799            resultMinimumWaitTime = 0L;
800            expectedMinimumCount = -1;
801            expectedBodyValues = null;
802            actualBodyValues = new ArrayList<Object>();
803        }
804    
805        protected synchronized void onExchange(Exchange exchange) {
806            try {
807                if (reporter != null) {
808                    reporter.process(exchange);
809                }
810                performAssertions(exchange);
811            } catch (Throwable e) {
812                // must catch java.lang.Throwable as AssertionException extends java.lang.Error
813                failures.add(e);
814            } finally {
815                // make sure latch is counted down to avoid test hanging forever
816                if (latch != null) {
817                    latch.countDown();
818                }
819            }
820        }
821    
822        @SuppressWarnings("unchecked")
823        protected void performAssertions(Exchange exchange) throws Exception {
824            Message in = exchange.getIn();
825            Object actualBody = in.getBody();
826    
827            if (headerName != null) {
828                actualHeader = in.getHeader(headerName);
829            }
830    
831            if (propertyName != null) {
832                actualProperty = exchange.getProperty(propertyName);
833            }
834    
835            if (expectedBodyValues != null) {
836                int index = actualBodyValues.size();
837                if (expectedBodyValues.size() > index) {
838                    Object expectedBody = expectedBodyValues.get(index);
839                    if (expectedBody != null) {
840                        actualBody = in.getBody(expectedBody.getClass());
841                    }
842                    actualBodyValues.add(actualBody);
843                }
844            }
845    
846            ++counter;
847            if (LOG.isDebugEnabled()) {
848                LOG.debug(getEndpointUri() + " >>>> " + counter + " : " + exchange + " with body: " + actualBody);
849            }
850    
851            receivedExchanges.add(exchange);
852    
853            Processor processor = processors.get(getReceivedCounter()) != null
854                    ? processors.get(getReceivedCounter()) : defaultProcessor;
855    
856            if (processor != null) {
857                try {
858                    processor.process(exchange);
859                } catch (Exception e) {
860                    // set exceptions on exchange so we can throw exceptions to simulate errors
861                    exchange.setException(e);
862                }
863            }
864        }
865    
866        protected void waitForCompleteLatch() throws InterruptedException {
867            if (latch == null) {
868                fail("Should have a latch!");
869            }
870    
871            long start = System.currentTimeMillis();
872            waitForCompleteLatch(resultWaitTime);
873            long delta = System.currentTimeMillis() - start;
874            LOG.debug("Took " + delta + " millis to complete latch");
875    
876            if (resultMinimumWaitTime > 0 && delta < resultMinimumWaitTime) {
877                fail("Expected minimum " + resultMinimumWaitTime
878                    + " millis waiting on the result, but was faster with " + delta + " millis.");
879            }
880        }
881    
882        protected void waitForCompleteLatch(long timeout) throws InterruptedException {
883            // Wait for a default 10 seconds if resultWaitTime is not set
884            long waitTime = timeout == 0 ? 10000L : timeout;
885    
886            // now lets wait for the results
887            LOG.debug("Waiting on the latch for: " + timeout + " millis");
888            latch.await(waitTime, TimeUnit.MILLISECONDS);
889        }
890    
891        protected void assertEquals(String message, Object expectedValue, Object actualValue) {
892            if (!ObjectHelper.equal(expectedValue, actualValue)) {
893                fail(message + ". Expected: <" + expectedValue + "> but was: <" + actualValue + ">");
894            }
895        }
896    
897        protected void assertTrue(String message, boolean predicate) {
898            if (!predicate) {
899                fail(message);
900            }
901        }
902    
903        protected void fail(Object message) {
904            if (LOG.isDebugEnabled()) {
905                List<Exchange> list = getReceivedExchanges();
906                int index = 0;
907                for (Exchange exchange : list) {
908                    LOG.debug(getEndpointUri() + " failed and received[" + (++index) + "]: " + exchange);
909                }
910            }
911            throw new AssertionError(getEndpointUri() + " " + message);
912        }
913    
914        public int getExpectedMinimumCount() {
915            return expectedMinimumCount;
916        }
917    
918        public void await() throws InterruptedException {
919            if (latch != null) {
920                latch.await();
921            }
922        }
923    
924        public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
925            if (latch != null) {
926                return latch.await(timeout, unit);
927            }
928            return true;
929        }
930    
931        public boolean isSingleton() {
932            return true;
933        }
934    }