/*
* 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.context.notification;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import static org.mule.runtime.core.DefaultEventContext.create;
import static org.mule.runtime.core.api.context.notification.EnrichedNotificationInfo.createInfo;
import static org.mule.runtime.core.context.notification.MessageProcessingFlowTraceManager.FLOW_STACK_INFO_KEY;
import static org.mule.runtime.core.context.notification.MessageProcessorNotification.MESSAGE_PROCESSOR_PRE_INVOKE;
import static org.mule.runtime.core.context.notification.PipelineMessageNotification.PROCESS_START;
import org.mule.runtime.api.component.location.ComponentLocation;
import org.mule.runtime.api.meta.AnnotatedObject;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.EventContext;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.config.MuleConfiguration;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.core.api.construct.Pipeline;
import org.mule.runtime.core.api.context.notification.FlowCallStack;
import org.mule.runtime.core.api.context.notification.ProcessorsTrace;
import org.mule.runtime.core.api.processor.Processor;
import org.mule.runtime.core.config.DefaultMuleConfiguration;
import org.mule.tck.junit4.AbstractMuleTestCase;
import org.mule.tck.size.SmallTest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.xml.namespace.QName;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@SmallTest
public class MessageProcessingFlowTraceManagerTestCase extends AbstractMuleTestCase {
private static QName docNameAttrName = new QName("http://www.mulesoft.org/schema/mule/documentation", "name");
private static QName sourceFileNameAttrName = new QName("http://www.mulesoft.org/schema/mule/documentation", "sourceFileName");
private static QName sourceFileLineAttrName = new QName("http://www.mulesoft.org/schema/mule/documentation", "sourceFileLine");
private static final String NESTED_FLOW_NAME = "nestedFlow";
private static final String ROOT_FLOW_NAME = "rootFlow";
private static final String APP_ID = "MessageProcessingFlowTraceManagerTestCase";
private static boolean originalFlowTrace;
@BeforeClass
public static void beforeClass() {
originalFlowTrace = DefaultMuleConfiguration.flowTrace;
DefaultMuleConfiguration.flowTrace = true;
}
@AfterClass
public static void afterClass() {
DefaultMuleConfiguration.flowTrace = originalFlowTrace;
}
private MessageProcessingFlowTraceManager manager;
private EventContext messageContext;
private FlowConstruct rootFlowConstruct;
private FlowConstruct nestedFlowConstruct;
@Before
public void before() {
manager = new MessageProcessingFlowTraceManager();
MuleContext context = mock(MuleContext.class);
MuleConfiguration config = mock(MuleConfiguration.class);
when(config.getId()).thenReturn(APP_ID);
when(context.getConfiguration()).thenReturn(config);
manager.setMuleContext(context);
rootFlowConstruct = mock(FlowConstruct.class);
when(rootFlowConstruct.getName()).thenReturn(ROOT_FLOW_NAME);
when(rootFlowConstruct.getMuleContext()).thenReturn(context);
nestedFlowConstruct = mock(FlowConstruct.class);
when(nestedFlowConstruct.getName()).thenReturn(NESTED_FLOW_NAME);
when(nestedFlowConstruct.getMuleContext()).thenReturn(context);
messageContext = create(rootFlowConstruct, TEST_CONNECTOR_LOCATION);
}
@Test
public void newFlowInvocation() {
Event event = buildEvent("newFlowInvocation");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
manager.onPipelineNotificationComplete(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
}
@Test
public void nestedFlowInvocations() {
Event event = buildEvent("nestedFlowInvocations");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification);
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(event,
createMockProcessor(NESTED_FLOW_NAME + "_ref")));
PipelineMessageNotification pipelineNotificationNested = buildPipelineNotification(event, NESTED_FLOW_NAME);
manager.onPipelineNotificationStart(pipelineNotificationNested);
String rootEntry = "at " + rootFlowConstruct.getName() + "(" + NESTED_FLOW_NAME + "_ref @ " + APP_ID + ":null:null)";
assertThat(getContextInfo(event, rootFlowConstruct), is("at " + NESTED_FLOW_NAME + System.lineSeparator() + rootEntry));
manager.onPipelineNotificationComplete(pipelineNotificationNested);
assertThat(getContextInfo(event, rootFlowConstruct), is(rootEntry));
manager.onPipelineNotificationComplete(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
}
@Test
public void newComponentCall() {
Event event = buildEvent("newComponentCall");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is("at " + ROOT_FLOW_NAME));
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(event, createMockProcessor("/comp")));
assertThat(getContextInfo(event, rootFlowConstruct), is("at " + ROOT_FLOW_NAME + "(/comp @ " + APP_ID + ":null:null)"));
manager.onPipelineNotificationComplete(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
}
protected String getContextInfo(Event event, FlowConstruct flow) {
return (String) manager.getContextInfo(createInfo(event, null, null), null, flow).get(FLOW_STACK_INFO_KEY);
}
@Test
public void newAnnotatedComponentCall() {
Event event = buildEvent("newAnnotatedComponentCall");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
AnnotatedObject annotatedMessageProcessor = (AnnotatedObject) createMockProcessor("/comp");
when(annotatedMessageProcessor.getAnnotation(docNameAttrName)).thenReturn("annotatedName");
when(annotatedMessageProcessor.getAnnotation(sourceFileNameAttrName)).thenReturn("muleApp.xml");
when(annotatedMessageProcessor.getAnnotation(sourceFileLineAttrName)).thenReturn(10);
manager
.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(event, (Processor) annotatedMessageProcessor));
assertThat(getContextInfo(event, rootFlowConstruct),
is("at " + rootFlowConstruct.getName() + "(/comp @ " + APP_ID + ":muleApp.xml:10 (annotatedName))"));
manager.onPipelineNotificationComplete(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
}
@Test
public void splitStack() {
Event event = buildEvent("newAnnotatedComponentCall");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(event, createMockProcessor("/comp")));
assertThat(getContextInfo(event, rootFlowConstruct),
is("at " + rootFlowConstruct.getName() + "(/comp @ " + APP_ID + ":null:null)"));
Event eventCopy = buildEvent("newAnnotatedComponentCall", event.getFlowCallStack().clone());
assertThat(getContextInfo(eventCopy, rootFlowConstruct),
is("at " + rootFlowConstruct.getName() + "(/comp @ " + APP_ID + ":null:null)"));
manager.onPipelineNotificationComplete(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
final FlowConstruct asyncFlowConstruct = mock(FlowConstruct.class);
when(asyncFlowConstruct.getName()).thenReturn("asyncFlow");
manager.onPipelineNotificationStart(buildPipelineNotification(eventCopy, asyncFlowConstruct.getName()));
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy, createMockProcessor("/asyncComp")));
assertThat(getContextInfo(eventCopy, asyncFlowConstruct),
is("at " + asyncFlowConstruct.getName() + "(/asyncComp @ " + APP_ID + ":null:null)" + System.lineSeparator()
+ "at " + rootFlowConstruct.getName() + "(/comp @ " + APP_ID + ":null:null)"));
}
@Test
public void splitStackEnd() {
Event event = buildEvent("newAnnotatedComponentCall");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
manager.onPipelineNotificationStart(pipelineNotification);
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(event, createMockProcessor("/comp")));
FlowCallStack flowCallStack = event.getFlowCallStack();
Event eventCopy = buildEvent("newAnnotatedComponentCall", flowCallStack.clone());
manager.onPipelineNotificationComplete(pipelineNotification);
String asyncFlowName = "asyncFlow";
manager.onPipelineNotificationStart(buildPipelineNotification(eventCopy, asyncFlowName));
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy, createMockProcessor("/asyncComp")));
assertThat(event.getContext().getProcessorsTrace(),
hasExecutedProcessors("/comp @ " + APP_ID + ":null:null", "/asyncComp @ " + APP_ID + ":null:null"));
}
@Test
public void joinStack() {
Event event = buildEvent("joinStack");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(event, createMockProcessor("/scatter-gather")));
assertThat(getContextInfo(event, rootFlowConstruct),
is("at " + rootFlowConstruct.getName() + "(/scatter-gather @ " + APP_ID + ":null:null)"));
Event eventCopy0 = buildEvent("joinStack_0", event.getFlowCallStack().clone());
Event eventCopy1 = buildEvent("joinStack_1", event.getFlowCallStack().clone());
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy0, createMockProcessor("/route_0")));
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy1,
createMockProcessor(NESTED_FLOW_NAME + "_ref")));
PipelineMessageNotification pipelineNotificationNested = buildPipelineNotification(eventCopy1, NESTED_FLOW_NAME);
manager.onPipelineNotificationStart(pipelineNotificationNested);
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy1, createMockProcessor("/route_1")));
assertThat(getContextInfo(eventCopy1, rootFlowConstruct),
is("at " + NESTED_FLOW_NAME + "(/route_1 @ " + APP_ID + ":null:null)" + System.lineSeparator()
+ "at " + ROOT_FLOW_NAME + "(" + NESTED_FLOW_NAME + "_ref @ " + APP_ID + ":null:null)"));
manager.onPipelineNotificationComplete(pipelineNotificationNested);
assertThat(getContextInfo(event, rootFlowConstruct),
is("at " + rootFlowConstruct.getName() + "(/scatter-gather @ " + APP_ID + ":null:null)"));
manager.onPipelineNotificationComplete(pipelineNotification);
assertThat(getContextInfo(event, rootFlowConstruct), is(""));
}
public Processor createMockProcessor(String processorPath) {
ComponentLocation componentLocation = mock(ComponentLocation.class);
when(componentLocation.getLocation()).thenReturn(processorPath);
AnnotatedObject annotatedMessageProcessor =
(AnnotatedObject) mock(Processor.class,
withSettings().extraInterfaces(AnnotatedObject.class).defaultAnswer(RETURNS_DEEP_STUBS));
when(annotatedMessageProcessor.getAnnotation(any())).thenReturn(null);
when(annotatedMessageProcessor.getLocation()).thenReturn(componentLocation);
return (Processor) annotatedMessageProcessor;
}
@Test
public void joinStackEnd() {
Event event = buildEvent("joinStack");
PipelineMessageNotification pipelineNotification = buildPipelineNotification(event, rootFlowConstruct.getName());
manager.onPipelineNotificationStart(pipelineNotification);
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(event, createMockProcessor("/scatter-gather")));
FlowCallStack flowCallStack = event.getFlowCallStack();
Event eventCopy0 = buildEvent("joinStack_0", flowCallStack.clone());
Event eventCopy1 = buildEvent("joinStack_1", flowCallStack.clone());
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy0, createMockProcessor("/route_0")));
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy1,
createMockProcessor(NESTED_FLOW_NAME + "_ref")));
PipelineMessageNotification pipelineNotificationNested = buildPipelineNotification(eventCopy1, NESTED_FLOW_NAME);
manager.onPipelineNotificationStart(pipelineNotificationNested);
manager.onMessageProcessorNotificationPreInvoke(buildProcessorNotification(eventCopy1, createMockProcessor("/route_1")));
manager.onPipelineNotificationComplete(pipelineNotificationNested);
manager.onPipelineNotificationComplete(pipelineNotification);
assertThat(event.getContext().getProcessorsTrace(),
hasExecutedProcessors("/scatter-gather @ " + APP_ID + ":null:null", "/route_0 @ " + APP_ID + ":null:null",
NESTED_FLOW_NAME + "_ref @ " + APP_ID + ":null:null",
"/route_1 @ " + APP_ID + ":null:null"));
}
@Test
public void mixedEvents() {
Event event1 = buildEvent("mixedEvents_1");
Event event2 = buildEvent("mixedEvents_2");
PipelineMessageNotification pipelineNotification1 = buildPipelineNotification(event1, rootFlowConstruct.getName());
PipelineMessageNotification pipelineNotification2 = buildPipelineNotification(event2, rootFlowConstruct.getName());
assertThat(getContextInfo(event1, rootFlowConstruct), is(""));
assertThat(getContextInfo(event2, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification1);
assertThat(getContextInfo(event1, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
assertThat(getContextInfo(event2, rootFlowConstruct), is(""));
manager.onPipelineNotificationStart(pipelineNotification2);
assertThat(getContextInfo(event1, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
assertThat(getContextInfo(event2, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
manager.onPipelineNotificationComplete(pipelineNotification1);
assertThat(getContextInfo(event1, rootFlowConstruct), is(""));
assertThat(getContextInfo(event2, rootFlowConstruct), is("at " + rootFlowConstruct.getName()));
manager.onPipelineNotificationComplete(pipelineNotification2);
assertThat(getContextInfo(event1, rootFlowConstruct), is(""));
assertThat(getContextInfo(event2, rootFlowConstruct), is(""));
}
protected Event buildEvent(String eventId) {
return buildEvent(eventId, new DefaultFlowCallStack());
}
protected Event buildEvent(String eventId, FlowCallStack flowStack) {
Event event = mock(Event.class);
when(event.getContext()).thenReturn(messageContext);
when(event.getFlowCallStack()).thenReturn(flowStack);
return event;
}
protected MessageProcessorNotification buildProcessorNotification(Event event, Processor processor) {
return MessageProcessorNotification.createFrom(event, null, processor, null, MESSAGE_PROCESSOR_PRE_INVOKE);
}
protected PipelineMessageNotification buildPipelineNotification(Event event, String name) {
Pipeline flowConstruct = mock(Pipeline.class);
when(flowConstruct.getName()).thenReturn(name);
return new PipelineMessageNotification(createInfo(event, null, null), flowConstruct, PROCESS_START);
}
private Matcher<ProcessorsTrace> hasExecutedProcessors(final String... expectedProcessors) {
return new TypeSafeMatcher<ProcessorsTrace>() {
private List<Matcher> failed = new ArrayList<>();
@Override
protected boolean matchesSafely(ProcessorsTrace processorsTrace) {
Matcher<Collection<? extends Object>> sizeMatcher = hasSize(expectedProcessors.length);
if (!sizeMatcher.matches(processorsTrace.getExecutedProcessors())) {
failed.add(sizeMatcher);
}
int i = 0;
for (String expectedProcessor : expectedProcessors) {
Matcher processorItemMatcher = is(expectedProcessor);
if (!processorItemMatcher.matches(processorsTrace.getExecutedProcessors().get(i))) {
failed.add(processorItemMatcher);
}
++i;
}
return failed.isEmpty();
}
@Override
public void describeTo(Description description) {
description.appendValue(Arrays.asList(expectedProcessors));
}
@Override
protected void describeMismatchSafely(ProcessorsTrace item, Description description) {
description.appendText("was ").appendValue(item.getExecutedProcessors());
}
};
}
}