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