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