001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.component.mock;
018
019import java.io.File;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.CopyOnWriteArrayList;
030import java.util.concurrent.CopyOnWriteArraySet;
031import java.util.concurrent.CountDownLatch;
032import java.util.concurrent.TimeUnit;
033
034import org.apache.camel.AsyncCallback;
035import org.apache.camel.CamelContext;
036import org.apache.camel.Component;
037import org.apache.camel.Consumer;
038import org.apache.camel.Endpoint;
039import org.apache.camel.Exchange;
040import org.apache.camel.ExchangePattern;
041import org.apache.camel.Expression;
042import org.apache.camel.Handler;
043import org.apache.camel.Message;
044import org.apache.camel.Predicate;
045import org.apache.camel.Processor;
046import org.apache.camel.Producer;
047import org.apache.camel.builder.ProcessorBuilder;
048import org.apache.camel.impl.DefaultAsyncProducer;
049import org.apache.camel.impl.DefaultEndpoint;
050import org.apache.camel.impl.InterceptSendToEndpoint;
051import org.apache.camel.spi.BrowsableEndpoint;
052import org.apache.camel.spi.UriEndpoint;
053import org.apache.camel.spi.UriParam;
054import org.apache.camel.util.CamelContextHelper;
055import org.apache.camel.util.CaseInsensitiveMap;
056import org.apache.camel.util.ExchangeHelper;
057import org.apache.camel.util.ExpressionComparator;
058import org.apache.camel.util.FileUtil;
059import org.apache.camel.util.ObjectHelper;
060import org.apache.camel.util.StopWatch;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064/**
065 * A Mock endpoint which provides a literate, fluent API for testing routes
066 * using a <a href="http://jmock.org/">JMock style</a> API.
067 * <p/>
068 * The mock endpoint have two set of methods
069 * <ul>
070 *   <li>expectedXXX or expectsXXX - To set pre conditions, before the test is executed</li>
071 *   <li>assertXXX - To assert assertions, after the test has been executed</li>
072 * </ul>
073 * Its <b>important</b> to know the difference between the two set. The former is used to
074 * set expectations before the test is being started (eg before the mock receives messages).
075 * The latter is used after the test has been executed, to verify the expectations; or
076 * other assertions which you can perform after the test has been completed.
077 * <p/>
078 * <b>Beware:</b> If you want to expect a mock does not receive any messages, by calling
079 * {@link #setExpectedMessageCount(int)} with <tt>0</tt>, then take extra care,
080 * as <tt>0</tt> matches when the tests starts, so you need to set a assert period time
081 * to let the test run for a while to make sure there are still no messages arrived; for
082 * that use {@link #setAssertPeriod(long)}.
083 * An alternative is to use <a href="http://camel.apache.org/notifybuilder.html">NotifyBuilder</a>, and use the notifier
084 * to know when Camel is done routing some messages, before you call the {@link #assertIsSatisfied()} method on the mocks.
085 * This allows you to not use a fixed assert period, to speedup testing times.
086 *
087 * @version 
088 */
089@UriEndpoint(scheme = "mock")
090public class MockEndpoint extends DefaultEndpoint implements BrowsableEndpoint {
091    private static final Logger LOG = LoggerFactory.getLogger(MockEndpoint.class);
092    // must be volatile so changes is visible between the thread which performs the assertions
093    // and the threads which process the exchanges when routing messages in Camel
094    protected volatile Processor reporter;
095    protected boolean copyOnExchange = true;
096    @UriParam
097    private volatile int expectedCount;
098    private volatile int counter;
099    private volatile Processor defaultProcessor;
100    private volatile Map<Integer, Processor> processors;
101    private volatile List<Exchange> receivedExchanges;
102    private volatile List<Throwable> failures;
103    private volatile List<Runnable> tests;
104    private volatile CountDownLatch latch;
105    @UriParam
106    private volatile long sleepForEmptyTest;
107    @UriParam
108    private volatile long resultWaitTime;
109    @UriParam
110    private volatile long resultMinimumWaitTime;
111    @UriParam
112    private volatile long assertPeriod;
113    @UriParam
114    private volatile int expectedMinimumCount;
115    private volatile List<?> expectedBodyValues;
116    private volatile List<Object> actualBodyValues;
117    private volatile Map<String, Object> expectedHeaderValues;
118    private volatile Map<String, Object> actualHeaderValues;
119    private volatile Map<String, Object> expectedPropertyValues;
120    private volatile Map<String, Object> actualPropertyValues;
121    @UriParam
122    private volatile int retainFirst;
123    @UriParam
124    private volatile int retainLast;
125
126    public MockEndpoint(String endpointUri, Component component) {
127        super(endpointUri, component);
128        init();
129    }
130
131    @Deprecated
132    public MockEndpoint(String endpointUri) {
133        super(endpointUri);
134        init();
135    }
136
137    public MockEndpoint() {
138        this(null);
139    }
140
141    /**
142     * A helper method to resolve the mock endpoint of the given URI on the given context
143     *
144     * @param context the camel context to try resolve the mock endpoint from
145     * @param uri the uri of the endpoint to resolve
146     * @return the endpoint
147     */
148    public static MockEndpoint resolve(CamelContext context, String uri) {
149        return CamelContextHelper.getMandatoryEndpoint(context, uri, MockEndpoint.class);
150    }
151
152    public static void assertWait(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
153        long start = System.currentTimeMillis();
154        long left = unit.toMillis(timeout);
155        long end = start + left;
156        for (MockEndpoint endpoint : endpoints) {
157            if (!endpoint.await(left, TimeUnit.MILLISECONDS)) {
158                throw new AssertionError("Timeout waiting for endpoints to receive enough messages. " + endpoint.getEndpointUri() + " timed out.");
159            }
160            left = end - System.currentTimeMillis();
161            if (left <= 0) {
162                left = 0;
163            }
164        }
165    }
166
167    public static void assertIsSatisfied(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
168        assertWait(timeout, unit, endpoints);
169        for (MockEndpoint endpoint : endpoints) {
170            endpoint.assertIsSatisfied();
171        }
172    }
173
174    public static void assertIsSatisfied(MockEndpoint... endpoints) throws InterruptedException {
175        for (MockEndpoint endpoint : endpoints) {
176            endpoint.assertIsSatisfied();
177        }
178    }
179
180
181    /**
182     * Asserts that all the expectations on any {@link MockEndpoint} instances registered
183     * in the given context are valid
184     *
185     * @param context the camel context used to find all the available endpoints to be asserted
186     */
187    public static void assertIsSatisfied(CamelContext context) throws InterruptedException {
188        ObjectHelper.notNull(context, "camelContext");
189        Collection<Endpoint> endpoints = context.getEndpoints();
190        for (Endpoint endpoint : endpoints) {
191            // if the endpoint was intercepted we should get the delegate
192            if (endpoint instanceof InterceptSendToEndpoint) {
193                endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
194            }
195            if (endpoint instanceof MockEndpoint) {
196                MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
197                mockEndpoint.assertIsSatisfied();
198            }
199        }
200    }
201
202    /**
203     * Asserts that all the expectations on any {@link MockEndpoint} instances registered
204     * in the given context are valid
205     *
206     * @param context the camel context used to find all the available endpoints to be asserted
207     * @param timeout timeout
208     * @param unit    time unit
209     */
210    public static void assertIsSatisfied(CamelContext context, long timeout, TimeUnit unit) throws InterruptedException {
211        ObjectHelper.notNull(context, "camelContext");
212        ObjectHelper.notNull(unit, "unit");
213        Collection<Endpoint> endpoints = context.getEndpoints();
214        long millis = unit.toMillis(timeout);
215        for (Endpoint endpoint : endpoints) {
216            // if the endpoint was intercepted we should get the delegate
217            if (endpoint instanceof InterceptSendToEndpoint) {
218                endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
219            }
220            if (endpoint instanceof MockEndpoint) {
221                MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
222                mockEndpoint.setResultWaitTime(millis);
223                mockEndpoint.assertIsSatisfied();
224            }
225        }
226    }
227
228    /**
229     * Sets the assert period on all the expectations on any {@link MockEndpoint} instances registered
230     * in the given context.
231     *
232     * @param context the camel context used to find all the available endpoints
233     * @param period the period in millis
234     */
235    public static void setAssertPeriod(CamelContext context, long period) {
236        ObjectHelper.notNull(context, "camelContext");
237        Collection<Endpoint> endpoints = context.getEndpoints();
238        for (Endpoint endpoint : endpoints) {
239            // if the endpoint was intercepted we should get the delegate
240            if (endpoint instanceof InterceptSendToEndpoint) {
241                endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
242            }
243            if (endpoint instanceof MockEndpoint) {
244                MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
245                mockEndpoint.setAssertPeriod(period);
246            }
247        }
248    }
249
250    /**
251     * Reset all mock endpoints
252     *
253     * @param context the camel context used to find all the available endpoints to reset
254     */
255    public static void resetMocks(CamelContext context) {
256        ObjectHelper.notNull(context, "camelContext");
257        Collection<Endpoint> endpoints = context.getEndpoints();
258        for (Endpoint endpoint : endpoints) {
259            // if the endpoint was intercepted we should get the delegate
260            if (endpoint instanceof InterceptSendToEndpoint) {
261                endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
262            }
263            if (endpoint instanceof MockEndpoint) {
264                MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
265                mockEndpoint.reset();
266            }
267        }
268    }
269
270    public static void expectsMessageCount(int count, MockEndpoint... endpoints) throws InterruptedException {
271        for (MockEndpoint endpoint : endpoints) {
272            endpoint.setExpectedMessageCount(count);
273        }
274    }
275
276    public List<Exchange> getExchanges() {
277        return getReceivedExchanges();
278    }
279
280    public Consumer createConsumer(Processor processor) throws Exception {
281        throw new UnsupportedOperationException("You cannot consume from this endpoint");
282    }
283
284    public Producer createProducer() throws Exception {
285        return new DefaultAsyncProducer(this) {
286            public boolean process(Exchange exchange, AsyncCallback callback) {
287                onExchange(exchange);
288                callback.done(true);
289                return true;
290            }
291        };
292    }
293
294    public void reset() {
295        init();
296    }
297
298
299    // Testing API
300    // -------------------------------------------------------------------------
301
302    /**
303     * Handles the incoming exchange.
304     * <p/>
305     * This method turns this mock endpoint into a bean which you can use
306     * in the Camel routes, which allows you to inject MockEndpoint as beans
307     * in your routes and use the features of the mock to control the bean.
308     *
309     * @param exchange  the exchange
310     * @throws Exception can be thrown
311     */
312    @Handler
313    public void handle(Exchange exchange) throws Exception {
314        onExchange(exchange);
315    }
316
317    /**
318     * Set the processor that will be invoked when the index
319     * message is received.
320     */
321    public void whenExchangeReceived(int index, Processor processor) {
322        this.processors.put(index, processor);
323    }
324
325    /**
326     * Set the processor that will be invoked when the some message
327     * is received.
328     *
329     * This processor could be overwritten by
330     * {@link #whenExchangeReceived(int, Processor)} method.
331     */
332    public void whenAnyExchangeReceived(Processor processor) {
333        this.defaultProcessor = processor;
334    }
335    
336    /**
337     * Set the expression which value will be set to the message body
338     * @param expression which is use to set the message body 
339     */
340    public void returnReplyBody(Expression expression) {
341        this.defaultProcessor = ProcessorBuilder.setBody(expression);
342    }
343    
344    /**
345     * Set the expression which value will be set to the message header
346     * @param headerName that will be set value
347     * @param expression which is use to set the message header 
348     */
349    public void returnReplyHeader(String headerName, Expression expression) {
350        this.defaultProcessor = ProcessorBuilder.setHeader(headerName, expression);
351    }
352    
353
354    /**
355     * Validates that all the available expectations on this endpoint are
356     * satisfied; or throw an exception
357     */
358    public void assertIsSatisfied() throws InterruptedException {
359        assertIsSatisfied(sleepForEmptyTest);
360    }
361
362    /**
363     * Validates that all the available expectations on this endpoint are
364     * satisfied; or throw an exception
365     *
366     * @param timeoutForEmptyEndpoints the timeout in milliseconds that we
367     *                should wait for the test to be true
368     */
369    public void assertIsSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
370        LOG.info("Asserting: " + this + " is satisfied");
371        doAssertIsSatisfied(timeoutForEmptyEndpoints);
372        if (assertPeriod > 0) {
373            // if an assert period was set then re-assert again to ensure the assertion is still valid
374            Thread.sleep(assertPeriod);
375            LOG.info("Re-asserting: " + this + " is satisfied after " + assertPeriod + " millis");
376            // do not use timeout when we re-assert
377            doAssertIsSatisfied(0);
378        }
379    }
380
381    protected void doAssertIsSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
382        if (expectedCount == 0) {
383            if (timeoutForEmptyEndpoints > 0) {
384                LOG.debug("Sleeping for: " + timeoutForEmptyEndpoints + " millis to check there really are no messages received");
385                Thread.sleep(timeoutForEmptyEndpoints);
386            }
387            assertEquals("Received message count", expectedCount, getReceivedCounter());
388        } else if (expectedCount > 0) {
389            if (expectedCount != getReceivedCounter()) {
390                waitForCompleteLatch();
391            }
392            assertEquals("Received message count", expectedCount, getReceivedCounter());
393        } else if (expectedMinimumCount > 0 && getReceivedCounter() < expectedMinimumCount) {
394            waitForCompleteLatch();
395        }
396
397        if (expectedMinimumCount >= 0) {
398            int receivedCounter = getReceivedCounter();
399            assertTrue("Received message count " + receivedCounter + ", expected at least " + expectedMinimumCount, expectedMinimumCount <= receivedCounter);
400        }
401
402        for (Runnable test : tests) {
403            test.run();
404        }
405
406        for (Throwable failure : failures) {
407            if (failure != null) {
408                LOG.error("Caught on " + getEndpointUri() + " Exception: " + failure, failure);
409                fail("Failed due to caught exception: " + failure);
410            }
411        }
412    }
413
414    /**
415     * Validates that the assertions fail on this endpoint
416     */
417    public void assertIsNotSatisfied() throws InterruptedException {
418        boolean failed = false;
419        try {
420            assertIsSatisfied();
421            // did not throw expected error... fail!
422            failed = true;
423        } catch (AssertionError e) {
424            LOG.info("Caught expected failure: " + e);
425        }
426        if (failed) {
427            // fail() throws the AssertionError to indicate the test failed. 
428            fail("Expected assertion failure but test succeeded!");
429        }
430    }
431
432    /**
433     * Validates that the assertions fail on this endpoint
434
435     * @param timeoutForEmptyEndpoints the timeout in milliseconds that we
436     *        should wait for the test to be true
437     */
438    public void assertIsNotSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
439        boolean failed = false;
440        try {
441            assertIsSatisfied(timeoutForEmptyEndpoints);
442            // did not throw expected error... fail!
443            failed = true;
444        } catch (AssertionError e) {
445            LOG.info("Caught expected failure: " + e);
446        }
447        if (failed) { 
448            // fail() throws the AssertionError to indicate the test failed. 
449            fail("Expected assertion failure but test succeeded!");
450        }
451    }    
452    
453    /**
454     * Specifies the expected number of message exchanges that should be
455     * received by this endpoint
456     *
457     * If you want to assert that <b>exactly</b> n messages arrives to this mock
458     * endpoint, then see also the {@link #setAssertPeriod(long)} method for further details.
459     *
460     * @param expectedCount the number of message exchanges that should be
461     *                expected by this endpoint
462     * @see #setAssertPeriod(long)
463     */
464    public void expectedMessageCount(int expectedCount) {
465        setExpectedMessageCount(expectedCount);
466    }
467
468    /**
469     * Sets a grace period after which the mock endpoint will re-assert
470     * to ensure the preliminary assertion is still valid.
471     * <p/>
472     * This is used for example to assert that <b>exactly</b> a number of messages 
473     * arrives. For example if {@link #expectedMessageCount(int)} was set to 5, then
474     * the assertion is satisfied when 5 or more message arrives. To ensure that
475     * exactly 5 messages arrives, then you would need to wait a little period
476     * to ensure no further message arrives. This is what you can use this
477     * {@link #setAssertPeriod(long)} method for.
478     * <p/>
479     * By default this period is disabled.
480     *
481     * @param period grace period in millis
482     */
483    public void setAssertPeriod(long period) {
484        this.assertPeriod = period;
485    }
486
487    /**
488     * Specifies the minimum number of expected message exchanges that should be
489     * received by this endpoint
490     *
491     * @param expectedCount the number of message exchanges that should be
492     *                expected by this endpoint
493     */
494    public void expectedMinimumMessageCount(int expectedCount) {
495        setMinimumExpectedMessageCount(expectedCount);
496    }
497
498    /**
499     * Sets an expectation that the given header name & value are received by this endpoint
500     * <p/>
501     * You can set multiple expectations for different header names.
502     * If you set a value of <tt>null</tt> that means we accept either the header is absent, or its value is <tt>null</tt>
503     */
504    public void expectedHeaderReceived(final String name, final Object value) {
505        if (expectedHeaderValues == null) {
506            expectedHeaderValues = new CaseInsensitiveMap();
507            // we just wants to expects to be called once
508            expects(new Runnable() {
509                public void run() {
510                    for (int i = 0; i < getReceivedExchanges().size(); i++) {
511                        Exchange exchange = getReceivedExchange(i);
512                        for (Map.Entry<String, Object> entry : expectedHeaderValues.entrySet()) {
513                            String key = entry.getKey();
514                            Object expectedValue = entry.getValue();
515
516                            // we accept that an expectedValue of null also means that the header may be absent
517                            if (expectedValue != null) {
518                                assertTrue("Exchange " + i + " has no headers", exchange.getIn().hasHeaders());
519                                boolean hasKey = exchange.getIn().getHeaders().containsKey(key);
520                                assertTrue("No header with name " + key + " found for message: " + i, hasKey);
521                            }
522
523                            Object actualValue = exchange.getIn().getHeader(key);
524                            actualValue = extractActualValue(exchange, actualValue, expectedValue);
525
526                            assertEquals("Header with name " + key + " for message: " + i, expectedValue, actualValue);
527                        }
528                    }
529                }
530            });
531        }
532        expectedHeaderValues.put(name, value);
533    }
534
535    /**
536     * Adds an expectation that the given header values are received by this
537     * endpoint in any order
538     */
539    public void expectedHeaderValuesReceivedInAnyOrder(final String name, final List<?> values) {
540        expectedMessageCount(values.size());
541
542        expects(new Runnable() {
543            public void run() {
544                // these are the expected values to find
545                final Set<Object> actualHeaderValues = new CopyOnWriteArraySet<Object>(values);
546
547                for (int i = 0; i < getReceivedExchanges().size(); i++) {
548                    Exchange exchange = getReceivedExchange(i);
549
550                    Object actualValue = exchange.getIn().getHeader(name);
551                    for (Object expectedValue : actualHeaderValues) {
552                        actualValue = extractActualValue(exchange, actualValue, expectedValue);
553                        // remove any found values
554                        actualHeaderValues.remove(actualValue);
555                    }
556                }
557
558                // should be empty, as we should find all the values
559                assertTrue("Expected " + values.size() + " headers with key[" + name + "], received " + (values.size() - actualHeaderValues.size())
560                        + " headers. Expected header values: " + actualHeaderValues, actualHeaderValues.isEmpty());
561            }
562        });
563    }
564
565    /**
566     * Adds an expectation that the given header values are received by this
567     * endpoint in any order
568     */
569    public void expectedHeaderValuesReceivedInAnyOrder(String name, Object... values) {
570        List<Object> valueList = new ArrayList<Object>();
571        valueList.addAll(Arrays.asList(values));
572        expectedHeaderValuesReceivedInAnyOrder(name, valueList);
573    }
574
575    /**
576     * Sets an expectation that the given property name & value are received by this endpoint
577     * <p/>
578     * You can set multiple expectations for different property names.
579     * If you set a value of <tt>null</tt> that means we accept either the property is absent, or its value is <tt>null</tt>
580     */
581    public void expectedPropertyReceived(final String name, final Object value) {
582        if (expectedPropertyValues == null) {
583            expectedPropertyValues = new ConcurrentHashMap<String, Object>();
584        }
585        if (value != null) {
586            // ConcurrentHashMap cannot store null values
587            expectedPropertyValues.put(name, value);
588        }
589
590        expects(new Runnable() {
591            public void run() {
592                for (int i = 0; i < getReceivedExchanges().size(); i++) {
593                    Exchange exchange = getReceivedExchange(i);
594                    for (Map.Entry<String, Object> entry : expectedPropertyValues.entrySet()) {
595                        String key = entry.getKey();
596                        Object expectedValue = entry.getValue();
597
598                        // we accept that an expectedValue of null also means that the header may be absent
599                        if (expectedValue != null) {
600                            assertTrue("Exchange " + i + " has no properties", !exchange.getProperties().isEmpty());
601                            boolean hasKey = exchange.getProperties().containsKey(key);
602                            assertTrue("No property with name " + key + " found for message: " + i, hasKey);
603                        }
604
605                        Object actualValue = exchange.getProperty(key);
606                        actualValue = extractActualValue(exchange, actualValue, expectedValue);
607
608                        assertEquals("Property with name " + key + " for message: " + i, expectedValue, actualValue);
609                    }
610                }
611            }
612        });
613    }
614
615    /**
616     * Adds an expectation that the given body values are received by this
617     * endpoint in the specified order
618     */
619    public void expectedBodiesReceived(final List<?> bodies) {
620        expectedMessageCount(bodies.size());
621        this.expectedBodyValues = bodies;
622        this.actualBodyValues = new ArrayList<Object>();
623
624        expects(new Runnable() {
625            public void run() {
626                for (int i = 0; i < expectedBodyValues.size(); i++) {
627                    Exchange exchange = getReceivedExchange(i);
628                    assertTrue("No exchange received for counter: " + i, exchange != null);
629
630                    Object expectedBody = expectedBodyValues.get(i);
631                    Object actualBody = null;
632                    if (i < actualBodyValues.size()) {
633                        actualBody = actualBodyValues.get(i);
634                    }
635                    actualBody = extractActualValue(exchange, actualBody, expectedBody);
636
637                    assertEquals("Body of message: " + i, expectedBody, actualBody);
638                }
639            }
640        });
641    }
642
643    private Object extractActualValue(Exchange exchange, Object actualValue, Object expectedValue) {
644        if (actualValue == null) {
645            return null;
646        }
647
648        if (actualValue instanceof Expression) {
649            actualValue = ((Expression)actualValue).evaluate(exchange, expectedValue != null ? expectedValue.getClass() : Object.class);
650        } else if (actualValue instanceof Predicate) {
651            actualValue = ((Predicate)actualValue).matches(exchange);
652        } else if (expectedValue != null) {
653            String from = actualValue.getClass().getName();
654            String to = expectedValue.getClass().getName();
655            actualValue = getCamelContext().getTypeConverter().convertTo(expectedValue.getClass(), exchange, actualValue);
656            assertTrue("There is no type conversion possible from " + from + " to " + to, actualValue != null);
657        }
658        return actualValue;
659    }
660
661    /**
662     * Sets an expectation that the given predicates matches the received messages by this endpoint
663     */
664    public void expectedMessagesMatches(Predicate... predicates) {
665        for (int i = 0; i < predicates.length; i++) {
666            final int messageIndex = i;
667            final Predicate predicate = predicates[i];
668            final AssertionClause clause = new AssertionClause(this) {
669                public void run() {
670                    addPredicate(predicate);
671                    applyAssertionOn(MockEndpoint.this, messageIndex, assertExchangeReceived(messageIndex));
672                }
673            };
674            expects(clause);
675        }
676    }
677
678    /**
679     * Sets an expectation that the given body values are received by this endpoint
680     */
681    public void expectedBodiesReceived(Object... bodies) {
682        List<Object> bodyList = new ArrayList<Object>();
683        bodyList.addAll(Arrays.asList(bodies));
684        expectedBodiesReceived(bodyList);
685    }
686
687    /**
688     * Adds an expectation that the given body value are received by this endpoint
689     */
690    public AssertionClause expectedBodyReceived() {
691        expectedMessageCount(1);
692        final AssertionClause clause = new AssertionClause(this) {
693            public void run() {
694                Exchange exchange = getReceivedExchange(0);
695                assertTrue("No exchange received for counter: " + 0, exchange != null);
696
697                Object actualBody = exchange.getIn().getBody();
698                Expression exp = createExpression(getCamelContext());
699                Object expectedBody = exp.evaluate(exchange, Object.class);
700
701                assertEquals("Body of message: " + 0, expectedBody, actualBody);
702            }
703        };
704        expects(clause);
705        return clause;
706    }
707
708    /**
709     * Adds an expectation that the given body values are received by this
710     * endpoint in any order
711     */
712    public void expectedBodiesReceivedInAnyOrder(final List<?> bodies) {
713        expectedMessageCount(bodies.size());
714        this.expectedBodyValues = bodies;
715        this.actualBodyValues = new ArrayList<Object>();
716
717        expects(new Runnable() {
718            public void run() {
719                List<Object> actualBodyValuesSet = new ArrayList<Object>(actualBodyValues);
720                for (int i = 0; i < expectedBodyValues.size(); i++) {
721                    Exchange exchange = getReceivedExchange(i);
722                    assertTrue("No exchange received for counter: " + i, exchange != null);
723
724                    Object expectedBody = expectedBodyValues.get(i);
725                    assertTrue("Message with body " + expectedBody + " was expected but not found in " + actualBodyValuesSet, actualBodyValuesSet.remove(expectedBody));
726                }
727            }
728        });
729    }
730
731    /**
732     * Adds an expectation that the given body values are received by this
733     * endpoint in any order
734     */
735    public void expectedBodiesReceivedInAnyOrder(Object... bodies) {
736        List<Object> bodyList = new ArrayList<Object>();
737        bodyList.addAll(Arrays.asList(bodies));
738        expectedBodiesReceivedInAnyOrder(bodyList);
739    }
740
741    /**
742     * Adds an expectation that a file exists with the given name
743     *
744     * @param name name of file, will cater for / and \ on different OS platforms
745     */
746    public void expectedFileExists(final String name) {
747        expectedFileExists(name, null);
748    }
749
750    /**
751     * Adds an expectation that a file exists with the given name
752     * <p/>
753     * Will wait at most 5 seconds while checking for the existence of the file.
754     *
755     * @param name name of file, will cater for / and \ on different OS platforms
756     * @param content content of file to compare, can be <tt>null</tt> to not compare content
757     */
758    public void expectedFileExists(final String name, final String content) {
759        final File file = new File(FileUtil.normalizePath(name));
760
761        expects(new Runnable() {
762            public void run() {
763                // wait at most 5 seconds for the file to exists
764                final long timeout = System.currentTimeMillis() + 5000;
765
766                boolean stop = false;
767                while (!stop && !file.exists()) {
768                    try {
769                        Thread.sleep(50);
770                    } catch (InterruptedException e) {
771                        // ignore
772                    }
773                    stop = System.currentTimeMillis() > timeout;
774                }
775
776                assertTrue("The file should exists: " + name, file.exists());
777
778                if (content != null) {
779                    String body = getCamelContext().getTypeConverter().convertTo(String.class, file);
780                    assertEquals("Content of file: " + name, content, body);
781                }
782            }
783        });
784    }
785
786    /**
787     * Adds an expectation that messages received should have the given exchange pattern
788     */
789    public void expectedExchangePattern(final ExchangePattern exchangePattern) {
790        expectedMessagesMatches(new Predicate() {
791            public boolean matches(Exchange exchange) {
792                return exchange.getPattern().equals(exchangePattern);
793            }
794        });
795    }
796
797    /**
798     * Adds an expectation that messages received should have ascending values
799     * of the given expression such as a user generated counter value
800     */
801    public void expectsAscending(final Expression expression) {
802        expects(new Runnable() {
803            public void run() {
804                assertMessagesAscending(expression);
805            }
806        });
807    }
808
809    /**
810     * Adds an expectation that messages received should have ascending values
811     * of the given expression such as a user generated counter value
812     */
813    public AssertionClause expectsAscending() {
814        final AssertionClause clause = new AssertionClause(this) {
815            public void run() {
816                assertMessagesAscending(createExpression(getCamelContext()));
817            }
818        };
819        expects(clause);
820        return clause;
821    }
822
823    /**
824     * Adds an expectation that messages received should have descending values
825     * of the given expression such as a user generated counter value
826     */
827    public void expectsDescending(final Expression expression) {
828        expects(new Runnable() {
829            public void run() {
830                assertMessagesDescending(expression);
831            }
832        });
833    }
834
835    /**
836     * Adds an expectation that messages received should have descending values
837     * of the given expression such as a user generated counter value
838     */
839    public AssertionClause expectsDescending() {
840        final AssertionClause clause = new AssertionClause(this) {
841            public void run() {
842                assertMessagesDescending(createExpression(getCamelContext()));
843            }
844        };
845        expects(clause);
846        return clause;
847    }
848
849    /**
850     * Adds an expectation that no duplicate messages should be received using
851     * the expression to determine the message ID
852     *
853     * @param expression the expression used to create a unique message ID for
854     *                message comparison (which could just be the message
855     *                payload if the payload can be tested for uniqueness using
856     *                {@link Object#equals(Object)} and
857     *                {@link Object#hashCode()}
858     */
859    public void expectsNoDuplicates(final Expression expression) {
860        expects(new Runnable() {
861            public void run() {
862                assertNoDuplicates(expression);
863            }
864        });
865    }
866
867    /**
868     * Adds an expectation that no duplicate messages should be received using
869     * the expression to determine the message ID
870     */
871    public AssertionClause expectsNoDuplicates() {
872        final AssertionClause clause = new AssertionClause(this) {
873            public void run() {
874                assertNoDuplicates(createExpression(getCamelContext()));
875            }
876        };
877        expects(clause);
878        return clause;
879    }
880
881    /**
882     * Asserts that the messages have ascending values of the given expression
883     */
884    public void assertMessagesAscending(Expression expression) {
885        assertMessagesSorted(expression, true);
886    }
887
888    /**
889     * Asserts that the messages have descending values of the given expression
890     */
891    public void assertMessagesDescending(Expression expression) {
892        assertMessagesSorted(expression, false);
893    }
894
895    protected void assertMessagesSorted(Expression expression, boolean ascending) {
896        String type = ascending ? "ascending" : "descending";
897        ExpressionComparator comparator = new ExpressionComparator(expression);
898        List<Exchange> list = getReceivedExchanges();
899        for (int i = 1; i < list.size(); i++) {
900            int j = i - 1;
901            Exchange e1 = list.get(j);
902            Exchange e2 = list.get(i);
903            int result = comparator.compare(e1, e2);
904            if (result == 0) {
905                fail("Messages not " + type + ". Messages" + j + " and " + i + " are equal with value: "
906                    + expression.evaluate(e1, Object.class) + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2);
907            } else {
908                if (!ascending) {
909                    result = result * -1;
910                }
911                if (result > 0) {
912                    fail("Messages not " + type + ". Message " + j + " has value: " + expression.evaluate(e1, Object.class)
913                        + " and message " + i + " has value: " + expression.evaluate(e2, Object.class) + " for expression: "
914                        + expression + ". Exchanges: " + e1 + " and " + e2);
915                }
916            }
917        }
918    }
919
920    public void assertNoDuplicates(Expression expression) {
921        Map<Object, Exchange> map = new HashMap<Object, Exchange>();
922        List<Exchange> list = getReceivedExchanges();
923        for (int i = 0; i < list.size(); i++) {
924            Exchange e2 = list.get(i);
925            Object key = expression.evaluate(e2, Object.class);
926            Exchange e1 = map.get(key);
927            if (e1 != null) {
928                fail("Duplicate message found on message " + i + " has value: " + key + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2);
929            } else {
930                map.put(key, e2);
931            }
932        }
933    }
934
935    /**
936     * Adds the expectation which will be invoked when enough messages are received
937     */
938    public void expects(Runnable runnable) {
939        tests.add(runnable);
940    }
941
942    /**
943     * Adds an assertion to the given message index
944     *
945     * @param messageIndex the number of the message
946     * @return the assertion clause
947     */
948    public AssertionClause message(final int messageIndex) {
949        final AssertionClause clause = new AssertionClause(this) {
950            public void run() {
951                applyAssertionOn(MockEndpoint.this, messageIndex, assertExchangeReceived(messageIndex));
952            }
953        };
954        expects(clause);
955        return clause;
956    }
957
958    /**
959     * Adds an assertion to all the received messages
960     *
961     * @return the assertion clause
962     */
963    public AssertionClause allMessages() {
964        final AssertionClause clause = new AssertionClause(this) {
965            public void run() {
966                List<Exchange> list = getReceivedExchanges();
967                int index = 0;
968                for (Exchange exchange : list) {
969                    applyAssertionOn(MockEndpoint.this, index++, exchange);
970                }
971            }
972        };
973        expects(clause);
974        return clause;
975    }
976
977    /**
978     * Asserts that the given index of message is received (starting at zero)
979     */
980    public Exchange assertExchangeReceived(int index) {
981        int count = getReceivedCounter();
982        assertTrue("Not enough messages received. Was: " + count, count > index);
983        return getReceivedExchange(index);
984    }
985
986    // Properties
987    // -------------------------------------------------------------------------
988    public List<Throwable> getFailures() {
989        return failures;
990    }
991
992    public int getReceivedCounter() {
993        return counter;
994    }
995
996    public List<Exchange> getReceivedExchanges() {
997        return receivedExchanges;
998    }
999
1000    public int getExpectedCount() {
1001        return expectedCount;
1002    }
1003
1004    public long getSleepForEmptyTest() {
1005        return sleepForEmptyTest;
1006    }
1007
1008    /**
1009     * Allows a sleep to be specified to wait to check that this endpoint really
1010     * is empty when {@link #expectedMessageCount(int)} is called with zero
1011     *
1012     * @param sleepForEmptyTest the milliseconds to sleep for to determine that
1013     *                this endpoint really is empty
1014     */
1015    public void setSleepForEmptyTest(long sleepForEmptyTest) {
1016        this.sleepForEmptyTest = sleepForEmptyTest;
1017    }
1018
1019    public long getResultWaitTime() {
1020        return resultWaitTime;
1021    }
1022
1023    /**
1024     * Sets the maximum amount of time (in millis) the {@link #assertIsSatisfied()} will
1025     * wait on a latch until it is satisfied
1026     */
1027    public void setResultWaitTime(long resultWaitTime) {
1028        this.resultWaitTime = resultWaitTime;
1029    }
1030
1031    /**
1032     * Sets the minimum expected amount of time (in millis) the {@link #assertIsSatisfied()} will
1033     * wait on a latch until it is satisfied
1034     */
1035    public void setMinimumResultWaitTime(long resultMinimumWaitTime) {
1036        this.resultMinimumWaitTime = resultMinimumWaitTime;
1037    }
1038
1039    /**
1040     * Specifies the expected number of message exchanges that should be
1041     * received by this endpoint.
1042     * <p/>
1043     * <b>Beware:</b> If you want to expect that <tt>0</tt> messages, then take extra care,
1044     * as <tt>0</tt> matches when the tests starts, so you need to set a assert period time
1045     * to let the test run for a while to make sure there are still no messages arrived; for
1046     * that use {@link #setAssertPeriod(long)}.
1047     * An alternative is to use <a href="http://camel.apache.org/notifybuilder.html">NotifyBuilder</a>, and use the notifier
1048     * to know when Camel is done routing some messages, before you call the {@link #assertIsSatisfied()} method on the mocks.
1049     * This allows you to not use a fixed assert period, to speedup testing times.
1050     * <p/>
1051     * If you want to assert that <b>exactly</b> n'th message arrives to this mock
1052     * endpoint, then see also the {@link #setAssertPeriod(long)} method for further details.
1053     *
1054     * @param expectedCount the number of message exchanges that should be
1055     *                expected by this endpoint
1056     * @see #setAssertPeriod(long)                      
1057     */
1058    public void setExpectedMessageCount(int expectedCount) {
1059        this.expectedCount = expectedCount;
1060        if (expectedCount <= 0) {
1061            latch = null;
1062        } else {
1063            latch = new CountDownLatch(expectedCount);
1064        }
1065    }
1066
1067    /**
1068     * Specifies the minimum number of expected message exchanges that should be
1069     * received by this endpoint
1070     *
1071     * @param expectedCount the number of message exchanges that should be
1072     *                expected by this endpoint
1073     */
1074    public void setMinimumExpectedMessageCount(int expectedCount) {
1075        this.expectedMinimumCount = expectedCount;
1076        if (expectedCount <= 0) {
1077            latch = null;
1078        } else {
1079            latch = new CountDownLatch(expectedMinimumCount);
1080        }
1081    }
1082
1083    public Processor getReporter() {
1084        return reporter;
1085    }
1086
1087    /**
1088     * Allows a processor to added to the endpoint to report on progress of the test
1089     */
1090    public void setReporter(Processor reporter) {
1091        this.reporter = reporter;
1092    }
1093
1094    /**
1095     * Specifies to only retain the first n'th number of received {@link Exchange}s.
1096     * <p/>
1097     * This is used when testing with big data, to reduce memory consumption by not storing
1098     * copies of every {@link Exchange} this mock endpoint receives.
1099     * <p/>
1100     * <b>Important:</b> When using this limitation, then the {@link #getReceivedCounter()}
1101     * will still return the actual number of received {@link Exchange}s. For example
1102     * if we have received 5000 {@link Exchange}s, and have configured to only retain the first
1103     * 10 {@link Exchange}s, then the {@link #getReceivedCounter()} will still return <tt>5000</tt>
1104     * but there is only the first 10 {@link Exchange}s in the {@link #getExchanges()} and
1105     * {@link #getReceivedExchanges()} methods.
1106     * <p/>
1107     * When using this method, then some of the other expectation methods is not supported,
1108     * for example the {@link #expectedBodiesReceived(Object...)} sets a expectation on the first
1109     * number of bodies received.
1110     * <p/>
1111     * You can configure both {@link #setRetainFirst(int)} and {@link #setRetainLast(int)} methods,
1112     * to limit both the first and last received.
1113     * 
1114     * @param retainFirst  to limit and only keep the first n'th received {@link Exchange}s, use
1115     *                     <tt>0</tt> to not retain any messages, or <tt>-1</tt> to retain all.
1116     * @see #setRetainLast(int)
1117     */
1118    public void setRetainFirst(int retainFirst) {
1119        this.retainFirst = retainFirst;
1120    }
1121
1122    /**
1123     * Specifies to only retain the last n'th number of received {@link Exchange}s.
1124     * <p/>
1125     * This is used when testing with big data, to reduce memory consumption by not storing
1126     * copies of every {@link Exchange} this mock endpoint receives.
1127     * <p/>
1128     * <b>Important:</b> When using this limitation, then the {@link #getReceivedCounter()}
1129     * will still return the actual number of received {@link Exchange}s. For example
1130     * if we have received 5000 {@link Exchange}s, and have configured to only retain the last
1131     * 20 {@link Exchange}s, then the {@link #getReceivedCounter()} will still return <tt>5000</tt>
1132     * but there is only the last 20 {@link Exchange}s in the {@link #getExchanges()} and
1133     * {@link #getReceivedExchanges()} methods.
1134     * <p/>
1135     * When using this method, then some of the other expectation methods is not supported,
1136     * for example the {@link #expectedBodiesReceived(Object...)} sets a expectation on the first
1137     * number of bodies received.
1138     * <p/>
1139     * You can configure both {@link #setRetainFirst(int)} and {@link #setRetainLast(int)} methods,
1140     * to limit both the first and last received.
1141     *
1142     * @param retainLast  to limit and only keep the last n'th received {@link Exchange}s, use
1143     *                     <tt>0</tt> to not retain any messages, or <tt>-1</tt> to retain all.
1144     * @see #setRetainFirst(int)
1145     */
1146    public void setRetainLast(int retainLast) {
1147        this.retainLast = retainLast;
1148    }
1149
1150    // Implementation methods
1151    // -------------------------------------------------------------------------
1152    private void init() {
1153        expectedCount = -1;
1154        counter = 0;
1155        defaultProcessor = null;
1156        processors = new HashMap<Integer, Processor>();
1157        receivedExchanges = new CopyOnWriteArrayList<Exchange>();
1158        failures = new CopyOnWriteArrayList<Throwable>();
1159        tests = new CopyOnWriteArrayList<Runnable>();
1160        latch = null;
1161        sleepForEmptyTest = 0;
1162        resultWaitTime = 0;
1163        resultMinimumWaitTime = 0L;
1164        assertPeriod = 0L;
1165        expectedMinimumCount = -1;
1166        expectedBodyValues = null;
1167        actualBodyValues = new ArrayList<Object>();
1168        expectedHeaderValues = null;
1169        actualHeaderValues = null;
1170        expectedPropertyValues = null;
1171        actualPropertyValues = null;
1172        retainFirst = -1;
1173        retainLast = -1;
1174    }
1175
1176    protected synchronized void onExchange(Exchange exchange) {
1177        try {
1178            if (reporter != null) {
1179                reporter.process(exchange);
1180            }
1181            Exchange copy = exchange;
1182            if (copyOnExchange) {
1183                // copy the exchange so the mock stores the copy and not the actual exchange
1184                copy = ExchangeHelper.createCopy(exchange, true);
1185            }
1186            performAssertions(exchange, copy);
1187        } catch (Throwable e) {
1188            // must catch java.lang.Throwable as AssertionError extends java.lang.Error
1189            failures.add(e);
1190        } finally {
1191            // make sure latch is counted down to avoid test hanging forever
1192            if (latch != null) {
1193                latch.countDown();
1194            }
1195        }
1196    }
1197
1198    /**
1199     * Performs the assertions on the incoming exchange.
1200     *
1201     * @param exchange   the actual exchange
1202     * @param copy       a copy of the exchange (only store this)
1203     * @throws Exception can be thrown if something went wrong
1204     */
1205    protected void performAssertions(Exchange exchange, Exchange copy) throws Exception {
1206        Message in = copy.getIn();
1207        Object actualBody = in.getBody();
1208
1209        if (expectedHeaderValues != null) {
1210            if (actualHeaderValues == null) {
1211                actualHeaderValues = new CaseInsensitiveMap();
1212            }
1213            if (in.hasHeaders()) {
1214                actualHeaderValues.putAll(in.getHeaders());
1215            }
1216        }
1217
1218        if (expectedPropertyValues != null) {
1219            if (actualPropertyValues == null) {
1220                actualPropertyValues = new ConcurrentHashMap<String, Object>();
1221            }
1222            actualPropertyValues.putAll(copy.getProperties());
1223        }
1224
1225        if (expectedBodyValues != null) {
1226            int index = actualBodyValues.size();
1227            if (expectedBodyValues.size() > index) {
1228                Object expectedBody = expectedBodyValues.get(index);
1229                if (expectedBody != null) {
1230                    // prefer to convert body early, for example when using files
1231                    // we need to read the content at this time
1232                    Object body = in.getBody(expectedBody.getClass());
1233                    if (body != null) {
1234                        actualBody = body;
1235                    }
1236                }
1237                actualBodyValues.add(actualBody);
1238            }
1239        }
1240
1241        // let counter be 0 index-based in the logs
1242        if (LOG.isDebugEnabled()) {
1243            String msg = getEndpointUri() + " >>>> " + counter + " : " + copy + " with body: " + actualBody;
1244            if (copy.getIn().hasHeaders()) {
1245                msg += " and headers:" + copy.getIn().getHeaders();
1246            }
1247            LOG.debug(msg);
1248        }
1249
1250        // record timestamp when exchange was received
1251        copy.setProperty(Exchange.RECEIVED_TIMESTAMP, new Date());
1252
1253        // add a copy of the received exchange
1254        addReceivedExchange(copy);
1255        // and then increment counter after adding received exchange
1256        ++counter;
1257
1258        Processor processor = processors.get(getReceivedCounter()) != null
1259                ? processors.get(getReceivedCounter()) : defaultProcessor;
1260
1261        if (processor != null) {
1262            try {
1263                // must process the incoming exchange and NOT the copy as the idea
1264                // is the end user can manipulate the exchange
1265                processor.process(exchange);
1266            } catch (Exception e) {
1267                // set exceptions on exchange so we can throw exceptions to simulate errors
1268                exchange.setException(e);
1269            }
1270        }
1271    }
1272
1273    /**
1274     * Adds the received exchange.
1275     * 
1276     * @param copy  a copy of the received exchange
1277     */
1278    protected void addReceivedExchange(Exchange copy) {
1279        if (retainFirst == 0 && retainLast == 0) {
1280            // do not retain any messages at all
1281        } else if (retainFirst < 0 && retainLast < 0) {
1282            // no limitation so keep them all
1283            receivedExchanges.add(copy);
1284        } else {
1285            // okay there is some sort of limitations, so figure out what to retain
1286            if (retainFirst > 0 && counter < retainFirst) {
1287                // store a copy as its within the retain first limitation
1288                receivedExchanges.add(copy);
1289            } else if (retainLast > 0) {
1290                // remove the oldest from the last retained boundary,
1291                int index = receivedExchanges.size() - retainLast;
1292                if (index >= 0) {
1293                    // but must be outside the first range as well
1294                    // otherwise we should not remove the oldest
1295                    if (retainFirst <= 0 || retainFirst <= index) {
1296                        receivedExchanges.remove(index);
1297                    }
1298                }
1299                // store a copy of the last n'th received
1300                receivedExchanges.add(copy);
1301            }
1302        }
1303    }
1304
1305    protected void waitForCompleteLatch() throws InterruptedException {
1306        if (latch == null) {
1307            fail("Should have a latch!");
1308        }
1309
1310        StopWatch watch = new StopWatch();
1311        waitForCompleteLatch(resultWaitTime);
1312        long delta = watch.stop();
1313        LOG.debug("Took {} millis to complete latch", delta);
1314
1315        if (resultMinimumWaitTime > 0 && delta < resultMinimumWaitTime) {
1316            fail("Expected minimum " + resultMinimumWaitTime
1317                + " millis waiting on the result, but was faster with " + delta + " millis.");
1318        }
1319    }
1320
1321    protected void waitForCompleteLatch(long timeout) throws InterruptedException {
1322        // Wait for a default 10 seconds if resultWaitTime is not set
1323        long waitTime = timeout == 0 ? 10000L : timeout;
1324
1325        // now let's wait for the results
1326        LOG.debug("Waiting on the latch for: " + timeout + " millis");
1327        latch.await(waitTime, TimeUnit.MILLISECONDS);
1328    }
1329
1330    protected void assertEquals(String message, Object expectedValue, Object actualValue) {
1331        if (!ObjectHelper.equal(expectedValue, actualValue)) {
1332            fail(message + ". Expected: <" + expectedValue + "> but was: <" + actualValue + ">");
1333        }
1334    }
1335
1336    protected void assertTrue(String message, boolean predicate) {
1337        if (!predicate) {
1338            fail(message);
1339        }
1340    }
1341
1342    protected void fail(Object message) {
1343        if (LOG.isDebugEnabled()) {
1344            List<Exchange> list = getReceivedExchanges();
1345            int index = 0;
1346            for (Exchange exchange : list) {
1347                LOG.debug("{} failed and received[{}]: {}", new Object[]{getEndpointUri(), ++index, exchange});
1348            }
1349        }
1350        throw new AssertionError(getEndpointUri() + " " + message);
1351    }
1352
1353    public int getExpectedMinimumCount() {
1354        return expectedMinimumCount;
1355    }
1356
1357    public void await() throws InterruptedException {
1358        if (latch != null) {
1359            latch.await();
1360        }
1361    }
1362
1363    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
1364        if (latch != null) {
1365            return latch.await(timeout, unit);
1366        }
1367        return true;
1368    }
1369
1370    public boolean isSingleton() {
1371        return true;
1372    }
1373
1374    public boolean isLenientProperties() {
1375        return true;
1376    }
1377
1378    private Exchange getReceivedExchange(int index) {
1379        if (index <= receivedExchanges.size() - 1) {
1380            return receivedExchanges.get(index);
1381        } else {
1382            return null;
1383        }
1384    }
1385
1386}