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