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 }