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: 36865 $
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 }