/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.core;
import static java.time.Duration.ofMillis;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mule.runtime.api.component.ComponentIdentifier.buildFromStringRepresentation;
import static org.mule.runtime.api.component.TypedComponentIdentifier.ComponentType.SOURCE;
import static org.mule.runtime.core.DefaultEventContext.create;
import static org.mule.runtime.core.api.rx.Exceptions.checkedConsumer;
import static org.mule.runtime.internal.dsl.DslConstants.CORE_PREFIX;
import static org.mule.tck.MuleTestUtils.getTestFlow;
import static org.mule.test.allure.AllureConstants.EventContextFeature.EVENT_CONTEXT;
import static org.mule.test.allure.AllureConstants.EventContextFeature.EventContextStory.RESPONSE_AND_COMPLETION_PUBLISHERS;
import static reactor.core.publisher.Mono.from;
import org.mule.runtime.api.component.TypedComponentIdentifier;
import org.mule.runtime.api.component.location.ComponentLocation;
import org.mule.runtime.api.scheduler.Scheduler;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.EventContext;
import org.mule.runtime.core.util.concurrent.Latch;
import org.mule.tck.junit4.AbstractMuleContextTestCase;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import reactor.core.publisher.MonoProcessor;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@Features(EVENT_CONTEXT)
@Stories(RESPONSE_AND_COMPLETION_PUBLISHERS)
public class DefaultEventContextTestCase extends AbstractMuleContextTestCase {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
@Description("EventContext response publisher completes with value of result. Also given response publisher completed and " +
"there there are no child contexts the completion publisher also completes.")
public void successWithResult() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
Event event = testEvent();
assertCompletionNotDone(parent);
parent.success(event);
awaitAndAssertResponse(parent, event);
assertCompletionDone(parent);
}
@Test
@Description("EventContext response publisher completes with null result. Also given response publisher completed and " +
"there there are no child contexts the completion publisher also completes.")
public void successNoResult() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
parent.success();
awaittNullResponse(parent);
assertBeforeResponseDone(parent);
assertThat(from(parent.getCompletionPublisher()).block(ofMillis(BLOCK_TIMEOUT)), is(nullValue()));
}
@Test
@Description("EventContext response publisher completes with error. Also given response publisher completed and " +
"there there are no child contexts the completion publisher also completes.")
public void error() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
RuntimeException exception = new RuntimeException();
assertCompletionNotDone(parent);
parent.error(exception);
assertCompletionDone(parent);
assertResponseDone(parent);
assertBeforeResponseDone(parent);
expectedException.expect(is(exception));
from(parent.getResponsePublisher()).block(ofMillis(BLOCK_TIMEOUT));
}
@Test
@Description("EventContext beforeResponsePublisher subscribers are notified before responsePublisher subscribers (assuming " +
"both are subscribed before response is completed)")
public void beforeResponse() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
Event event = testEvent();
Latch latch = new Latch();
Latch lock = new Latch();
Latch responseSubscriberFired = new Latch();
from(parent.getBeforeResponsePublisher()).doOnNext(checkedConsumer(e -> {
lock.countDown();
latch.await();
})).subscribe();
from(parent.getResponsePublisher()).doOnNext(checkedConsumer(e -> {
responseSubscriberFired.countDown();
})).subscribe();
Scheduler testScheduler = muleContext.getSchedulerService().ioScheduler();
try {
testScheduler.submit(() -> parent.success(event));
// Wait until 'before response' publisher subscriber is fired
lock.await();
// Assert that `response` publisher subscriber is not fired until `before response` subscriber finishes
assertThat(responseSubscriberFired.await(BLOCK_TIMEOUT, MILLISECONDS), is(false));
// Unblock `before response` publisher
latch.countDown();
// Assert that `response` publisher subscriber is now fired
assertThat(responseSubscriberFired.await(BLOCK_TIMEOUT, MILLISECONDS), is(true));
awaitAndAssertResponse(parent, event);
awaitCompletion(parent);
} finally {
testScheduler.stop();
}
}
@Test
@Description("Parent EventContext only completes once response publisher completes with a value and all child contexts are " +
"complete.")
public void childSuccessWithResult() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
Event event = testEvent();
child.success(event);
awaitAndAssertResponse(child, event);
assertCompletionDone(child);
// Child completion does not complete parent
assertCompletionNotDone(parent);
parent.success(event);
awaitAndAssertResponse(parent, event);
assertCompletionDone(parent);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with a value and all child contexts are " +
"complete, even when child context completes after parent context response.")
public void childDelayedSuccessWithResult() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
Event event = testEvent();
parent.success(event);
awaitAndAssertResponse(parent, event);
// Parent context does not complete because it still has uncompleted children
assertCompletionNotDone(parent);
assertCompletionNotDone(child);
child.success(event);
awaitAndAssertResponse(child, event);
assertCompletionDone(child);
// Now child contexts are complete, parent completes
assertCompletionDone(parent);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with no value and all child contexts are " +
"complete.")
public void childSuccessWithNoResult() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
child.success();
parent.success();
awaittNullResponse(child);
assertCompletionDone(child);
awaittNullResponse(parent);
assertCompletionDone(parent);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with no value and all child contexts are " +
"complete, even when child context completes after parent context response.")
public void childDelayedSuccessWithNoResult() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
parent.success();
awaittNullResponse(parent);
assertCompletionNotDone(parent);
assertCompletionNotDone(child);
child.success();
awaittNullResponse(child);
assertCompletionDone(child);
assertCompletionDone(parent);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with error and all child contexts are " +
"complete.")
public void childError() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
RuntimeException exception = new RuntimeException();
child.error(exception);
parent.error(exception);
assertResponseDone(child);
assertCompletionDone(child);
assertResponseDone(parent);
assertCompletionDone(parent);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with error and all child contexts are " +
"complete, even when child context completes after parent context response.")
public void childDelayedError() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
RuntimeException exception = new RuntimeException();
parent.error(exception);
assertResponseDone(parent);
assertCompletionNotDone(parent);
assertCompletionNotDone(child);
child.error(exception);
assertCompletionDone(parent);
assertCompletionDone(parent);
expectedException.expect(is(exception));
from(child.getResponsePublisher()).block(ofMillis(BLOCK_TIMEOUT));
}
@Test
@Description("Parent EventContext only completes once response publisher completes with a value and all child contexts are " +
"complete, even when child is run async with a delay.")
public void asyncChild() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child1 = DefaultEventContext.child(parent);
Event event = testEvent();
Scheduler testScheduler = muleContext.getSchedulerService().ioScheduler();
try {
testScheduler.submit(() -> {
Thread.sleep(5);
child1.success(event);
return null;
});
parent.success(event);
assertCompletionNotDone(child1);
awaitAndAssertResponse(child1, event);
awaitCompletion(child1);
awaitAndAssertResponse(parent, event);
assertCompletionDone(child1);
} finally {
testScheduler.stop();
}
}
@Test
@Description("Parent EventContext only completes once response publisher completes with a value and all child and grandchild " +
"contexts are complete.")
public void multipleLevelsGrandchildFirst() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
EventContext grandchild = DefaultEventContext.child(child);
assertResponseNotDone(parent);
assertCompletionNotDone(parent);
assertResponseNotDone(child);
assertCompletionNotDone(child);
assertResponseNotDone(grandchild);
assertCompletionNotDone(grandchild);
grandchild.success();
assertResponseNotDone(parent);
assertCompletionNotDone(parent);
assertResponseNotDone(child);
assertCompletionNotDone(child);
assertResponseDone(grandchild);
assertCompletionDone(grandchild);
child.success();
assertResponseNotDone(parent);
assertCompletionNotDone(parent);
assertResponseDone(child);
assertCompletionDone(child);
assertResponseDone(grandchild);
assertCompletionDone(grandchild);
parent.success();
assertResponseDone(parent);
assertCompletionDone(parent);
assertResponseDone(child);
assertCompletionDone(child);
assertResponseDone(grandchild);
assertCompletionDone(grandchild);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with a value and all child and grandchild " +
"contexts are complete, even if parent response is available earlier.")
public void multipleLevelsParentFirst() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child = DefaultEventContext.child(parent);
EventContext grandchild = DefaultEventContext.child(child);
assertResponseNotDone(parent);
assertCompletionNotDone(parent);
assertResponseNotDone(child);
assertCompletionNotDone(child);
assertResponseNotDone(grandchild);
assertCompletionNotDone(grandchild);
parent.success();
assertResponseDone(parent);
assertCompletionNotDone(parent);
assertResponseNotDone(child);
assertCompletionNotDone(child);
assertResponseNotDone(grandchild);
assertCompletionNotDone(grandchild);
child.success();
assertResponseDone(parent);
assertCompletionNotDone(parent);
assertResponseDone(child);
assertCompletionNotDone(child);
assertResponseNotDone(grandchild);
assertCompletionNotDone(grandchild);
grandchild.success();
assertResponseDone(parent);
assertCompletionDone(parent);
assertResponseDone(child);
assertCompletionDone(child);
assertResponseDone(grandchild);
assertCompletionDone(grandchild);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with a value and all child contexts are " +
"complete, even if one branch of the tree completes.")
public void multipleBranches() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
EventContext child1 = DefaultEventContext.child(parent);
EventContext child2 = DefaultEventContext.child(parent);
EventContext grandchild1 = DefaultEventContext.child(child1);
EventContext grandchild2 = DefaultEventContext.child(child1);
EventContext grandchild3 = DefaultEventContext.child(child2);
EventContext grandchild4 = DefaultEventContext.child(child2);
grandchild1.success();
grandchild2.success();
assertCompletionDone(grandchild1);
assertCompletionDone(grandchild2);
assertCompletionNotDone(child1);
assertCompletionNotDone(parent);
child1.success();
assertCompletionDone(child1);
assertCompletionNotDone(parent);
grandchild3.success();
grandchild4.success();
child2.success();
assertCompletionDone(grandchild3);
assertCompletionDone(grandchild4);
assertCompletionDone(child2);
assertCompletionNotDone(parent);
parent.success();
assertCompletionDone(parent);
}
@Test
@Description("EventContext response publisher completes with value of result but the completion publisher only completes "
+ " once the external publisher completes.")
public void externalCompletionSuccess() throws Exception {
MonoProcessor<Void> externalCompletion = MonoProcessor.create();
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION, null, externalCompletion);
Event event = testEvent();
assertCompletionNotDone(parent);
parent.success(event);
awaitAndAssertResponse(parent, event);
assertCompletionNotDone(parent);
externalCompletion.onComplete();
assertCompletionDone(parent);
}
@Test
@Description("EventContext response publisher completes with error but the completion publisher only completes "
+ " once the external publisher completes.")
public void externalCompletionError() throws Exception {
MonoProcessor<Void> externalCompletion = MonoProcessor.create();
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION, null, externalCompletion);
RuntimeException exception = new RuntimeException();
assertCompletionNotDone(parent);
parent.error(exception);
assertCompletionNotDone(parent);
externalCompletion.onComplete();
assertCompletionDone(parent);
}
@Test
@Description("Parent EventContext only completes once response publisher completes with a value and all child contexts are " +
"complete and external completion completes.")
public void externalCompletionWithChild() throws Exception {
MonoProcessor<Void> externalCompletion = MonoProcessor.create();
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION, null, externalCompletion);
EventContext child = DefaultEventContext.child(parent);
Event event = testEvent();
child.success(event);
awaitAndAssertResponse(child, event);
assertCompletionDone(child);
// Child completion does not complete parent
assertCompletionNotDone(parent);
parent.success(event);
awaitAndAssertResponse(parent, event);
assertCompletionNotDone(parent);
externalCompletion.onComplete();
assertCompletionDone(parent);
}
@Test
@Description("When a child event context is de-serialized it is decoupled from parent context but response and completion " +
"publisher still complete when a response event is available.")
public void deserializedChild() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION, null);
EventContext child = DefaultEventContext.child(parent);
byte[] bytes = muleContext.getObjectSerializer().getExternalProtocol().serialize(child);
EventContext deserializedChild = muleContext.getObjectSerializer().getExternalProtocol().deserialize(bytes);
Event event = testEvent();
deserializedChild.success(event);
awaitAndAssertResponse(deserializedChild, event);
assertCompletionDone(deserializedChild);
}
@Test
@Description("When a parent event context is de-serialized the parent context no longer waits for completion of child context.")
public void deserializedParent() throws Exception {
EventContext parent = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION, null);
EventContext child = DefaultEventContext.child(parent);
byte[] bytes = muleContext.getObjectSerializer().getExternalProtocol().serialize(parent);
EventContext deserializedParent = muleContext.getObjectSerializer().getExternalProtocol().deserialize(bytes);
Event event = testEvent();
deserializedParent.success(event);
awaitAndAssertResponse(deserializedParent, event);
assertCompletionDone(deserializedParent);
}
@Test
@Description("Verify that a location produces connector and source data.")
public void componentData() throws Exception {
TypedComponentIdentifier typedComponentIdentifier = TypedComponentIdentifier.builder()
.withType(SOURCE)
.withIdentifier(buildFromStringRepresentation("http:listener"))
.build();
ComponentLocation location = mock(ComponentLocation.class);
when(location.getComponentIdentifier()).thenReturn(typedComponentIdentifier);
EventContext context = create(getTestFlow(muleContext), location);
assertThat(context.getOriginatingConnectorName(), is("http"));
assertThat(context.getOriginatingSourceName(), is("listener"));
}
@Test
@Description("Verify that a single component location produces connector and source data.")
public void componentDataFromSingleComponent() throws Exception {
EventContext context = create(getTestFlow(muleContext), TEST_CONNECTOR_LOCATION);
assertThat(context.getOriginatingConnectorName(), is(CORE_PREFIX));
assertThat(context.getOriginatingSourceName(), is("test"));
}
private void assertBeforeResponseDone(EventContext parent) {
assertThat(from(parent.getBeforeResponsePublisher()).toFuture().isDone(), is(true));
}
private void awaitAndAssertResponse(EventContext parent, Event event) {
assertThat(from(parent.getResponsePublisher()).block(ofMillis(BLOCK_TIMEOUT)), equalTo(event));
}
private void awaittNullResponse(EventContext child) {
assertThat(from(child.getResponsePublisher()).block(ofMillis(BLOCK_TIMEOUT)), is(nullValue()));
}
private void assertResponseDone(EventContext parent) {
assertThat(from(parent.getResponsePublisher()).toFuture().isDone(), is(true));
}
private void assertResponseNotDone(EventContext parent) {
assertThat(from(parent.getResponsePublisher()).toFuture().isDone(), is(false));
}
private void awaitCompletion(EventContext parent) {
assertThat(from(parent.getCompletionPublisher()).block(ofMillis(BLOCK_TIMEOUT)), is(nullValue()));
}
private void assertCompletionDone(EventContext parent) {
assertThat(from(parent.getCompletionPublisher()).toFuture().isDone(), is(true));
}
private void assertCompletionNotDone(EventContext child1) {
assertThat(from(child1.getCompletionPublisher()).toFuture().isDone(), is(false));
}
}