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