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 }