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