001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.camel.component.mock; 018 019 import java.beans.PropertyChangeListener; 020 import java.beans.PropertyChangeSupport; 021 import java.util.ArrayList; 022 import java.util.Arrays; 023 import java.util.Collection; 024 import java.util.HashMap; 025 import java.util.HashSet; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.Set; 029 import java.util.concurrent.CopyOnWriteArrayList; 030 import java.util.concurrent.CountDownLatch; 031 import java.util.concurrent.TimeUnit; 032 033 import org.apache.camel.CamelContext; 034 import org.apache.camel.Component; 035 import org.apache.camel.Consumer; 036 import org.apache.camel.Endpoint; 037 import org.apache.camel.Exchange; 038 import org.apache.camel.Expression; 039 import org.apache.camel.Message; 040 import org.apache.camel.Processor; 041 import org.apache.camel.Producer; 042 import org.apache.camel.impl.DefaultEndpoint; 043 import org.apache.camel.impl.DefaultProducer; 044 import org.apache.camel.spi.BrowsableEndpoint; 045 import org.apache.camel.util.CamelContextHelper; 046 import org.apache.camel.util.ExpressionComparator; 047 import org.apache.camel.util.ObjectHelper; 048 import org.apache.commons.logging.Log; 049 import org.apache.commons.logging.LogFactory; 050 051 /** 052 * A Mock endpoint which provides a literate, fluent API for testing routes 053 * using a <a href="http://jmock.org/">JMock style</a> API. 054 * 055 * @version $Revision: 46137 $ 056 */ 057 public class MockEndpoint extends DefaultEndpoint<Exchange> implements BrowsableEndpoint<Exchange> { 058 private static final transient Log LOG = LogFactory.getLog(MockEndpoint.class); 059 private int expectedCount; 060 private int counter; 061 private Processor defaultProcessor; 062 private Map<Integer, Processor> processors; 063 private List<Exchange> receivedExchanges; 064 private List<Throwable> failures; 065 private List<Runnable> tests; 066 private CountDownLatch latch; 067 private long sleepForEmptyTest; 068 private long resultWaitTime; 069 private int expectedMinimumCount; 070 private List expectedBodyValues; 071 private List actualBodyValues; 072 private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); 073 private String headerName; 074 private String headerValue; 075 private Object actualHeader; 076 private Processor reporter; 077 078 public MockEndpoint(String endpointUri, Component component) { 079 super(endpointUri, component); 080 init(); 081 } 082 083 public MockEndpoint(String endpointUri) { 084 super(endpointUri); 085 init(); 086 } 087 088 089 /** 090 * A helper method to resolve the mock endpoint of the given URI on the given context 091 * 092 * @param context the camel context to try resolve the mock endpoint from 093 * @param uri the uri of the endpoint to resolve 094 * @return the endpoint 095 */ 096 public static MockEndpoint resolve(CamelContext context, String uri) { 097 return CamelContextHelper.getMandatoryEndpoint(context, uri, MockEndpoint.class); 098 } 099 100 public static void assertWait(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException { 101 long start = System.currentTimeMillis(); 102 long left = unit.toMillis(timeout); 103 long end = start + left; 104 for (MockEndpoint endpoint : endpoints) { 105 if (!endpoint.await(left, TimeUnit.MILLISECONDS)) { 106 throw new AssertionError("Timeout waiting for endpoints to receive enough messages. " + endpoint.getEndpointUri() + " timed out."); 107 } 108 left = end - System.currentTimeMillis(); 109 if (left <= 0) { 110 left = 0; 111 } 112 } 113 } 114 115 public static void assertIsSatisfied(long timeout, TimeUnit unit, MockEndpoint... endpoints) throws InterruptedException { 116 assertWait(timeout, unit, endpoints); 117 for (MockEndpoint endpoint : endpoints) { 118 endpoint.assertIsSatisfied(); 119 } 120 } 121 122 public static void assertIsSatisfied(MockEndpoint... endpoints) throws InterruptedException { 123 for (MockEndpoint endpoint : endpoints) { 124 endpoint.assertIsSatisfied(); 125 } 126 } 127 128 129 /** 130 * Asserts that all the expectations on any {@link MockEndpoint} instances registered 131 * in the given context are valid 132 * 133 * @param context the camel context used to find all the available endpoints to be asserted 134 */ 135 public static void assertIsSatisfied(CamelContext context) throws InterruptedException { 136 ObjectHelper.notNull(context, "camelContext"); 137 Collection<Endpoint> endpoints = context.getSingletonEndpoints(); 138 for (Endpoint endpoint : endpoints) { 139 if (endpoint instanceof MockEndpoint) { 140 MockEndpoint mockEndpoint = (MockEndpoint) endpoint; 141 mockEndpoint.assertIsSatisfied(); 142 } 143 } 144 } 145 146 147 public static void expectsMessageCount(int count, MockEndpoint... endpoints) throws InterruptedException { 148 for (MockEndpoint endpoint : endpoints) { 149 MockEndpoint.expectsMessageCount(count); 150 } 151 } 152 153 public List<Exchange> getExchanges() { 154 return getReceivedExchanges(); 155 } 156 157 public void addPropertyChangeListener(PropertyChangeListener listener) { 158 propertyChangeSupport.addPropertyChangeListener(listener); 159 } 160 161 public void removePropertyChangeListener(PropertyChangeListener listener) { 162 propertyChangeSupport.removePropertyChangeListener(listener); 163 } 164 165 public Consumer<Exchange> createConsumer(Processor processor) throws Exception { 166 throw new UnsupportedOperationException("You cannot consume from this endpoint"); 167 } 168 169 public Producer<Exchange> createProducer() throws Exception { 170 return new DefaultProducer<Exchange>(this) { 171 public void process(Exchange exchange) { 172 onExchange(exchange); 173 } 174 }; 175 } 176 177 public void reset() { 178 init(); 179 } 180 181 182 // Testing API 183 // ------------------------------------------------------------------------- 184 185 /** 186 * Set the processor that will be invoked when the index 187 * message is received. 188 * 189 * @param index 190 * @param processor 191 */ 192 public void whenExchangeReceived(int index, Processor processor) { 193 this.processors.put(index, processor); 194 } 195 196 /** 197 * Set the processor that will be invoked when the some message 198 * is received. 199 * 200 * This processor could be overwritten by 201 * {@link #whenExchangeReceived(int, Processor)} method. 202 * 203 * @param processor 204 */ 205 public void whenAnyExchangeReceived(Processor processor) { 206 this.defaultProcessor = processor; 207 } 208 209 /** 210 * Validates that all the available expectations on this endpoint are 211 * satisfied; or throw an exception 212 */ 213 public void assertIsSatisfied() throws InterruptedException { 214 assertIsSatisfied(sleepForEmptyTest); 215 } 216 217 /** 218 * Validates that all the available expectations on this endpoint are 219 * satisfied; or throw an exception 220 * 221 * @param timeoutForEmptyEndpoints the timeout in milliseconds that we 222 * should wait for the test to be true 223 */ 224 public void assertIsSatisfied(long timeoutForEmptyEndpoints) throws InterruptedException { 225 LOG.info("Asserting: " + this + " is satisfied"); 226 if (expectedCount >= 0) { 227 if (expectedCount != getReceivedCounter()) { 228 if (expectedCount == 0) { 229 // lets wait a little bit just in case 230 if (timeoutForEmptyEndpoints > 0) { 231 LOG.debug("Sleeping for: " + timeoutForEmptyEndpoints + " millis to check there really are no messages received"); 232 Thread.sleep(timeoutForEmptyEndpoints); 233 } 234 } else { 235 waitForCompleteLatch(); 236 } 237 } 238 assertEquals("Received message count", expectedCount, getReceivedCounter()); 239 } else if (expectedMinimumCount > 0 && getReceivedCounter() < expectedMinimumCount) { 240 waitForCompleteLatch(); 241 } 242 243 if (expectedMinimumCount >= 0) { 244 int receivedCounter = getReceivedCounter(); 245 assertTrue("Received message count " + receivedCounter + ", expected at least " + expectedCount, expectedCount <= receivedCounter); 246 } 247 248 for (Runnable test : tests) { 249 test.run(); 250 } 251 252 for (Throwable failure : failures) { 253 if (failure != null) { 254 LOG.error("Caught on " + getEndpointUri() + " Exception: " + failure, failure); 255 fail("Failed due to caught exception: " + failure); 256 } 257 } 258 } 259 260 /** 261 * Validates that the assertions fail on this endpoint 262 */ 263 public void assertIsNotSatisfied() throws InterruptedException { 264 try { 265 assertIsSatisfied(); 266 fail("Expected assertion failure!"); 267 } catch (AssertionError e) { 268 LOG.info("Caught expected failure: " + e); 269 } 270 } 271 272 /** 273 * Specifies the expected number of message exchanges that should be 274 * received by this endpoint 275 * 276 * @param expectedCount the number of message exchanges that should be 277 * expected by this endpoint 278 */ 279 public void expectedMessageCount(int expectedCount) { 280 setExpectedMessageCount(expectedCount); 281 } 282 283 /** 284 * Specifies the minimum number of expected message exchanges that should be 285 * received by this endpoint 286 * 287 * @param expectedCount the number of message exchanges that should be 288 * expected by this endpoint 289 */ 290 public void expectedMinimumMessageCount(int expectedCount) { 291 setMinimumExpectedMessageCount(expectedCount); 292 } 293 294 /** 295 * Adds an expectation that the given header name & value are received by this 296 * endpoint 297 */ 298 public void expectedHeaderReceived(String name, String value) { 299 this.headerName = name; 300 this.headerValue = value; 301 302 expects(new Runnable() { 303 public void run() { 304 assertTrue("No header with name " + headerName + " found.", actualHeader != null); 305 306 assertEquals("Header of message", headerValue, actualHeader); 307 } 308 }); 309 } 310 311 /** 312 * Adds an expectation that the given body values are received by this 313 * endpoint in the specified order 314 */ 315 public void expectedBodiesReceived(final List bodies) { 316 expectedMessageCount(bodies.size()); 317 this.expectedBodyValues = bodies; 318 this.actualBodyValues = new ArrayList(); 319 320 expects(new Runnable() { 321 public void run() { 322 for (int i = 0; i < expectedBodyValues.size(); i++) { 323 Exchange exchange = getReceivedExchanges().get(i); 324 assertTrue("No exchange received for counter: " + i, exchange != null); 325 326 Object expectedBody = expectedBodyValues.get(i); 327 Object actualBody = actualBodyValues.get(i); 328 329 assertEquals("Body of message: " + i, expectedBody, actualBody); 330 } 331 } 332 }); 333 } 334 335 /** 336 * Adds an expectation that the given body values are received by this 337 * endpoint 338 */ 339 public void expectedBodiesReceived(Object... bodies) { 340 List bodyList = new ArrayList(); 341 bodyList.addAll(Arrays.asList(bodies)); 342 expectedBodiesReceived(bodyList); 343 } 344 345 /** 346 * Adds an expectation that the given body values are received by this 347 * endpoint in any order 348 */ 349 public void expectedBodiesReceivedInAnyOrder(final List bodies) { 350 expectedMessageCount(bodies.size()); 351 this.expectedBodyValues = bodies; 352 this.actualBodyValues = new ArrayList(); 353 354 expects(new Runnable() { 355 public void run() { 356 Set actualBodyValuesSet = new HashSet(actualBodyValues); 357 for (int i = 0; i < expectedBodyValues.size(); i++) { 358 Exchange exchange = getReceivedExchanges().get(i); 359 assertTrue("No exchange received for counter: " + i, exchange != null); 360 361 Object expectedBody = expectedBodyValues.get(i); 362 assertTrue("Message with body " + expectedBody 363 + " was expected but not found in " + actualBodyValuesSet, 364 actualBodyValuesSet.remove(expectedBody)); 365 } 366 } 367 }); 368 } 369 370 /** 371 * Adds an expectation that the given body values are received by this 372 * endpoint in any order 373 */ 374 public void expectedBodiesReceivedInAnyOrder(Object... bodies) { 375 List bodyList = new ArrayList(); 376 bodyList.addAll(Arrays.asList(bodies)); 377 expectedBodiesReceivedInAnyOrder(bodyList); 378 } 379 380 /** 381 * Adds an expectation that messages received should have ascending values 382 * of the given expression such as a user generated counter value 383 * 384 * @param expression 385 */ 386 public void expectsAscending(final Expression<Exchange> expression) { 387 expects(new Runnable() { 388 public void run() { 389 assertMessagesAscending(expression); 390 } 391 }); 392 } 393 394 /** 395 * Adds an expectation that messages received should have descending values 396 * of the given expression such as a user generated counter value 397 * 398 * @param expression 399 */ 400 public void expectsDescending(final Expression<Exchange> expression) { 401 expects(new Runnable() { 402 public void run() { 403 assertMessagesDescending(expression); 404 } 405 }); 406 } 407 408 /** 409 * Adds an expectation that no duplicate messages should be received using 410 * the expression to determine the message ID 411 * 412 * @param expression the expression used to create a unique message ID for 413 * message comparison (which could just be the message 414 * payload if the payload can be tested for uniqueness using 415 * {@link Object#equals(Object)} and 416 * {@link Object#hashCode()} 417 */ 418 public void expectsNoDuplicates(final Expression<Exchange> expression) { 419 expects(new Runnable() { 420 public void run() { 421 assertNoDuplicates(expression); 422 } 423 }); 424 } 425 426 /** 427 * Asserts that the messages have ascending values of the given expression 428 */ 429 public void assertMessagesAscending(Expression<Exchange> expression) { 430 assertMessagesSorted(expression, true); 431 } 432 433 /** 434 * Asserts that the messages have descending values of the given expression 435 */ 436 public void assertMessagesDescending(Expression<Exchange> expression) { 437 assertMessagesSorted(expression, false); 438 } 439 440 protected void assertMessagesSorted(Expression<Exchange> expression, boolean ascending) { 441 String type = ascending ? "ascending" : "descending"; 442 ExpressionComparator comparator = new ExpressionComparator(expression); 443 List<Exchange> list = getReceivedExchanges(); 444 for (int i = 1; i < list.size(); i++) { 445 int j = i - 1; 446 Exchange e1 = list.get(j); 447 Exchange e2 = list.get(i); 448 int result = comparator.compare(e1, e2); 449 if (result == 0) { 450 fail("Messages not " + type + ". Messages" + j + " and " + i + " are equal with value: " + expression.evaluate(e1) + " for expression: " + expression + ". Exchanges: " + e1 + " and " 451 + e2); 452 } else { 453 if (!ascending) { 454 result = result * -1; 455 } 456 if (result > 0) { 457 fail("Messages not " + type + ". Message " + j + " has value: " + expression.evaluate(e1) + " and message " + i + " has value: " + expression.evaluate(e2) + " for expression: " 458 + expression + ". Exchanges: " + e1 + " and " + e2); 459 } 460 } 461 } 462 } 463 464 public void assertNoDuplicates(Expression<Exchange> expression) { 465 Map<Object, Exchange> map = new HashMap<Object, Exchange>(); 466 List<Exchange> list = getReceivedExchanges(); 467 for (int i = 0; i < list.size(); i++) { 468 Exchange e2 = list.get(i); 469 Object key = expression.evaluate(e2); 470 Exchange e1 = map.get(key); 471 if (e1 != null) { 472 fail("Duplicate message found on message " + i + " has value: " + key + " for expression: " + expression + ". Exchanges: " + e1 + " and " + e2); 473 } else { 474 map.put(key, e2); 475 } 476 } 477 } 478 479 /** 480 * Adds the expectation which will be invoked when enough messages are 481 * received 482 */ 483 public void expects(Runnable runnable) { 484 tests.add(runnable); 485 } 486 487 /** 488 * Adds an assertion to the given message index 489 * 490 * @param messageIndex the number of the message 491 * @return the assertion clause 492 */ 493 public AssertionClause message(final int messageIndex) { 494 AssertionClause clause = new AssertionClause() { 495 public void run() { 496 applyAssertionOn(MockEndpoint.this, messageIndex, assertExchangeReceived(messageIndex)); 497 } 498 }; 499 expects(clause); 500 return clause; 501 } 502 503 /** 504 * Adds an assertion to all the received messages 505 * 506 * @return the assertion clause 507 */ 508 public AssertionClause allMessages() { 509 AssertionClause clause = new AssertionClause() { 510 public void run() { 511 List<Exchange> list = getReceivedExchanges(); 512 int index = 0; 513 for (Exchange exchange : list) { 514 applyAssertionOn(MockEndpoint.this, index++, exchange); 515 } 516 } 517 }; 518 expects(clause); 519 return clause; 520 } 521 522 /** 523 * Asserts that the given index of message is received (starting at zero) 524 */ 525 public Exchange assertExchangeReceived(int index) { 526 int count = getReceivedCounter(); 527 assertTrue("Not enough messages received. Was: " + count, count > index); 528 return getReceivedExchanges().get(index); 529 } 530 531 // Properties 532 // ------------------------------------------------------------------------- 533 public List<Throwable> getFailures() { 534 return failures; 535 } 536 537 public int getReceivedCounter() { 538 return getReceivedExchanges().size(); 539 } 540 541 public List<Exchange> getReceivedExchanges() { 542 return receivedExchanges; 543 } 544 545 public int getExpectedCount() { 546 return expectedCount; 547 } 548 549 public long getSleepForEmptyTest() { 550 return sleepForEmptyTest; 551 } 552 553 /** 554 * Allows a sleep to be specified to wait to check that this endpoint really 555 * is empty when {@link #expectedMessageCount(int)} is called with zero 556 * 557 * @param sleepForEmptyTest the milliseconds to sleep for to determine that 558 * this endpoint really is empty 559 */ 560 public void setSleepForEmptyTest(long sleepForEmptyTest) { 561 this.sleepForEmptyTest = sleepForEmptyTest; 562 } 563 564 public long getResultWaitTime() { 565 return resultWaitTime; 566 } 567 568 /** 569 * Sets the maximum amount of time (in millis) the {@link #assertIsSatisfied()} will 570 * wait on a latch until it is satisfied 571 */ 572 public void setResultWaitTime(long resultWaitTime) { 573 this.resultWaitTime = resultWaitTime; 574 } 575 576 /** 577 * Specifies the expected number of message exchanges that should be 578 * received by this endpoint 579 * 580 * @param expectedCount the number of message exchanges that should be 581 * expected by this endpoint 582 */ 583 public void setExpectedMessageCount(int expectedCount) { 584 this.expectedCount = expectedCount; 585 if (expectedCount <= 0) { 586 latch = null; 587 } else { 588 latch = new CountDownLatch(expectedCount); 589 } 590 } 591 592 /** 593 * Specifies the minimum number of expected message exchanges that should be 594 * received by this endpoint 595 * 596 * @param expectedCount the number of message exchanges that should be 597 * expected by this endpoint 598 */ 599 public void setMinimumExpectedMessageCount(int expectedCount) { 600 this.expectedMinimumCount = expectedCount; 601 if (expectedCount <= 0) { 602 latch = null; 603 } else { 604 latch = new CountDownLatch(expectedMinimumCount); 605 } 606 } 607 608 public Processor getReporter() { 609 return reporter; 610 } 611 612 /** 613 * Allows a processor to added to the endpoint to report on progress of the test 614 */ 615 public void setReporter(Processor reporter) { 616 this.reporter = reporter; 617 } 618 619 // Implementation methods 620 // ------------------------------------------------------------------------- 621 private void init() { 622 expectedCount = -1; 623 counter = 0; 624 processors = new HashMap<Integer, Processor>(); 625 receivedExchanges = new CopyOnWriteArrayList<Exchange>(); 626 failures = new CopyOnWriteArrayList<Throwable>(); 627 tests = new CopyOnWriteArrayList<Runnable>(); 628 latch = null; 629 sleepForEmptyTest = 1000L; 630 resultWaitTime = 20000L; 631 expectedMinimumCount = -1; 632 expectedBodyValues = null; 633 actualBodyValues = new ArrayList(); 634 } 635 636 protected synchronized void onExchange(Exchange exchange) { 637 try { 638 if (reporter != null) { 639 reporter.process(exchange); 640 } 641 642 performAssertions(exchange); 643 } catch (Throwable e) { 644 failures.add(e); 645 } 646 if (latch != null) { 647 latch.countDown(); 648 } 649 } 650 651 protected void performAssertions(Exchange exchange) throws Exception { 652 Message in = exchange.getIn(); 653 Object actualBody = in.getBody(); 654 655 if (headerName != null) { 656 actualHeader = in.getHeader(headerName); 657 } 658 659 if (expectedBodyValues != null) { 660 int index = actualBodyValues.size(); 661 if (expectedBodyValues.size() > index) { 662 Object expectedBody = expectedBodyValues.get(index); 663 if (expectedBody != null) { 664 actualBody = in.getBody(expectedBody.getClass()); 665 } 666 actualBodyValues.add(actualBody); 667 } 668 } 669 670 LOG.debug(getEndpointUri() + " >>>> " + (++counter) + " : " + exchange + " with body: " + actualBody); 671 672 receivedExchanges.add(exchange); 673 674 Processor processor = processors.get(getReceivedCounter()) != null 675 ? processors.get(getReceivedCounter()) : defaultProcessor; 676 677 if (processor != null) { 678 processor.process(exchange); 679 } 680 } 681 682 protected void waitForCompleteLatch() throws InterruptedException { 683 if (latch == null) { 684 fail("Should have a latch!"); 685 } 686 687 // now lets wait for the results 688 LOG.debug("Waiting on the latch for: " + resultWaitTime + " millis"); 689 latch.await(resultWaitTime, TimeUnit.MILLISECONDS); 690 } 691 692 protected void assertEquals(String message, Object expectedValue, Object actualValue) { 693 if (!ObjectHelper.equal(expectedValue, actualValue)) { 694 fail(message + ". Expected: <" + expectedValue + "> but was: <" + actualValue + ">"); 695 } 696 } 697 698 protected void assertTrue(String message, boolean predicate) { 699 if (!predicate) { 700 fail(message); 701 } 702 } 703 704 protected void fail(Object message) { 705 if (LOG.isDebugEnabled()) { 706 List<Exchange> list = getReceivedExchanges(); 707 int index = 0; 708 for (Exchange exchange : list) { 709 LOG.debug("Received[" + (++index) + "]: " + exchange); 710 } 711 } 712 throw new AssertionError(getEndpointUri() + " " + message); 713 } 714 715 public int getExpectedMinimumCount() { 716 return expectedMinimumCount; 717 } 718 719 public void await() throws InterruptedException { 720 if (latch != null) { 721 latch.await(); 722 } 723 } 724 725 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { 726 if (latch != null) { 727 return latch.await(timeout, unit); 728 } 729 return true; 730 } 731 732 public boolean isSingleton() { 733 return true; 734 } 735 }