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.io.File;
020 import java.util.ArrayList;
021 import java.util.Arrays;
022 import java.util.Collection;
023 import java.util.Date;
024 import java.util.HashMap;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.Set;
028 import java.util.concurrent.ConcurrentHashMap;
029 import java.util.concurrent.CopyOnWriteArrayList;
030 import java.util.concurrent.CopyOnWriteArraySet;
031 import java.util.concurrent.CountDownLatch;
032 import java.util.concurrent.TimeUnit;
033
034 import org.apache.camel.AsyncCallback;
035 import org.apache.camel.CamelContext;
036 import org.apache.camel.Component;
037 import org.apache.camel.Consumer;
038 import org.apache.camel.Endpoint;
039 import org.apache.camel.Exchange;
040 import org.apache.camel.ExchangePattern;
041 import org.apache.camel.Expression;
042 import org.apache.camel.Handler;
043 import org.apache.camel.Message;
044 import org.apache.camel.Predicate;
045 import org.apache.camel.Processor;
046 import org.apache.camel.Producer;
047 import org.apache.camel.builder.ProcessorBuilder;
048 import org.apache.camel.impl.DefaultAsyncProducer;
049 import org.apache.camel.impl.DefaultEndpoint;
050 import org.apache.camel.impl.InterceptSendToEndpoint;
051 import org.apache.camel.spi.BrowsableEndpoint;
052 import org.apache.camel.util.CamelContextHelper;
053 import org.apache.camel.util.CaseInsensitiveMap;
054 import org.apache.camel.util.ExchangeHelper;
055 import org.apache.camel.util.ExpressionComparator;
056 import org.apache.camel.util.FileUtil;
057 import org.apache.camel.util.ObjectHelper;
058 import org.apache.camel.util.StopWatch;
059 import org.slf4j.Logger;
060 import org.slf4j.LoggerFactory;
061
062 /**
063 * A Mock endpoint which provides a literate, fluent API for testing routes
064 * using a <a href="http://jmock.org/">JMock style</a> API.
065 * <p/>
066 * The mock endpoint have two set of methods
067 * <ul>
068 * <li>expectedXXX or expectsXXX - To set pre conditions, before the test is executed</li>
069 * <li>assertXXX - To assert assertions, after the test has been executed</li>
070 * </ul>
071 * Its <b>important</b> to know the difference between the two set. The former is used to
072 * set expectations before the test is being started (eg before the mock receives messages).
073 * The latter is used after the test has been executed, to verify the expectations; or
074 * other assertions which you can perform after the test has been completed.
075 *
076 * @version
077 */
078 public class MockEndpoint extends DefaultEndpoint implements BrowsableEndpoint {
079 private static final transient Logger LOG = LoggerFactory.getLogger(MockEndpoint.class);
080 // must be volatile so changes is visible between the thread which performs the assertions
081 // and the threads which process the exchanges when routing messages in Camel
082 protected volatile Processor reporter;
083 protected boolean copyOnExchange = true;
084 private volatile int expectedCount;
085 private volatile int counter;
086 private volatile Processor defaultProcessor;
087 private volatile Map<Integer, Processor> processors;
088 private volatile List<Exchange> receivedExchanges;
089 private volatile List<Throwable> failures;
090 private volatile List<Runnable> tests;
091 private volatile CountDownLatch latch;
092 private volatile long sleepForEmptyTest;
093 private volatile long resultWaitTime;
094 private volatile long resultMinimumWaitTime;
095 private volatile long assertPeriod;
096 private volatile int expectedMinimumCount;
097 private volatile List<?> expectedBodyValues;
098 private volatile List<Object> actualBodyValues;
099 private volatile Map<String, Object> expectedHeaderValues;
100 private volatile Map<String, Object> actualHeaderValues;
101 private volatile Map<String, Object> expectedPropertyValues;
102 private volatile Map<String, Object> actualPropertyValues;
103 private volatile int retainFirst;
104 private volatile int retainLast;
105
106 public MockEndpoint(String endpointUri, Component component) {
107 super(endpointUri, component);
108 init();
109 }
110
111 @Deprecated
112 public MockEndpoint(String endpointUri) {
113 super(endpointUri);
114 init();
115 }
116
117 public MockEndpoint() {
118 this(null);
119 }
120
121 /**
122 * A helper method to resolve the mock endpoint of the given URI on the given context
123 *
124 * @param context the camel context to try resolve the mock endpoint from
125 * @param uri the uri of the endpoint to resolve
126 * @return the endpoint
127 */
128 public static MockEndpoint resolve(CamelContext context, String uri) {
129 return CamelContextHelper.getMandatoryEndpoint(context, uri, MockEndpoint.class);
130 }
131
132 public static void assertWait(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
133 long start = System.currentTimeMillis();
134 long left = unit.toMillis(timeout);
135 long end = start + left;
136 for (MockEndpoint endpoint : endpoints) {
137 if (!endpoint.await(left, TimeUnit.MILLISECONDS)) {
138 throw new AssertionError("Timeout waiting for endpoints to receive enough messages. " + endpoint.getEndpointUri() + " timed out.");
139 }
140 left = end - System.currentTimeMillis();
141 if (left <= 0) {
142 left = 0;
143 }
144 }
145 }
146
147 public static void assertIsSatisfied(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException {
148 assertWait(timeout, unit, endpoints);
149 for (MockEndpoint endpoint : endpoints) {
150 endpoint.assertIsSatisfied();
151 }
152 }
153
154 public static void assertIsSatisfied(MockEndpoint... endpoints) throws InterruptedException {
155 for (MockEndpoint endpoint : endpoints) {
156 endpoint.assertIsSatisfied();
157 }
158 }
159
160
161 /**
162 * Asserts that all the expectations on any {@link MockEndpoint} instances registered
163 * in the given context are valid
164 *
165 * @param context the camel context used to find all the available endpoints to be asserted
166 */
167 public static void assertIsSatisfied(CamelContext context) throws InterruptedException {
168 ObjectHelper.notNull(context, "camelContext");
169 Collection<Endpoint> endpoints = context.getEndpoints();
170 for (Endpoint endpoint : endpoints) {
171 // if the endpoint was intercepted we should get the delegate
172 if (endpoint instanceof InterceptSendToEndpoint) {
173 endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
174 }
175 if (endpoint instanceof MockEndpoint) {
176 MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
177 mockEndpoint.assertIsSatisfied();
178 }
179 }
180 }
181
182 /**
183 * Asserts that all the expectations on any {@link MockEndpoint} instances registered
184 * in the given context are valid
185 *
186 * @param context the camel context used to find all the available endpoints to be asserted
187 * @param timeout timeout
188 * @param unit time unit
189 */
190 public static void assertIsSatisfied(CamelContext context, long timeout, TimeUnit unit) throws InterruptedException {
191 ObjectHelper.notNull(context, "camelContext");
192 ObjectHelper.notNull(unit, "unit");
193 Collection<Endpoint> endpoints = context.getEndpoints();
194 long millis = unit.toMillis(timeout);
195 for (Endpoint endpoint : endpoints) {
196 // if the endpoint was intercepted we should get the delegate
197 if (endpoint instanceof InterceptSendToEndpoint) {
198 endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
199 }
200 if (endpoint instanceof MockEndpoint) {
201 MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
202 mockEndpoint.setResultWaitTime(millis);
203 mockEndpoint.assertIsSatisfied();
204 }
205 }
206 }
207
208 /**
209 * Sets the assert period on all the expectations on any {@link MockEndpoint} instances registered
210 * in the given context.
211 *
212 * @param context the camel context used to find all the available endpoints
213 * @param period the period in millis
214 */
215 public static void setAssertPeriod(CamelContext context, long period) {
216 ObjectHelper.notNull(context, "camelContext");
217 Collection<Endpoint> endpoints = context.getEndpoints();
218 for (Endpoint endpoint : endpoints) {
219 // if the endpoint was intercepted we should get the delegate
220 if (endpoint instanceof InterceptSendToEndpoint) {
221 endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
222 }
223 if (endpoint instanceof MockEndpoint) {
224 MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
225 mockEndpoint.setAssertPeriod(period);
226 }
227 }
228 }
229
230 /**
231 * Reset all mock endpoints
232 *
233 * @param context the camel context used to find all the available endpoints to reset
234 */
235 public static void resetMocks(CamelContext context) {
236 ObjectHelper.notNull(context, "camelContext");
237 Collection<Endpoint> endpoints = context.getEndpoints();
238 for (Endpoint endpoint : endpoints) {
239 // if the endpoint was intercepted we should get the delegate
240 if (endpoint instanceof InterceptSendToEndpoint) {
241 endpoint = ((InterceptSendToEndpoint) endpoint).getDelegate();
242 }
243 if (endpoint instanceof MockEndpoint) {
244 MockEndpoint mockEndpoint = (MockEndpoint) endpoint;
245 mockEndpoint.reset();
246 }
247 }
248 }
249
250 public static void expectsMessageCount(int count, MockEndpoint... endpoints) throws InterruptedException {
251 for (MockEndpoint endpoint : endpoints) {
252 endpoint.setExpectedMessageCount(count);
253 }
254 }
255
256 public List<Exchange> getExchanges() {
257 return getReceivedExchanges();
258 }
259
260 public Consumer createConsumer(Processor processor) throws Exception {
261 throw new UnsupportedOperationException("You cannot consume from this endpoint");
262 }
263
264 public Producer createProducer() throws Exception {
265 return new DefaultAsyncProducer(this) {
266 public boolean process(Exchange exchange, AsyncCallback callback) {
267 onExchange(exchange);
268 callback.done(true);
269 return true;
270 }
271 };
272 }
273
274 public void reset() {
275 init();
276 }
277
278
279 // Testing API
280 // -------------------------------------------------------------------------
281
282 /**
283 * Handles the incoming exchange.
284 * <p/>
285 * This method turns this mock endpoint into a bean which you can use
286 * in the Camel routes, which allows you to inject MockEndpoint as beans
287 * in your routes and use the features of the mock to control the bean.
288 *
289 * @param exchange the exchange
290 * @throws Exception can be thrown
291 */
292 @Handler
293 public void handle(Exchange exchange) throws Exception {
294 onExchange(exchange);
295 }
296
297 /**
298 * Set the processor that will be invoked when the index
299 * message is received.
300 */
301 public void whenExchangeReceived(int index, Processor processor) {
302 this.processors.put(index, processor);
303 }
304
305 /**
306 * Set the processor that will be invoked when the some message
307 * is received.
308 *
309 * This processor could be overwritten by
310 * {@link #whenExchangeReceived(int, Processor)} method.
311 */
312 public void whenAnyExchangeReceived(Processor processor) {
313 this.defaultProcessor = processor;
314 }
315
316 /**
317 * Set the expression which value will be set to the message body
318 * @param expression which is use to set the message body
319 */
320 public void returnReplyBody(Expression expression) {
321 this.defaultProcessor = ProcessorBuilder.setBody(expression);
322 }
323
324 /**
325 * Set the expression which value will be set to the message header
326 * @param headerName that will be set value
327 * @param expression which is use to set the message header
328 */
329 public void returnReplyHeader(String headerName, Expression expression) {
330 this.defaultProcessor = ProcessorBuilder.setHeader(headerName, expression);
331 }
332
333
334 /**
335 * Validates that all the available expectations on this endpoint are
336 * satisfied; or throw an exception
337 */
338 public void assertIsSatisfied() throws InterruptedException {
339 assertIsSatisfied(sleepForEmptyTest);
340 }
341
342 /**
343 * Validates that all the available expectations on this endpoint are
344 * satisfied; or throw an exception
345 *
346 * @param timeoutForEmptyEndpoints the timeout in milliseconds that we
347 * should wait for the test to be true
348 */
349 public void assertIsSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
350 LOG.info("Asserting: " + this + " is satisfied");
351 doAssertIsSatisfied(timeoutForEmptyEndpoints);
352 if (assertPeriod > 0) {
353 // if an assert period was set then re-assert again to ensure the assertion is still valid
354 Thread.sleep(assertPeriod);
355 LOG.info("Re-asserting: " + this + " is satisfied after " + assertPeriod + " millis");
356 // do not use timeout when we re-assert
357 doAssertIsSatisfied(0);
358 }
359 }
360
361 protected void doAssertIsSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
362 if (expectedCount == 0) {
363 if (timeoutForEmptyEndpoints > 0) {
364 LOG.debug("Sleeping for: " + timeoutForEmptyEndpoints + " millis to check there really are no messages received");
365 Thread.sleep(timeoutForEmptyEndpoints);
366 }
367 assertEquals("Received message count", expectedCount, getReceivedCounter());
368 } else if (expectedCount > 0) {
369 if (expectedCount != getReceivedCounter()) {
370 waitForCompleteLatch();
371 }
372 assertEquals("Received message count", expectedCount, getReceivedCounter());
373 } else if (expectedMinimumCount > 0 && getReceivedCounter() < expectedMinimumCount) {
374 waitForCompleteLatch();
375 }
376
377 if (expectedMinimumCount >= 0) {
378 int receivedCounter = getReceivedCounter();
379 assertTrue("Received message count " + receivedCounter + ", expected at least " + expectedMinimumCount, expectedMinimumCount <= receivedCounter);
380 }
381
382 for (Runnable test : tests) {
383 test.run();
384 }
385
386 for (Throwable failure : failures) {
387 if (failure != null) {
388 LOG.error("Caught on " + getEndpointUri() + " Exception: " + failure, failure);
389 fail("Failed due to caught exception: " + failure);
390 }
391 }
392 }
393
394 /**
395 * Validates that the assertions fail on this endpoint
396 */
397 public void assertIsNotSatisfied() throws InterruptedException {
398 boolean failed = false;
399 try {
400 assertIsSatisfied();
401 // did not throw expected error... fail!
402 failed = true;
403 } catch (AssertionError e) {
404 LOG.info("Caught expected failure: " + e);
405 }
406 if (failed) {
407 // fail() throws the AssertionError to indicate the test failed.
408 fail("Expected assertion failure but test succeeded!");
409 }
410 }
411
412 /**
413 * Validates that the assertions fail on this endpoint
414
415 * @param timeoutForEmptyEndpoints the timeout in milliseconds that we
416 * should wait for the test to be true
417 */
418 public void assertIsNotSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException {
419 boolean failed = false;
420 try {
421 assertIsSatisfied(timeoutForEmptyEndpoints);
422 // did not throw expected error... fail!
423 failed = true;
424 } catch (AssertionError e) {
425 LOG.info("Caught expected failure: " + e);
426 }
427 if (failed) {
428 // fail() throws the AssertionError to indicate the test failed.
429 fail("Expected assertion failure but test succeeded!");
430 }
431 }
432
433 /**
434 * Specifies the expected number of message exchanges that should be
435 * received by this endpoint
436 *
437 * @param expectedCount the number of message exchanges that should be
438 * expected by this endpoint
439 */
440 public void expectedMessageCount(int expectedCount) {
441 setExpectedMessageCount(expectedCount);
442 }
443
444 /**
445 * Sets a grace period after which the mock endpoint will re-assert
446 * to ensure the preliminary assertion is still valid.
447 * <p/>
448 * This is used for example to assert that <b>exactly</b> a number of messages
449 * arrives. For example if {@link #expectedMessageCount(int)} was set to 5, then
450 * the assertion is satisfied when 5 or more message arrives. To ensure that
451 * exactly 5 messages arrives, then you would need to wait a little period
452 * to ensure no further message arrives. This is what you can use this
453 * {@link #setAssertPeriod(long)} method for.
454 * <p/>
455 * By default this period is disabled.
456 *
457 * @param period grace period in millis
458 */
459 public void setAssertPeriod(long period) {
460 this.assertPeriod = period;
461 }
462
463 /**
464 * Specifies the minimum number of expected message exchanges that should be
465 * received by this endpoint
466 *
467 * @param expectedCount the number of message exchanges that should be
468 * expected by this endpoint
469 */
470 public void expectedMinimumMessageCount(int expectedCount) {
471 setMinimumExpectedMessageCount(expectedCount);
472 }
473
474 /**
475 * Sets an expectation that the given header name & value are received by this endpoint
476 * <p/>
477 * You can set multiple expectations for different header names.
478 * If you set a value of <tt>null</tt> that means we accept either the header is absent, or its value is <tt>null</tt>
479 */
480 public void expectedHeaderReceived(final String name, final Object value) {
481 if (expectedHeaderValues == null) {
482 expectedHeaderValues = new CaseInsensitiveMap();
483 // we just wants to expects to be called once
484 expects(new Runnable() {
485 public void run() {
486 for (int i = 0; i < getReceivedExchanges().size(); i++) {
487 Exchange exchange = getReceivedExchange(i);
488 for (Map.Entry<String, Object> entry : expectedHeaderValues.entrySet()) {
489 String key = entry.getKey();
490 Object expectedValue = entry.getValue();
491
492 // we accept that an expectedValue of null also means that the header may be absent
493 if (expectedValue != null) {
494 assertTrue("Exchange " + i + " has no headers", exchange.getIn().hasHeaders());
495 boolean hasKey = exchange.getIn().getHeaders().containsKey(key);
496 assertTrue("No header with name " + key + " found for message: " + i, hasKey);
497 }
498
499 Object actualValue = exchange.getIn().getHeader(key);
500 actualValue = extractActualValue(exchange, actualValue, expectedValue);
501
502 assertEquals("Header with name " + key + " for message: " + i, expectedValue, actualValue);
503 }
504 }
505 }
506 });
507 }
508 expectedHeaderValues.put(name, value);
509 }
510
511 /**
512 * Adds an expectation that the given header values are received by this
513 * endpoint in any order
514 */
515 public void expectedHeaderValuesReceivedInAnyOrder(final String name, final List<?> values) {
516 expectedMessageCount(values.size());
517
518 expects(new Runnable() {
519 public void run() {
520 // these are the expected values to find
521 final Set<Object> actualHeaderValues = new CopyOnWriteArraySet<Object>(values);
522
523 for (int i = 0; i < getReceivedExchanges().size(); i++) {
524 Exchange exchange = getReceivedExchange(i);
525
526 Object actualValue = exchange.getIn().getHeader(name);
527 for (Object expectedValue : actualHeaderValues) {
528 actualValue = extractActualValue(exchange, actualValue, expectedValue);
529 // remove any found values
530 actualHeaderValues.remove(actualValue);
531 }
532 }
533
534 // should be empty, as we should find all the values
535 assertTrue("Expected " + values.size() + " headers with key[" + name + "], received " + (values.size() - actualHeaderValues.size())
536 + " headers. Expected header values: " + actualHeaderValues, actualHeaderValues.isEmpty());
537 }
538 });
539 }
540
541 /**
542 * Adds an expectation that the given header values are received by this
543 * endpoint in any order
544 */
545 public void expectedHeaderValuesReceivedInAnyOrder(String name, Object... values) {
546 List<Object> valueList = new ArrayList<Object>();
547 valueList.addAll(Arrays.asList(values));
548 expectedHeaderValuesReceivedInAnyOrder(name, valueList);
549 }
550
551 /**
552 * Sets an expectation that the given property name & value are received by this endpoint
553 * <p/>
554 * You can set multiple expectations for different property names.
555 * If you set a value of <tt>null</tt> that means we accept either the property is absent, or its value is <tt>null</tt>
556 */
557 public void expectedPropertyReceived(final String name, final Object value) {
558 if (expectedPropertyValues == null) {
559 expectedPropertyValues = new ConcurrentHashMap<String, Object>();
560 }
561 if (value != null) {
562 // ConcurrentHashMap cannot store null values
563 expectedPropertyValues.put(name, value);
564 }
565
566 expects(new Runnable() {
567 public void run() {
568 for (int i = 0; i < getReceivedExchanges().size(); i++) {
569 Exchange exchange = getReceivedExchange(i);
570 for (Map.Entry<String, Object> entry : expectedPropertyValues.entrySet()) {
571 String key = entry.getKey();
572 Object expectedValue = entry.getValue();
573
574 // we accept that an expectedValue of null also means that the header may be absent
575 if (expectedValue != null) {
576 assertTrue("Exchange " + i + " has no properties", !exchange.getProperties().isEmpty());
577 boolean hasKey = exchange.getProperties().containsKey(key);
578 assertTrue("No property with name " + key + " found for message: " + i, hasKey);
579 }
580
581 Object actualValue = exchange.getProperty(key);
582 actualValue = extractActualValue(exchange, actualValue, expectedValue);
583
584 assertEquals("Property with name " + key + " for message: " + i, expectedValue, actualValue);
585 }
586 }
587 }
588 });
589 }
590
591 /**
592 * Adds an expectation that the given body values are received by this
593 * endpoint in the specified order
594 */
595 public void expectedBodiesReceived(final List<?> bodies) {
596 expectedMessageCount(bodies.size());
597 this.expectedBodyValues = bodies;
598 this.actualBodyValues = new ArrayList<Object>();
599
600 expects(new Runnable() {
601 public void run() {
602 for (int i = 0; i < expectedBodyValues.size(); i++) {
603 Exchange exchange = getReceivedExchange(i);
604 assertTrue("No exchange received for counter: " + i, exchange != null);
605
606 Object expectedBody = expectedBodyValues.get(i);
607 Object actualBody = null;
608 if (i < actualBodyValues.size()) {
609 actualBody = actualBodyValues.get(i);
610 }
611 actualBody = extractActualValue(exchange, actualBody, expectedBody);
612
613 assertEquals("Body of message: " + i, expectedBody, actualBody);
614 }
615 }
616 });
617 }
618
619 private Object extractActualValue(Exchange exchange, Object actualValue, Object expectedValue) {
620 if (actualValue == null) {
621 return null;
622 }
623
624 if (actualValue instanceof Expression) {
625 actualValue = ((Expression)actualValue).evaluate(exchange, expectedValue != null ? expectedValue.getClass() : Object.class);
626 } else if (actualValue instanceof Predicate) {
627 actualValue = ((Predicate)actualValue).matches(exchange);
628 } else if (expectedValue != null) {
629 String from = actualValue.getClass().getName();
630 String to = expectedValue.getClass().getName();
631 actualValue = getCamelContext().getTypeConverter().convertTo(expectedValue.getClass(), actualValue);
632 assertTrue("There is no type conversion possible from " + from + " to " + to, actualValue != null);
633 }
634 return actualValue;
635 }
636
637 /**
638 * Sets an expectation that the given predicates matches the received messages by this endpoint
639 */
640 public void expectedMessagesMatches(Predicate... predicates) {
641 for (int i = 0; i < predicates.length; i++) {
642 final int messageIndex = i;
643 final Predicate predicate = predicates[i];
644 final AssertionClause clause = new AssertionClause(this) {
645 public void run() {
646 addPredicate(predicate);
647 applyAssertionOn(MockEndpoint.this, messageIndex, assertExchangeReceived(messageIndex));
648 }
649 };
650 expects(clause);
651 }
652 }
653
654 /**
655 * Sets an expectation that the given body values are received by this endpoint
656 */
657 public void expectedBodiesReceived(Object... bodies) {
658 List<Object> bodyList = new ArrayList<Object>();
659 bodyList.addAll(Arrays.asList(bodies));
660 expectedBodiesReceived(bodyList);
661 }
662
663 /**
664 * Adds an expectation that the given body value are received by this endpoint
665 */
666 public AssertionClause expectedBodyReceived() {
667 expectedMessageCount(1);
668 final AssertionClause clause = new AssertionClause(this) {
669 public void run() {
670 Exchange exchange = getReceivedExchange(0);
671 assertTrue("No exchange received for counter: " + 0, exchange != null);
672
673 Object actualBody = exchange.getIn().getBody();
674 Expression exp = createExpression(getCamelContext());
675 Object expectedBody = exp.evaluate(exchange, Object.class);
676
677 assertEquals("Body of message: " + 0, expectedBody, actualBody);
678 }
679 };
680 expects(clause);
681 return clause;
682 }
683
684 /**
685 * Adds an expectation that the given body values are received by this
686 * endpoint in any order
687 */
688 public void expectedBodiesReceivedInAnyOrder(final List<?> bodies) {
689 expectedMessageCount(bodies.size());
690 this.expectedBodyValues = bodies;
691 this.actualBodyValues = new ArrayList<Object>();
692
693 expects(new Runnable() {
694 public void run() {
695 List<Object> actualBodyValuesSet = new ArrayList<Object>(actualBodyValues);
696 for (int i = 0; i < expectedBodyValues.size(); i++) {
697 Exchange exchange = getReceivedExchange(i);
698 assertTrue("No exchange received for counter: " + i, exchange != null);
699
700 Object expectedBody = expectedBodyValues.get(i);
701 assertTrue("Message with body " + expectedBody + " was expected but not found in " + actualBodyValuesSet, actualBodyValuesSet.remove(expectedBody));
702 }
703 }
704 });
705 }
706
707 /**
708 * Adds an expectation that the given body values are received by this
709 * endpoint in any order
710 */
711 public void expectedBodiesReceivedInAnyOrder(Object... bodies) {
712 List<Object> bodyList = new ArrayList<Object>();
713 bodyList.addAll(Arrays.asList(bodies));
714 expectedBodiesReceivedInAnyOrder(bodyList);
715 }
716
717 /**
718 * Adds an expectation that a file exists with the given name
719 *
720 * @param name name of file, will cater for / and \ on different OS platforms
721 */
722 public void expectedFileExists(final String name) {
723 expectedFileExists(name, null);
724 }
725
726 /**
727 * Adds an expectation that a file exists with the given name
728 * <p/>
729 * Will wait at most 5 seconds while checking for the existence of the file.
730 *
731 * @param name name of file, will cater for / and \ on different OS platforms
732 * @param content content of file to compare, can be <tt>null</tt> to not compare content
733 */
734 public void expectedFileExists(final String name, final String content) {
735 final File file = new File(FileUtil.normalizePath(name)).getAbsoluteFile();
736
737 expects(new Runnable() {
738 public void run() {
739 // wait at most 5 seconds for the file to exists
740 final long timeout = System.currentTimeMillis() + 5000;
741
742 boolean stop = false;
743 while (!stop && !file.exists()) {
744 try {
745 Thread.sleep(50);
746 } catch (InterruptedException e) {
747 // ignore
748 }
749 stop = System.currentTimeMillis() > timeout;
750 }
751
752 assertTrue("The file should exists: " + name, file.exists());
753
754 if (content != null) {
755 String body = getCamelContext().getTypeConverter().convertTo(String.class, file);
756 assertEquals("Content of file: " + name, content, body);
757 }
758 }
759 });
760 }
761
762 /**
763 * Adds an expectation that messages received should have the given exchange pattern
764 */
765 public void expectedExchangePattern(final ExchangePattern exchangePattern) {
766 expectedMessagesMatches(new Predicate() {
767 public boolean matches(Exchange exchange) {
768 return exchange.getPattern().equals(exchangePattern);
769 }
770 });
771 }
772
773 /**
774 * Adds an expectation that messages received should have ascending values
775 * of the given expression such as a user generated counter value
776 */
777 public void expectsAscending(final Expression expression) {
778 expects(new Runnable() {
779 public void run() {
780 assertMessagesAscending(expression);
781 }
782 });
783 }
784
785 /**
786 * Adds an expectation that messages received should have ascending values
787 * of the given expression such as a user generated counter value
788 */
789 public AssertionClause expectsAscending() {
790 final AssertionClause clause = new AssertionClause(this) {
791 public void run() {
792 assertMessagesAscending(createExpression(getCamelContext()));
793 }
794 };
795 expects(clause);
796 return clause;
797 }
798
799 /**
800 * Adds an expectation that messages received should have descending values
801 * of the given expression such as a user generated counter value
802 */
803 public void expectsDescending(final Expression expression) {
804 expects(new Runnable() {
805 public void run() {
806 assertMessagesDescending(expression);
807 }
808 });
809 }
810
811 /**
812 * Adds an expectation that messages received should have descending values
813 * of the given expression such as a user generated counter value
814 */
815 public AssertionClause expectsDescending() {
816 final AssertionClause clause = new AssertionClause(this) {
817 public void run() {
818 assertMessagesDescending(createExpression(getCamelContext()));
819 }
820 };
821 expects(clause);
822 return clause;
823 }
824
825 /**
826 * Adds an expectation that no duplicate messages should be received using
827 * the expression to determine the message ID
828 *
829 * @param expression the expression used to create a unique message ID for
830 * message comparison (which could just be the message
831 * payload if the payload can be tested for uniqueness using
832 * {@link Object#equals(Object)} and
833 * {@link Object#hashCode()}
834 */
835 public void expectsNoDuplicates(final Expression expression) {
836 expects(new Runnable() {
837 public void run() {
838 assertNoDuplicates(expression);
839 }
840 });
841 }
842
843 /**
844 * Adds an expectation that no duplicate messages should be received using
845 * the expression to determine the message ID
846 */
847 public AssertionClause expectsNoDuplicates() {
848 final AssertionClause clause = new AssertionClause(this) {
849 public void run() {
850 assertNoDuplicates(createExpression(getCamelContext()));
851 }
852 };
853 expects(clause);
854 return clause;
855 }
856
857 /**
858 * Asserts that the messages have ascending values of the given expression
859 */
860 public void assertMessagesAscending(Expression expression) {
861 assertMessagesSorted(expression, true);
862 }
863
864 /**
865 * Asserts that the messages have descending values of the given expression
866 */
867 public void assertMessagesDescending(Expression expression) {
868 assertMessagesSorted(expression, false);
869 }
870
871 protected void assertMessagesSorted(Expression expression, boolean ascending) {
872 String type = ascending ? "ascending" : "descending";
873 ExpressionComparator comparator = new ExpressionComparator(expression);
874 List<Exchange> list = getReceivedExchanges();
875 for (int i = 1; i < list.size(); i++) {
876 int j = i - 1;
877 Exchange e1 = list.get(j);
878 Exchange e2 = list.get(i);
879 int result = comparator.compare(e1, e2);
880 if (result == 0) {
881 fail("Messages not " + type + ". Messages" + j + " and " + i + " are equal with value: "
882 + expression.evaluate(e1, Object.class) + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2);
883 } else {
884 if (!ascending) {
885 result = result * -1;
886 }
887 if (result > 0) {
888 fail("Messages not " + type + ". Message " + j + " has value: " + expression.evaluate(e1, Object.class)
889 + " and message " + i + " has value: " + expression.evaluate(e2, Object.class) + " for expression: "
890 + expression + ". Exchanges: " + e1 + " and " + e2);
891 }
892 }
893 }
894 }
895
896 public void assertNoDuplicates(Expression expression) {
897 Map<Object, Exchange> map = new HashMap<Object, Exchange>();
898 List<Exchange> list = getReceivedExchanges();
899 for (int i = 0; i < list.size(); i++) {
900 Exchange e2 = list.get(i);
901 Object key = expression.evaluate(e2, Object.class);
902 Exchange e1 = map.get(key);
903 if (e1 != null) {
904 fail("Duplicate message found on message " + i + " has value: " + key + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2);
905 } else {
906 map.put(key, e2);
907 }
908 }
909 }
910
911 /**
912 * Adds the expectation which will be invoked when enough messages are received
913 */
914 public void expects(Runnable runnable) {
915 tests.add(runnable);
916 }
917
918 /**
919 * Adds an assertion to the given message index
920 *
921 * @param messageIndex the number of the message
922 * @return the assertion clause
923 */
924 public AssertionClause message(final int messageIndex) {
925 final AssertionClause clause = new AssertionClause(this) {
926 public void run() {
927 applyAssertionOn(MockEndpoint.this, messageIndex, assertExchangeReceived(messageIndex));
928 }
929 };
930 expects(clause);
931 return clause;
932 }
933
934 /**
935 * Adds an assertion to all the received messages
936 *
937 * @return the assertion clause
938 */
939 public AssertionClause allMessages() {
940 final AssertionClause clause = new AssertionClause(this) {
941 public void run() {
942 List<Exchange> list = getReceivedExchanges();
943 int index = 0;
944 for (Exchange exchange : list) {
945 applyAssertionOn(MockEndpoint.this, index++, exchange);
946 }
947 }
948 };
949 expects(clause);
950 return clause;
951 }
952
953 /**
954 * Asserts that the given index of message is received (starting at zero)
955 */
956 public Exchange assertExchangeReceived(int index) {
957 int count = getReceivedCounter();
958 assertTrue("Not enough messages received. Was: " + count, count > index);
959 return getReceivedExchange(index);
960 }
961
962 // Properties
963 // -------------------------------------------------------------------------
964 public List<Throwable> getFailures() {
965 return failures;
966 }
967
968 public int getReceivedCounter() {
969 return counter;
970 }
971
972 public List<Exchange> getReceivedExchanges() {
973 return receivedExchanges;
974 }
975
976 public int getExpectedCount() {
977 return expectedCount;
978 }
979
980 public long getSleepForEmptyTest() {
981 return sleepForEmptyTest;
982 }
983
984 /**
985 * Allows a sleep to be specified to wait to check that this endpoint really
986 * is empty when {@link #expectedMessageCount(int)} is called with zero
987 *
988 * @param sleepForEmptyTest the milliseconds to sleep for to determine that
989 * this endpoint really is empty
990 */
991 public void setSleepForEmptyTest(long sleepForEmptyTest) {
992 this.sleepForEmptyTest = sleepForEmptyTest;
993 }
994
995 public long getResultWaitTime() {
996 return resultWaitTime;
997 }
998
999 /**
1000 * Sets the maximum amount of time (in millis) the {@link #assertIsSatisfied()} will
1001 * wait on a latch until it is satisfied
1002 */
1003 public void setResultWaitTime(long resultWaitTime) {
1004 this.resultWaitTime = resultWaitTime;
1005 }
1006
1007 /**
1008 * Sets the minimum expected amount of time (in millis) the {@link #assertIsSatisfied()} will
1009 * wait on a latch until it is satisfied
1010 */
1011 public void setMinimumResultWaitTime(long resultMinimumWaitTime) {
1012 this.resultMinimumWaitTime = resultMinimumWaitTime;
1013 }
1014
1015 /**
1016 * Specifies the expected number of message exchanges that should be
1017 * received by this endpoint.
1018 * <p/>
1019 * If you want to assert that <b>exactly</b> n'th message arrives to this mock
1020 * endpoint, then see also the {@link #setAssertPeriod(long)} method for further details.
1021 *
1022 * @param expectedCount the number of message exchanges that should be
1023 * expected by this endpoint
1024 * @see #setAssertPeriod(long)
1025 */
1026 public void setExpectedMessageCount(int expectedCount) {
1027 this.expectedCount = expectedCount;
1028 if (expectedCount <= 0) {
1029 latch = null;
1030 } else {
1031 latch = new CountDownLatch(expectedCount);
1032 }
1033 }
1034
1035 /**
1036 * Specifies the minimum number of expected message exchanges that should be
1037 * received by this endpoint
1038 *
1039 * @param expectedCount the number of message exchanges that should be
1040 * expected by this endpoint
1041 */
1042 public void setMinimumExpectedMessageCount(int expectedCount) {
1043 this.expectedMinimumCount = expectedCount;
1044 if (expectedCount <= 0) {
1045 latch = null;
1046 } else {
1047 latch = new CountDownLatch(expectedMinimumCount);
1048 }
1049 }
1050
1051 public Processor getReporter() {
1052 return reporter;
1053 }
1054
1055 /**
1056 * Allows a processor to added to the endpoint to report on progress of the test
1057 */
1058 public void setReporter(Processor reporter) {
1059 this.reporter = reporter;
1060 }
1061
1062 /**
1063 * Specifies to only retain the first n'th number of received {@link Exchange}s.
1064 * <p/>
1065 * This is used when testing with big data, to reduce memory consumption by not storing
1066 * copies of every {@link Exchange} this mock endpoint receives.
1067 * <p/>
1068 * <b>Important:</b> When using this limitation, then the {@link #getReceivedCounter()}
1069 * will still return the actual number of received {@link Exchange}s. For example
1070 * if we have received 5000 {@link Exchange}s, and have configured to only retain the first
1071 * 10 {@link Exchange}s, then the {@link #getReceivedCounter()} will still return <tt>5000</tt>
1072 * but there is only the first 10 {@link Exchange}s in the {@link #getExchanges()} and
1073 * {@link #getReceivedExchanges()} methods.
1074 * <p/>
1075 * When using this method, then some of the other expectation methods is not supported,
1076 * for example the {@link #expectedBodiesReceived(Object...)} sets a expectation on the first
1077 * number of bodies received.
1078 * <p/>
1079 * You can configure both {@link #setRetainFirst(int)} and {@link #setRetainLast(int)} methods,
1080 * to limit both the first and last received.
1081 *
1082 * @param retainFirst to limit and only keep the first n'th received {@link Exchange}s, use
1083 * <tt>0</tt> to not retain any messages, or <tt>-1</tt> to retain all.
1084 * @see #setRetainLast(int)
1085 */
1086 public void setRetainFirst(int retainFirst) {
1087 this.retainFirst = retainFirst;
1088 }
1089
1090 /**
1091 * Specifies to only retain the last n'th number of received {@link Exchange}s.
1092 * <p/>
1093 * This is used when testing with big data, to reduce memory consumption by not storing
1094 * copies of every {@link Exchange} this mock endpoint receives.
1095 * <p/>
1096 * <b>Important:</b> When using this limitation, then the {@link #getReceivedCounter()}
1097 * will still return the actual number of received {@link Exchange}s. For example
1098 * if we have received 5000 {@link Exchange}s, and have configured to only retain the last
1099 * 20 {@link Exchange}s, then the {@link #getReceivedCounter()} will still return <tt>5000</tt>
1100 * but there is only the last 20 {@link Exchange}s in the {@link #getExchanges()} and
1101 * {@link #getReceivedExchanges()} methods.
1102 * <p/>
1103 * When using this method, then some of the other expectation methods is not supported,
1104 * for example the {@link #expectedBodiesReceived(Object...)} sets a expectation on the first
1105 * number of bodies received.
1106 * <p/>
1107 * You can configure both {@link #setRetainFirst(int)} and {@link #setRetainLast(int)} methods,
1108 * to limit both the first and last received.
1109 *
1110 * @param retainLast to limit and only keep the last n'th received {@link Exchange}s, use
1111 * <tt>0</tt> to not retain any messages, or <tt>-1</tt> to retain all.
1112 * @see #setRetainFirst(int)
1113 */
1114 public void setRetainLast(int retainLast) {
1115 this.retainLast = retainLast;
1116 }
1117
1118 // Implementation methods
1119 // -------------------------------------------------------------------------
1120 private void init() {
1121 expectedCount = -1;
1122 counter = 0;
1123 defaultProcessor = null;
1124 processors = new HashMap<Integer, Processor>();
1125 receivedExchanges = new CopyOnWriteArrayList<Exchange>();
1126 failures = new CopyOnWriteArrayList<Throwable>();
1127 tests = new CopyOnWriteArrayList<Runnable>();
1128 latch = null;
1129 sleepForEmptyTest = 0;
1130 resultWaitTime = 0;
1131 resultMinimumWaitTime = 0L;
1132 assertPeriod = 0L;
1133 expectedMinimumCount = -1;
1134 expectedBodyValues = null;
1135 actualBodyValues = new ArrayList<Object>();
1136 expectedHeaderValues = null;
1137 actualHeaderValues = null;
1138 expectedPropertyValues = null;
1139 actualPropertyValues = null;
1140 retainFirst = -1;
1141 retainLast = -1;
1142 }
1143
1144 protected synchronized void onExchange(Exchange exchange) {
1145 try {
1146 if (reporter != null) {
1147 reporter.process(exchange);
1148 }
1149 Exchange copy = exchange;
1150 if (copyOnExchange) {
1151 // copy the exchange so the mock stores the copy and not the actual exchange
1152 copy = ExchangeHelper.createCopy(exchange, true);
1153 }
1154 performAssertions(exchange, copy);
1155 } catch (Throwable e) {
1156 // must catch java.lang.Throwable as AssertionError extends java.lang.Error
1157 failures.add(e);
1158 } finally {
1159 // make sure latch is counted down to avoid test hanging forever
1160 if (latch != null) {
1161 latch.countDown();
1162 }
1163 }
1164 }
1165
1166 /**
1167 * Performs the assertions on the incoming exchange.
1168 *
1169 * @param exchange the actual exchange
1170 * @param copy a copy of the exchange (only store this)
1171 * @throws Exception can be thrown if something went wrong
1172 */
1173 protected void performAssertions(Exchange exchange, Exchange copy) throws Exception {
1174 Message in = copy.getIn();
1175 Object actualBody = in.getBody();
1176
1177 if (expectedHeaderValues != null) {
1178 if (actualHeaderValues == null) {
1179 actualHeaderValues = new CaseInsensitiveMap();
1180 }
1181 if (in.hasHeaders()) {
1182 actualHeaderValues.putAll(in.getHeaders());
1183 }
1184 }
1185
1186 if (expectedPropertyValues != null) {
1187 if (actualPropertyValues == null) {
1188 actualPropertyValues = new ConcurrentHashMap<String, Object>();
1189 }
1190 actualPropertyValues.putAll(copy.getProperties());
1191 }
1192
1193 if (expectedBodyValues != null) {
1194 int index = actualBodyValues.size();
1195 if (expectedBodyValues.size() > index) {
1196 Object expectedBody = expectedBodyValues.get(index);
1197 if (expectedBody != null) {
1198 // prefer to convert body early, for example when using files
1199 // we need to read the content at this time
1200 Object body = in.getBody(expectedBody.getClass());
1201 if (body != null) {
1202 actualBody = body;
1203 }
1204 }
1205 actualBodyValues.add(actualBody);
1206 }
1207 }
1208
1209 // let counter be 0 index-based in the logs
1210 if (LOG.isDebugEnabled()) {
1211 String msg = getEndpointUri() + " >>>> " + counter + " : " + copy + " with body: " + actualBody;
1212 if (copy.getIn().hasHeaders()) {
1213 msg += " and headers:" + copy.getIn().getHeaders();
1214 }
1215 LOG.debug(msg);
1216 }
1217
1218 // record timestamp when exchange was received
1219 copy.setProperty(Exchange.RECEIVED_TIMESTAMP, new Date());
1220
1221 // add a copy of the received exchange
1222 addReceivedExchange(copy);
1223 // and then increment counter after adding received exchange
1224 ++counter;
1225
1226 Processor processor = processors.get(getReceivedCounter()) != null
1227 ? processors.get(getReceivedCounter()) : defaultProcessor;
1228
1229 if (processor != null) {
1230 try {
1231 // must process the incoming exchange and NOT the copy as the idea
1232 // is the end user can manipulate the exchange
1233 processor.process(exchange);
1234 } catch (Exception e) {
1235 // set exceptions on exchange so we can throw exceptions to simulate errors
1236 exchange.setException(e);
1237 }
1238 }
1239 }
1240
1241 /**
1242 * Adds the received exchange.
1243 *
1244 * @param copy a copy of the received exchange
1245 */
1246 protected void addReceivedExchange(Exchange copy) {
1247 if (retainFirst == 0 && retainLast == 0) {
1248 // do not retain any messages at all
1249 } else if (retainFirst < 0 && retainLast < 0) {
1250 // no limitation so keep them all
1251 receivedExchanges.add(copy);
1252 } else {
1253 // okay there is some sort of limitations, so figure out what to retain
1254 if (retainFirst > 0 && counter < retainFirst) {
1255 // store a copy as its within the retain first limitation
1256 receivedExchanges.add(copy);
1257 } else if (retainLast > 0) {
1258 // remove the oldest from the last retained boundary,
1259 int index = receivedExchanges.size() - retainLast;
1260 if (index >= 0) {
1261 // but must be outside the first range as well
1262 // otherwise we should not remove the oldest
1263 if (retainFirst <= 0 || retainFirst <= index) {
1264 receivedExchanges.remove(index);
1265 }
1266 }
1267 // store a copy of the last n'th received
1268 receivedExchanges.add(copy);
1269 }
1270 }
1271 }
1272
1273 protected void waitForCompleteLatch() throws InterruptedException {
1274 if (latch == null) {
1275 fail("Should have a latch!");
1276 }
1277
1278 StopWatch watch = new StopWatch();
1279 waitForCompleteLatch(resultWaitTime);
1280 long delta = watch.stop();
1281 LOG.debug("Took {} millis to complete latch", delta);
1282
1283 if (resultMinimumWaitTime > 0 && delta < resultMinimumWaitTime) {
1284 fail("Expected minimum " + resultMinimumWaitTime
1285 + " millis waiting on the result, but was faster with " + delta + " millis.");
1286 }
1287 }
1288
1289 protected void waitForCompleteLatch(long timeout) throws InterruptedException {
1290 // Wait for a default 10 seconds if resultWaitTime is not set
1291 long waitTime = timeout == 0 ? 10000L : timeout;
1292
1293 // now let's wait for the results
1294 LOG.debug("Waiting on the latch for: " + timeout + " millis");
1295 latch.await(waitTime, TimeUnit.MILLISECONDS);
1296 }
1297
1298 protected void assertEquals(String message, Object expectedValue, Object actualValue) {
1299 if (!ObjectHelper.equal(expectedValue, actualValue)) {
1300 fail(message + ". Expected: <" + expectedValue + "> but was: <" + actualValue + ">");
1301 }
1302 }
1303
1304 protected void assertTrue(String message, boolean predicate) {
1305 if (!predicate) {
1306 fail(message);
1307 }
1308 }
1309
1310 protected void fail(Object message) {
1311 if (LOG.isDebugEnabled()) {
1312 List<Exchange> list = getReceivedExchanges();
1313 int index = 0;
1314 for (Exchange exchange : list) {
1315 LOG.debug("{} failed and received[{}]: {}", new Object[]{getEndpointUri(), ++index, exchange});
1316 }
1317 }
1318 throw new AssertionError(getEndpointUri() + " " + message);
1319 }
1320
1321 public int getExpectedMinimumCount() {
1322 return expectedMinimumCount;
1323 }
1324
1325 public void await() throws InterruptedException {
1326 if (latch != null) {
1327 latch.await();
1328 }
1329 }
1330
1331 public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
1332 if (latch != null) {
1333 return latch.await(timeout, unit);
1334 }
1335 return true;
1336 }
1337
1338 public boolean isSingleton() {
1339 return true;
1340 }
1341
1342 public boolean isLenientProperties() {
1343 return true;
1344 }
1345
1346 private Exchange getReceivedExchange(int index) {
1347 if (index <= receivedExchanges.size() - 1) {
1348 return receivedExchanges.get(index);
1349 } else {
1350 return null;
1351 }
1352 }
1353
1354 }