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.beans.PropertyChangeListener;
020    import java.beans.PropertyChangeSupport;
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.Collection;
024    import java.util.HashMap;
025    import java.util.HashSet;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    import java.util.concurrent.CopyOnWriteArrayList;
030    import java.util.concurrent.CountDownLatch;
031    import java.util.concurrent.TimeUnit;
032    
033    import org.apache.camel.CamelContext;
034    import org.apache.camel.Component;
035    import org.apache.camel.Consumer;
036    import org.apache.camel.Endpoint;
037    import org.apache.camel.Exchange;
038    import org.apache.camel.Expression;
039    import org.apache.camel.Message;
040    import org.apache.camel.Processor;
041    import org.apache.camel.Producer;
042    import org.apache.camel.impl.DefaultEndpoint;
043    import org.apache.camel.impl.DefaultProducer;
044    import org.apache.camel.spi.BrowsableEndpoint;
045    import org.apache.camel.util.CamelContextHelper;
046    import org.apache.camel.util.ExpressionComparator;
047    import org.apache.camel.util.ObjectHelper;
048    import org.apache.commons.logging.Log;
049    import org.apache.commons.logging.LogFactory;
050    
051    /**
052     * A Mock endpoint which provides a literate, fluent API for testing routes
053     * using a <a href="http://jmock.org/">JMock style</a> API.
054     *
055     * @version $Revision: 46137 $
056     */
057    public class MockEndpoint extends DefaultEndpoint<Exchange> implements BrowsableEndpoint<Exchange> {
058        private static final transient Log LOG = LogFactory.getLog(MockEndpoint.class);
059        private int expectedCount;
060        private int counter;
061        private Processor defaultProcessor;
062        private Map<Integer, Processor> processors;
063        private List<Exchange> receivedExchanges;
064        private List<Throwable> failures;
065        private List<Runnable> tests;
066        private CountDownLatch latch;
067        private long sleepForEmptyTest;
068        private long resultWaitTime;
069        private int expectedMinimumCount;
070        private List expectedBodyValues;
071        private List actualBodyValues;
072        private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
073        private String headerName;
074        private String headerValue;
075        private Object actualHeader;
076        private Processor reporter;
077    
078        public MockEndpoint(String endpointUri, Component component) {
079            super(endpointUri, component);
080            init();
081        }
082    
083        public MockEndpoint(String endpointUri) {
084            super(endpointUri);
085            init();
086        }
087    
088    
089        /**
090         * A helper method to resolve the mock endpoint of the given URI on the given context
091         *
092         * @param context the camel context to try resolve the mock endpoint from
093         * @param uri the uri of the endpoint to resolve
094         * @return the endpoint
095         */
096        public static MockEndpoint resolve(CamelContext context, String uri) {
097            return CamelContextHelper.getMandatoryEndpoint(context, uri, MockEndpoint.class);
098        }
099    
100        public static void assertWait(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
101            long start = System.currentTimeMillis();
102            long left = unit.toMillis(timeout);
103            long end = start + left;
104            for (MockEndpoint endpoint : endpoints) {
105                if (!endpoint.await(left, TimeUnit.MILLISECONDS)) {
106                    throw new AssertionError("Timeout waiting for endpoints to receive enough messages. " + endpoint.getEndpointUri() + " timed out.");
107                }
108                left = end - System.currentTimeMillis();
109                if (left <= 0) {
110                    left = 0;
111                }
112            }
113        }
114    
115        public static void assertIsSatisfied(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
116            assertWait(timeout, unit, endpoints);
117            for (MockEndpoint endpoint : endpoints) {
118                endpoint.assertIsSatisfied();
119            }
120        }
121    
122        public static void assertIsSatisfied(MockEndpoint... endpoints) throws InterruptedException {
123            for (MockEndpoint endpoint : endpoints) {
124                endpoint.assertIsSatisfied();
125            }
126        }
127    
128    
129        /**
130         * Asserts that all the expectations on any {@link MockEndpoint} instances registered
131         * in the given context are valid
132         *
133         * @param context the camel context used to find all the available endpoints to be asserted
134         */
135        public static void assertIsSatisfied(CamelContext context) throws InterruptedException {
136            ObjectHelper.notNull(context, "camelContext");
137            Collection<Endpoint> endpoints = context.getSingletonEndpoints();
138            for (Endpoint endpoint : endpoints) {
139                if (endpoint instanceof MockEndpoint) {
140                    MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
141                    mockEndpoint.assertIsSatisfied();
142                }
143            }
144        }
145    
146    
147        public static void expectsMessageCount(int count, MockEndpoint... endpoints) throws InterruptedException {
148            for (MockEndpoint endpoint : endpoints) {
149                MockEndpoint.expectsMessageCount(count);
150            }
151        }
152    
153        public List<Exchange> getExchanges() {
154            return getReceivedExchanges();
155        }
156    
157        public void addPropertyChangeListener(PropertyChangeListener listener) {
158            propertyChangeSupport.addPropertyChangeListener(listener);
159        }
160    
161        public void removePropertyChangeListener(PropertyChangeListener listener) {
162            propertyChangeSupport.removePropertyChangeListener(listener);
163        }
164    
165        public Consumer<Exchange> createConsumer(Processor processor) throws Exception {
166            throw new UnsupportedOperationException("You cannot consume from this endpoint");
167        }
168    
169        public Producer<Exchange> createProducer() throws Exception {
170            return new DefaultProducer<Exchange>(this) {
171                public void process(Exchange exchange) {
172                    onExchange(exchange);
173                }
174            };
175        }
176    
177        public void reset() {
178            init();
179        }
180    
181    
182        // Testing API
183        // -------------------------------------------------------------------------
184    
185        /**
186         * Set the processor that will be invoked when the index
187         * message is received.
188         *
189         * @param index
190         * @param processor
191         */
192        public void whenExchangeReceived(int index, Processor processor) {
193            this.processors.put(index, processor);
194        }
195    
196        /**
197         * Set the processor that will be invoked when the some message
198         * is received.
199         *
200         * This processor could be overwritten by
201         * {@link #whenExchangeReceived(int, Processor)} method.
202         *
203         * @param processor
204         */
205        public void whenAnyExchangeReceived(Processor processor) {
206            this.defaultProcessor = processor;
207        }
208    
209        /**
210         * Validates that all the available expectations on this endpoint are
211         * satisfied; or throw an exception
212         */
213        public void assertIsSatisfied() throws InterruptedException {
214            assertIsSatisfied(sleepForEmptyTest);
215        }
216    
217        /**
218         * Validates that all the available expectations on this endpoint are
219         * satisfied; or throw an exception
220         *
221         * @param timeoutForEmptyEndpoints the timeout in milliseconds that we
222         *                should wait for the test to be true
223         */
224        public void assertIsSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
225            LOG.info("Asserting: " + this + " is satisfied");
226            if (expectedCount >= 0) {
227                if (expectedCount != getReceivedCounter()) {
228                    if (expectedCount == 0) {
229                        // lets wait a little bit just in case
230                        if (timeoutForEmptyEndpoints > 0) {
231                            LOG.debug("Sleeping for: " + timeoutForEmptyEndpoints + " millis to check there really are no messages received");
232                            Thread.sleep(timeoutForEmptyEndpoints);
233                        }
234                    } else {
235                        waitForCompleteLatch();
236                    }
237                }
238                assertEquals("Received message count", expectedCount, getReceivedCounter());
239            } else if (expectedMinimumCount > 0 && getReceivedCounter() < expectedMinimumCount) {
240                waitForCompleteLatch();
241            }
242    
243            if (expectedMinimumCount >= 0) {
244                int receivedCounter = getReceivedCounter();
245                assertTrue("Received message count " + receivedCounter + ", expected at least " + expectedCount, expectedCount <= receivedCounter);
246            }
247    
248            for (Runnable test : tests) {
249                test.run();
250            }
251    
252            for (Throwable failure : failures) {
253                if (failure != null) {
254                    LOG.error("Caught on " + getEndpointUri() + " Exception: " + failure, failure);
255                    fail("Failed due to caught exception: " + failure);
256                }
257            }
258        }
259    
260        /**
261         * Validates that the assertions fail on this endpoint
262         */
263        public void assertIsNotSatisfied() throws InterruptedException {
264            try {
265                assertIsSatisfied();
266                fail("Expected assertion failure!");
267            } catch (AssertionError e) {
268                LOG.info("Caught expected failure: " + e);
269            }
270        }
271    
272        /**
273         * Specifies the expected number of message exchanges that should be
274         * received by this endpoint
275         *
276         * @param expectedCount the number of message exchanges that should be
277         *                expected by this endpoint
278         */
279        public void expectedMessageCount(int expectedCount) {
280            setExpectedMessageCount(expectedCount);
281        }
282    
283        /**
284         * Specifies the minimum number of expected message exchanges that should be
285         * received by this endpoint
286         *
287         * @param expectedCount the number of message exchanges that should be
288         *                expected by this endpoint
289         */
290        public void expectedMinimumMessageCount(int expectedCount) {
291            setMinimumExpectedMessageCount(expectedCount);
292        }
293    
294        /**
295         * Adds an expectation that the given header name & value are received by this
296         * endpoint
297         */
298        public void expectedHeaderReceived(String name, String value) {
299            this.headerName = name;
300            this.headerValue = value;
301    
302            expects(new Runnable() {
303                public void run() {
304                    assertTrue("No header with name " + headerName + " found.", actualHeader != null);
305    
306                    assertEquals("Header of message", headerValue, actualHeader);
307                }
308            });
309        }
310    
311        /**
312         * Adds an expectation that the given body values are received by this
313         * endpoint in the specified order
314         */
315        public void expectedBodiesReceived(final List bodies) {
316            expectedMessageCount(bodies.size());
317            this.expectedBodyValues = bodies;
318            this.actualBodyValues = new ArrayList();
319    
320            expects(new Runnable() {
321                public void run() {
322                    for (int i = 0; i < expectedBodyValues.size(); i++) {
323                        Exchange exchange = getReceivedExchanges().get(i);
324                        assertTrue("No exchange received for counter: " + i, exchange != null);
325    
326                        Object expectedBody = expectedBodyValues.get(i);
327                        Object actualBody = actualBodyValues.get(i);
328    
329                        assertEquals("Body of message: " + i, expectedBody, actualBody);
330                    }
331                }
332            });
333        }
334    
335        /**
336         * Adds an expectation that the given body values are received by this
337         * endpoint
338         */
339        public void expectedBodiesReceived(Object... bodies) {
340            List bodyList = new ArrayList();
341            bodyList.addAll(Arrays.asList(bodies));
342            expectedBodiesReceived(bodyList);
343        }
344    
345        /**
346         * Adds an expectation that the given body values are received by this
347         * endpoint in any order
348         */
349        public void expectedBodiesReceivedInAnyOrder(final List bodies) {
350            expectedMessageCount(bodies.size());
351            this.expectedBodyValues = bodies;
352            this.actualBodyValues = new ArrayList();
353    
354            expects(new Runnable() {
355                public void run() {
356                    Set actualBodyValuesSet = new HashSet(actualBodyValues);
357                    for (int i = 0; i < expectedBodyValues.size(); i++) {
358                        Exchange exchange = getReceivedExchanges().get(i);
359                        assertTrue("No exchange received for counter: " + i, exchange != null);
360    
361                        Object expectedBody = expectedBodyValues.get(i);
362                        assertTrue("Message with body " + expectedBody
363                                + " was expected but not found in " + actualBodyValuesSet,
364                                actualBodyValuesSet.remove(expectedBody));
365                    }
366                }
367            });
368        }
369    
370        /**
371         * Adds an expectation that the given body values are received by this
372         * endpoint in any order
373         */
374        public void expectedBodiesReceivedInAnyOrder(Object... bodies) {
375            List bodyList = new ArrayList();
376            bodyList.addAll(Arrays.asList(bodies));
377            expectedBodiesReceivedInAnyOrder(bodyList);
378        }
379    
380        /**
381         * Adds an expectation that messages received should have ascending values
382         * of the given expression such as a user generated counter value
383         *
384         * @param expression
385         */
386        public void expectsAscending(final Expression<Exchange> expression) {
387            expects(new Runnable() {
388                public void run() {
389                    assertMessagesAscending(expression);
390                }
391            });
392        }
393    
394        /**
395         * Adds an expectation that messages received should have descending values
396         * of the given expression such as a user generated counter value
397         *
398         * @param expression
399         */
400        public void expectsDescending(final Expression<Exchange> expression) {
401            expects(new Runnable() {
402                public void run() {
403                    assertMessagesDescending(expression);
404                }
405            });
406        }
407    
408        /**
409         * Adds an expectation that no duplicate messages should be received using
410         * the expression to determine the message ID
411         *
412         * @param expression the expression used to create a unique message ID for
413         *                message comparison (which could just be the message
414         *                payload if the payload can be tested for uniqueness using
415         *                {@link Object#equals(Object)} and
416         *                {@link Object#hashCode()}
417         */
418        public void expectsNoDuplicates(final Expression<Exchange> expression) {
419            expects(new Runnable() {
420                public void run() {
421                    assertNoDuplicates(expression);
422                }
423            });
424        }
425    
426        /**
427         * Asserts that the messages have ascending values of the given expression
428         */
429        public void assertMessagesAscending(Expression<Exchange> expression) {
430            assertMessagesSorted(expression, true);
431        }
432    
433        /**
434         * Asserts that the messages have descending values of the given expression
435         */
436        public void assertMessagesDescending(Expression<Exchange> expression) {
437            assertMessagesSorted(expression, false);
438        }
439    
440        protected void assertMessagesSorted(Expression<Exchange> expression, boolean ascending) {
441            String type = ascending ? "ascending" : "descending";
442            ExpressionComparator comparator = new ExpressionComparator(expression);
443            List<Exchange> list = getReceivedExchanges();
444            for (int i = 1; i < list.size(); i++) {
445                int j = i - 1;
446                Exchange e1 = list.get(j);
447                Exchange e2 = list.get(i);
448                int result = comparator.compare(e1, e2);
449                if (result == 0) {
450                    fail("Messages not " + type + ". Messages" + j + " and " + i + " are equal with value: " + expression.evaluate(e1) + " for expression: " + expression + ". Exchanges: " + e1 + " and "
451                         + e2);
452                } else {
453                    if (!ascending) {
454                        result = result * -1;
455                    }
456                    if (result > 0) {
457                        fail("Messages not " + type + ". Message " + j + " has value: " + expression.evaluate(e1) + " and message " + i + " has value: " + expression.evaluate(e2) + " for expression: "
458                             + expression + ". Exchanges: " + e1 + " and " + e2);
459                    }
460                }
461            }
462        }
463    
464        public void assertNoDuplicates(Expression<Exchange> expression) {
465            Map<Object, Exchange> map = new HashMap<Object, Exchange>();
466            List<Exchange> list = getReceivedExchanges();
467            for (int i = 0; i < list.size(); i++) {
468                Exchange e2 = list.get(i);
469                Object key = expression.evaluate(e2);
470                Exchange e1 = map.get(key);
471                if (e1 != null) {
472                    fail("Duplicate message found on message " + i + " has value: " + key + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2);
473                } else {
474                    map.put(key, e2);
475                }
476            }
477        }
478    
479        /**
480         * Adds the expectation which will be invoked when enough messages are
481         * received
482         */
483        public void expects(Runnable runnable) {
484            tests.add(runnable);
485        }
486    
487        /**
488         * Adds an assertion to the given message index
489         *
490         * @param messageIndex the number of the message
491         * @return the assertion clause
492         */
493        public AssertionClause message(final int messageIndex) {
494            AssertionClause clause = new AssertionClause() {
495                public void run() {
496                    applyAssertionOn(MockEndpoint.this, messageIndex, assertExchangeReceived(messageIndex));
497                }
498            };
499            expects(clause);
500            return clause;
501        }
502    
503        /**
504         * Adds an assertion to all the received messages
505         *
506         * @return the assertion clause
507         */
508        public AssertionClause allMessages() {
509            AssertionClause clause = new AssertionClause() {
510                public void run() {
511                    List<Exchange> list = getReceivedExchanges();
512                    int index = 0;
513                    for (Exchange exchange : list) {
514                        applyAssertionOn(MockEndpoint.this, index++, exchange);
515                    }
516                }
517            };
518            expects(clause);
519            return clause;
520        }
521    
522        /**
523         * Asserts that the given index of message is received (starting at zero)
524         */
525        public Exchange assertExchangeReceived(int index) {
526            int count = getReceivedCounter();
527            assertTrue("Not enough messages received. Was: " + count, count > index);
528            return getReceivedExchanges().get(index);
529        }
530    
531        // Properties
532        // -------------------------------------------------------------------------
533        public List<Throwable> getFailures() {
534            return failures;
535        }
536    
537        public int getReceivedCounter() {
538            return getReceivedExchanges().size();
539        }
540    
541        public List<Exchange> getReceivedExchanges() {
542            return receivedExchanges;
543        }
544    
545        public int getExpectedCount() {
546            return expectedCount;
547        }
548    
549        public long getSleepForEmptyTest() {
550            return sleepForEmptyTest;
551        }
552    
553        /**
554         * Allows a sleep to be specified to wait to check that this endpoint really
555         * is empty when {@link #expectedMessageCount(int)} is called with zero
556         *
557         * @param sleepForEmptyTest the milliseconds to sleep for to determine that
558         *                this endpoint really is empty
559         */
560        public void setSleepForEmptyTest(long sleepForEmptyTest) {
561            this.sleepForEmptyTest = sleepForEmptyTest;
562        }
563    
564        public long getResultWaitTime() {
565            return resultWaitTime;
566        }
567    
568        /**
569         * Sets the maximum amount of time (in millis) the {@link #assertIsSatisfied()} will
570         * wait on a latch until it is satisfied
571         */
572        public void setResultWaitTime(long resultWaitTime) {
573            this.resultWaitTime = resultWaitTime;
574        }
575    
576        /**
577         * Specifies the expected number of message exchanges that should be
578         * received by this endpoint
579         *
580         * @param expectedCount the number of message exchanges that should be
581         *                expected by this endpoint
582         */
583        public void setExpectedMessageCount(int expectedCount) {
584            this.expectedCount = expectedCount;
585            if (expectedCount <= 0) {
586                latch = null;
587            } else {
588                latch = new CountDownLatch(expectedCount);
589            }
590        }
591    
592        /**
593         * Specifies the minimum number of expected message exchanges that should be
594         * received by this endpoint
595         *
596         * @param expectedCount the number of message exchanges that should be
597         *                expected by this endpoint
598         */
599        public void setMinimumExpectedMessageCount(int expectedCount) {
600            this.expectedMinimumCount = expectedCount;
601            if (expectedCount <= 0) {
602                latch = null;
603            } else {
604                latch = new CountDownLatch(expectedMinimumCount);
605            }
606        }
607    
608        public Processor getReporter() {
609            return reporter;
610        }
611    
612        /**
613         * Allows a processor to added to the endpoint to report on progress of the test
614         */
615        public void setReporter(Processor reporter) {
616            this.reporter = reporter;
617        }
618    
619        // Implementation methods
620        // -------------------------------------------------------------------------
621        private void init() {
622            expectedCount = -1;
623            counter = 0;
624            processors = new HashMap<Integer, Processor>();
625            receivedExchanges = new CopyOnWriteArrayList<Exchange>();
626            failures = new CopyOnWriteArrayList<Throwable>();
627            tests = new CopyOnWriteArrayList<Runnable>();
628            latch = null;
629            sleepForEmptyTest = 1000L;
630            resultWaitTime = 20000L;
631            expectedMinimumCount = -1;
632            expectedBodyValues = null;
633            actualBodyValues = new ArrayList();
634        }
635    
636        protected synchronized void onExchange(Exchange exchange) {
637            try {
638                if (reporter != null) {
639                    reporter.process(exchange);
640                }
641    
642                performAssertions(exchange);
643            } catch (Throwable e) {
644                failures.add(e);
645            }
646            if (latch != null) {
647                latch.countDown();
648            }
649        }
650    
651        protected void performAssertions(Exchange exchange) throws Exception {
652            Message in = exchange.getIn();
653            Object actualBody = in.getBody();
654    
655            if (headerName != null) {
656                actualHeader = in.getHeader(headerName);
657            }
658    
659            if (expectedBodyValues != null) {
660                int index = actualBodyValues.size();
661                if (expectedBodyValues.size() > index) {
662                    Object expectedBody = expectedBodyValues.get(index);
663                    if (expectedBody != null) {
664                        actualBody = in.getBody(expectedBody.getClass());
665                    }
666                    actualBodyValues.add(actualBody);
667                }
668            }
669    
670            LOG.debug(getEndpointUri() + " >>>> " + (++counter) + " : " + exchange + " with body: " + actualBody);
671    
672            receivedExchanges.add(exchange);
673    
674            Processor processor = processors.get(getReceivedCounter()) != null
675                    ? processors.get(getReceivedCounter()) : defaultProcessor;
676    
677            if (processor != null) {
678                processor.process(exchange);
679            }
680        }
681    
682        protected void waitForCompleteLatch() throws InterruptedException {
683            if (latch == null) {
684                fail("Should have a latch!");
685            }
686    
687            // now lets wait for the results
688            LOG.debug("Waiting on the latch for: " + resultWaitTime + " millis");
689            latch.await(resultWaitTime, TimeUnit.MILLISECONDS);
690        }
691    
692        protected void assertEquals(String message, Object expectedValue, Object actualValue) {
693            if (!ObjectHelper.equal(expectedValue, actualValue)) {
694                fail(message + ". Expected: <" + expectedValue + "> but was: <" + actualValue + ">");
695            }
696        }
697    
698        protected void assertTrue(String message, boolean predicate) {
699            if (!predicate) {
700                fail(message);
701            }
702        }
703    
704        protected void fail(Object message) {
705            if (LOG.isDebugEnabled()) {
706                List<Exchange> list = getReceivedExchanges();
707                int index = 0;
708                for (Exchange exchange : list) {
709                    LOG.debug("Received[" + (++index) + "]: " + exchange);
710                }
711            }
712            throw new AssertionError(getEndpointUri() + " " + message);
713        }
714    
715        public int getExpectedMinimumCount() {
716            return expectedMinimumCount;
717        }
718    
719        public void await() throws InterruptedException {
720            if (latch != null) {
721                latch.await();
722            }
723        }
724    
725        public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
726            if (latch != null) {
727                return latch.await(timeout, unit);
728            }
729            return true;
730        }
731    
732        public boolean isSingleton() {
733            return true;
734        }
735    }