/*
* 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.processor.chain;
import static org.mule.runtime.core.api.Event.setCurrentEvent;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.disposeIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.setFlowConstructIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.setMuleContextIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.stopIfNeeded;
import static org.mule.runtime.core.context.notification.MessageProcessorNotification.MESSAGE_PROCESSOR_POST_INVOKE;
import static org.mule.runtime.core.context.notification.MessageProcessorNotification.MESSAGE_PROCESSOR_PRE_INVOKE;
import static org.mule.runtime.core.execution.MessageProcessorExecutionTemplate.createExecutionTemplate;
import static org.mule.runtime.core.util.ExceptionUtils.createErrorEvent;
import static org.mule.runtime.core.util.ExceptionUtils.putContext;
import static org.mule.runtime.core.util.ExceptionUtils.updateMessagingExceptionWithError;
import static org.slf4j.LoggerFactory.getLogger;
import static reactor.core.publisher.Flux.from;
import static reactor.core.publisher.Flux.just;
import static reactor.core.publisher.Mono.empty;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.lifecycle.Startable;
import org.mule.runtime.api.message.Message;
import org.mule.runtime.api.meta.AbstractAnnotatedObject;
import org.mule.runtime.api.meta.AnnotatedObject;
import org.mule.runtime.api.streaming.CursorProvider;
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.construct.Flow;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.core.api.construct.Pipeline;
import org.mule.runtime.core.api.exception.MessagingExceptionHandler;
import org.mule.runtime.core.api.exception.MessagingExceptionHandlerAware;
import org.mule.runtime.core.api.processor.MessageProcessorChain;
import org.mule.runtime.core.api.processor.Processor;
import org.mule.runtime.core.api.processor.ReactiveProcessor;
import org.mule.runtime.core.api.registry.RegistrationException;
import org.mule.runtime.core.api.transport.LegacyInboundEndpoint;
import org.mule.runtime.core.context.notification.MessageProcessorNotification;
import org.mule.runtime.core.context.notification.ServerNotificationManager;
import org.mule.runtime.core.exception.MessagingException;
import org.mule.runtime.core.execution.MessageProcessorExecutionTemplate;
import org.mule.runtime.core.processor.interceptor.ReactiveInterceptorAdapter;
import org.mule.runtime.core.streaming.StreamingManager;
import org.mule.runtime.core.util.StringUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Builder needs to return a composite rather than the first MessageProcessor in the chain. This is so that if this chain is
* nested in another chain the next MessageProcessor in the parent chain is not injected into the first in the nested chain.
*/
public abstract class AbstractMessageProcessorChain extends AbstractAnnotatedObject implements MessageProcessorChain {
private static final Logger LOGGER = getLogger(AbstractMessageProcessorChain.class);
protected String name;
protected List<Processor> processors;
protected MuleContext muleContext;
protected FlowConstruct flowConstruct;
protected MessageProcessorExecutionTemplate messageProcessorExecutionTemplate = createExecutionTemplate();
protected MessagingExceptionHandler messagingExceptionHandler;
private StreamingManager streamingManager;
public AbstractMessageProcessorChain(List<Processor> processors) {
this(null, processors);
}
public AbstractMessageProcessorChain(String name, List<Processor> processors) {
this.name = name;
this.processors = processors;
}
@Override
public Event process(Event event) throws MuleException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Invoking %s with event %s", this, event));
}
if (event == null) {
return null;
}
return doProcess(event);
}
protected Event doProcess(Event event) throws MuleException {
for (Processor processor : getProcessorsToExecute()) {
setCurrentEvent(event);
event = messageProcessorExecutionTemplate.execute(processor, event);
if (event == null) {
return null;
}
}
return event;
}
@Override
public Publisher<Event> apply(Publisher<Event> publisher) {
List<BiFunction<Processor, ReactiveProcessor, ReactiveProcessor>> interceptors = resolveInterceptors();
Flux<Event> stream = from(publisher);
for (Processor processor : getProcessorsToExecute()) {
// Perform assembly for processor chain by transforming the existing publisher with a publisher function for each processor
// along with the interceptors that decorate it.
stream = stream.transform(applyInterceptors(interceptors, processor));
}
return stream;
}
private ReactiveProcessor applyInterceptors(List<BiFunction<Processor, ReactiveProcessor, ReactiveProcessor>> interceptorsToBeExecuted,
Processor processor) {
ReactiveProcessor interceptorWrapperProcessorFunction = processor;
// Take processor publisher function itself and transform it by applying interceptor transformations onto it.
for (BiFunction<Processor, ReactiveProcessor, ReactiveProcessor> interceptor : interceptorsToBeExecuted) {
interceptorWrapperProcessorFunction = interceptor.apply(processor, interceptorWrapperProcessorFunction);
}
return interceptorWrapperProcessorFunction;
}
private List<BiFunction<Processor, ReactiveProcessor, ReactiveProcessor>> resolveInterceptors() {
List<BiFunction<Processor, ReactiveProcessor, ReactiveProcessor>> interceptors =
new ArrayList<>();
// #1 Update MessagingException with failing processor if required, create Error and set error context.
interceptors.add((processor, next) -> stream -> from(stream)
.transform(next)
.onErrorMap(MessagingException.class, updateMessagingException(processor)));
// #2 Fire MessageProcessor notifications before and after processor execution.
interceptors.add((processor, next) -> stream -> from(stream)
.doOnNext(preNotification(processor))
.transform(next)
.doOnNext(postNotification(processor))
.doOnError(MessagingException.class, errorNotification(processor)));
// #3 Update ThreadLocal event before and after processor execution.
interceptors.add((processor, next) -> stream -> from(stream)
.doOnNext(event -> setCurrentEvent(event))
.transform(next)
.doOnNext(result -> setCurrentEvent(result)));
// #4 If the processor returns a CursorProvider, then have the StreamingManager manage it
interceptors.add((processor, next) -> stream -> from(stream)
.transform(next)
.map(result -> {
Object payload = result.getMessage().getPayload().getValue();
if (payload instanceof CursorProvider) {
Message message = Message.builder(result.getMessage()).payload(
streamingManager.manage((CursorProvider) payload,
result))
.build();
result = Event.builder(result).message(message).build();
}
return result;
}));
// #4 Apply processor interceptors.
muleContext.getProcessorInterceptorManager().getInterceptorFactories().stream()
.forEach(interceptorFactory -> {
ReactiveInterceptorAdapter reactiveInterceptorAdapter = new ReactiveInterceptorAdapter(interceptorFactory);
reactiveInterceptorAdapter.setFlowConstruct(flowConstruct);
interceptors.add(0, reactiveInterceptorAdapter);
});
// #6 Apply processing strategy (notifications will be fired, interceptors executed on processing thread as defined by the
// processing strategy. Use anonymous ReactiveProcessor to apply processing strategy to processor + previous interceptors
// while using the processing type of the processor itself.
if (flowConstruct instanceof Pipeline) {
interceptors
.add((processor, next) -> ((Pipeline) flowConstruct).getProcessingStrategy().onProcessor(new ReactiveProcessor() {
@Override
public Publisher<Event> apply(Publisher<Event> eventPublisher) {
return next.apply(eventPublisher);
}
@Override
public ProcessingType getProcessingType() {
return processor.getProcessingType();
}
}));
}
// #6 Handle errors that occur during Processor execution. This is done outside to any scheduling to ensure errors in
// scheduling such as RejectedExecutionException's can be handled cleanly
interceptors.add((processor, next) -> stream -> from(stream).concatMap(event -> just(event)
.transform(next)
.onErrorResume(RejectedExecutionException.class,
throwable -> handleError(event.getContext())
.apply(updateMessagingExceptionWithError(new MessagingException(event, throwable, processor),
processor, flowConstruct)))
.onErrorResume(MessagingException.class, handleError(event.getContext()))));
return interceptors;
}
private Function<MessagingException, MessagingException> updateMessagingException(Processor processor) {
return exception -> {
Processor failing = exception.getFailingMessageProcessor();
if (failing == null) {
failing = processor;
exception = new MessagingException(exception.getI18nMessage(), exception.getEvent(), exception.getCause(), processor);
}
return updateMessagingExceptionWithError(exception, failing, flowConstruct);
};
}
private Function<MessagingException, Publisher<Event>> handleError(EventContext eventContext) {
if (flowConstruct instanceof Pipeline && ((Pipeline) flowConstruct).getMessageSource() instanceof LegacyInboundEndpoint) {
// TODO MULE-11023 Migrate transaction execution template mechanism to use non-blocking API
// Don't handle exception in chain as it needs to be handled by HandleExceptionInterceptor.
return messagingException -> {
eventContext.error(messagingException);
return empty();
};
} else {
return messagingException -> Mono.from(getMessagingExceptionHandler().apply(messagingException))
.flatMap(handled -> {
eventContext.success(handled);
return Mono.<Event>empty();
})
.onErrorResume(rethrown -> {
eventContext.error(rethrown);
return empty();
});
}
}
private Consumer<Event> preNotification(Processor processor) {
return event -> {
if (event.isNotificationsEnabled()) {
fireNotification(muleContext.getNotificationManager(), flowConstruct, event, processor, null,
MESSAGE_PROCESSOR_PRE_INVOKE);
}
};
}
private Consumer<Event> postNotification(Processor processor) {
return event -> {
if (event.isNotificationsEnabled()) {
fireNotification(muleContext.getNotificationManager(), flowConstruct, event, processor, null,
MESSAGE_PROCESSOR_POST_INVOKE);
}
};
}
private Consumer<MessagingException> errorNotification(Processor processor) {
return exception -> {
if (exception.getEvent().isNotificationsEnabled()) {
fireNotification(muleContext.getNotificationManager(), flowConstruct, exception.getEvent(), processor, exception,
MESSAGE_PROCESSOR_POST_INVOKE);
}
};
}
private void fireNotification(ServerNotificationManager serverNotificationManager, FlowConstruct flowConstruct,
Event event, Processor processor, MessagingException exceptionThrown,
int action) {
if (serverNotificationManager != null
&& serverNotificationManager.isNotificationEnabled(MessageProcessorNotification.class)) {
if (processor instanceof AnnotatedObject
&& ((AnnotatedObject) processor).getLocation() != null) {
serverNotificationManager
.fireNotification(MessageProcessorNotification.createFrom(event, flowConstruct, processor, exceptionThrown, action));
}
}
}
protected List<Processor> getProcessorsToExecute() {
return processors;
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append(getClass().getSimpleName());
if (StringUtils.isNotBlank(name)) {
string.append(String.format(" '%s' ", name));
}
Iterator<Processor> mpIterator = processors.iterator();
final String nl = String.format("%n");
// TODO have it print the nested structure with indents increasing for nested MPCs
if (mpIterator.hasNext()) {
string.append(String.format("%n[ "));
while (mpIterator.hasNext()) {
Processor mp = mpIterator.next();
final String indented = StringUtils.replace(mp.toString(), nl, String.format("%n "));
string.append(String.format("%n %s", indented));
if (mpIterator.hasNext()) {
string.append(", ");
}
}
string.append(String.format("%n]"));
}
return string.toString();
}
@Override
public List<Processor> getMessageProcessors() {
return processors;
}
protected List<Processor> getMessageProcessorsForLifecycle() {
return processors;
}
@Override
public void setMessagingExceptionHandler(MessagingExceptionHandler messagingExceptionHandler) {
this.messagingExceptionHandler = messagingExceptionHandler;
for (Processor processor : processors) {
if (processor instanceof MessagingExceptionHandlerAware) {
((MessagingExceptionHandlerAware) processor).setMessagingExceptionHandler(messagingExceptionHandler);
}
}
}
public MessagingExceptionHandler getMessagingExceptionHandler() {
return messagingExceptionHandler != null ? messagingExceptionHandler : flowConstruct.getExceptionListener();
}
@Override
public void setMuleContext(MuleContext muleContext) {
this.muleContext = muleContext;
this.messageProcessorExecutionTemplate.setMuleContext(muleContext);
setMuleContextIfNeeded(getMessageProcessorsForLifecycle(), muleContext);
}
@Override
public void setFlowConstruct(FlowConstruct flowConstruct) {
this.flowConstruct = flowConstruct;
this.messageProcessorExecutionTemplate.setFlowConstruct(flowConstruct);
setFlowConstructIfNeeded(getMessageProcessorsForLifecycle(), flowConstruct);
}
@Override
public void initialise() throws InitialisationException {
try {
streamingManager = muleContext.getRegistry().lookupObject(StreamingManager.class);
} catch (RegistrationException e) {
throw new InitialisationException(e, this);
}
initialiseIfNeeded(getMessageProcessorsForLifecycle(), true, muleContext);
}
@Override
public void start() throws MuleException {
List<Processor> startedProcessors = new ArrayList<>();
try {
for (Processor processor : getMessageProcessorsForLifecycle()) {
if (processor instanceof Startable) {
((Startable) processor).start();
startedProcessors.add(processor);
}
}
} catch (MuleException e) {
stopIfNeeded(getMessageProcessorsForLifecycle());
throw e;
}
}
@Override
public void stop() throws MuleException {
stopIfNeeded(getMessageProcessorsForLifecycle());
}
@Override
public void dispose() {
disposeIfNeeded(getMessageProcessorsForLifecycle(), LOGGER);
}
}