/*
* 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.functional.junit4;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.fail;
import static org.mule.runtime.core.execution.TransactionalExecutionTemplate.createTransactionalExecutionTemplate;
import org.mule.functional.functional.FlowAssert;
import org.mule.runtime.api.lifecycle.Disposable;
import org.mule.runtime.api.scheduler.Scheduler;
import org.mule.runtime.api.streaming.Cursor;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.construct.Flow;
import org.mule.runtime.core.api.execution.ExecutionCallback;
import org.mule.runtime.core.api.execution.ExecutionTemplate;
import org.mule.runtime.core.api.transaction.TransactionConfig;
import org.mule.runtime.core.api.transaction.TransactionFactory;
import org.mule.runtime.core.exception.MessagingException;
import org.mule.runtime.core.transaction.MuleTransactionConfig;
import java.util.concurrent.ExecutionException;
import org.apache.commons.collections.Transformer;
import reactor.core.publisher.MonoProcessor;
/**
* Provides a fluent API for running events through flows.
* <p>
* This runner is <b>not</b> thread-safe.
*/
public class FlowRunner extends FlowConstructRunner<FlowRunner> implements Disposable {
private String flowName;
private ExecutionTemplate<Event> txExecutionTemplate = callback -> callback.process();
private Transformer responseEventTransformer = input -> input;
private Scheduler scheduler;
private MonoProcessor externalCompletionCallback = MonoProcessor.create();
/**
* Initializes this flow runner.
*
* @param muleContext the context of the mule application
* @param flowName the name of the flow to run events through
*/
public FlowRunner(MuleContext muleContext, String flowName) {
super(muleContext);
this.flowName = flowName;
}
/**
* Configures the flow to run inside a transaction.
*
* @param action The action to do at the start of the transactional block. See {@link TransactionConfig} constants.
* @param factory See {@link MuleTransactionConfig#setFactory(TransactionFactory)}.
* @return this {@link FlowRunner}
*/
public FlowRunner transactionally(TransactionConfigEnum action, TransactionFactory factory) {
MuleTransactionConfig transactionConfig = new MuleTransactionConfig(action.getAction());
transactionConfig.setFactory(factory);
txExecutionTemplate = createTransactionalExecutionTemplate(muleContext, transactionConfig);
return this;
}
/**
* Makes all open {@link Cursor cursors} to not be closed when the executed flow is finished but when the test is disposed
*
* @return {@code this} {@link FlowRunner}
*/
public FlowRunner keepStreamsOpen() {
eventBuilder.setExternalCompletionCallback(externalCompletionCallback);
return this;
}
/**
* Run {@link Flow} as a task of a given {@link Scheduler}.
*
* @param scheduler the scheduler to use to run the {@link Flow}.
* @return this {@link FlowRunner}
* @see {@link org.mule.runtime.core.api.scheduler.SchedulerService}
*/
public FlowRunner withScheduler(Scheduler scheduler) {
this.scheduler = scheduler;
return this;
}
/**
* Runs the specified flow with the provided event and configuration, and performs a {@link FlowAssert#verify(String))}
* afterwards.
* <p>
* If this is called multiple times, the <b>same</b> event will be sent. To force the creation of a new event, use
* {@link #reset()}.
*
* @return the resulting <code>MuleEvent</code>
* @throws Exception
*/
public Event run() throws Exception {
return runAndVerify(flowName);
}
/**
* Runs the specified flow with the provided event and configuration.
* <p>
* If this is called multiple times, the <b>same</b> event will be sent. To force the creation of a new event, use
* {@link #reset()}.
*
* @return the resulting <code>MuleEvent</code>
* @throws Exception
*/
public Event runNoVerify() throws Exception {
return runAndVerify(new String[] {});
}
/**
* Runs the specified flow with the provided event and configuration, and performs a {@link FlowAssert#verify(String))} for each
* {@code flowNamesToVerify} afterwards.
* <p>
* If this is called multiple times, the <b>same</b> event will be sent. To force the creation of a new event, use
* {@link #reset()}.
*
* @param flowNamesToVerify the names of the flows to {@link FlowAssert#verify(String))} afterwards.
* @return the resulting <code>MuleEvent</code>
* @throws Exception
*/
public Event runAndVerify(String... flowNamesToVerify) throws Exception {
Flow flow = (Flow) getFlowConstruct();
Event response;
if (scheduler == null) {
response = txExecutionTemplate.execute(getFlowRunCallback(flow));
} else {
try {
response = scheduler.submit(() -> txExecutionTemplate.execute(getFlowRunCallback(flow))).get();
} catch (ExecutionException executionException) {
Throwable cause = executionException.getCause();
throw cause instanceof Exception ? (Exception) cause : new RuntimeException(cause);
}
}
for (String flowNameToVerify : flowNamesToVerify) {
FlowAssert.verify(flowNameToVerify);
}
return (Event) responseEventTransformer.transform(response);
}
/**
* Dispatches to the specified flow with the provided event and configuration, and performs a {@link FlowAssert#verify(String))}
* afterwards.
* <p>
* If this is called multiple times, the <b>same</b> event will be sent. To force the creation of a new event, use
* {@link #reset()}.
* <p>
* Dispatch behaves differently to {@link FlowRunner#run()} in that it does not propagate any exceptions to the test case or
* return a result.
*/
public void dispatch() throws Exception {
Flow flow = (Flow) getFlowConstruct();
try {
txExecutionTemplate.execute(getFlowDispatchCallback(flow));
} catch (Exception e) {
// Ignore
}
FlowAssert.verify(flowName);
}
/**
* Dispatches to the specified flow with the provided event and configuration in a new IO thread, and performs a
* {@link FlowAssert#verify(String))} afterwards.
* <p>
* If this is called multiple times, the <b>same</b> event will be sent. To force the creation of a new event, use
* {@link #reset()}.
* <p>
* Dispatch behaves differently to {@link FlowRunner#run()} in that it does not propagate any exceptions to the test case or
* return a result.
*/
public void dispatchAsync() throws Exception {
Flow flow = (Flow) getFlowConstruct();
scheduler =
muleContext.getSchedulerService().ioScheduler(muleContext.getSchedulerBaseConfig().withShutdownTimeout(0, SECONDS));
try {
scheduler.submit(() -> txExecutionTemplate.execute(getFlowDispatchCallback(flow)));
} catch (Exception e) {
// Ignore
}
FlowAssert.verify(flowName);
}
private ExecutionCallback<Event> getFlowRunCallback(final Flow flow) {
return () -> flow.process(getOrBuildEvent());
}
private ExecutionCallback<Event> getFlowDispatchCallback(final Flow flow) {
return () -> {
flow.process(getOrBuildEvent());
return null;
};
}
/**
* Runs the specified flow with the provided event and configuration expecting a failure. Will fail if there's no failure
* running the flow.
*
* @return the message exception return by the flow
* @throws Exception
*/
public MessagingException runExpectingException() throws Exception {
try {
run();
fail("Flow executed successfully. Expecting exception");
return null;
} catch (MessagingException e) {
return e;
}
}
@Override
public String getFlowConstructName() {
return flowName;
}
@Override
public void dispose() {
if (scheduler != null) {
scheduler.stop();
}
externalCompletionCallback.onComplete();
}
}