/*
* 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;
import static java.util.Collections.singletonList;
import static org.mule.runtime.core.api.context.notification.EnrichedNotificationInfo.createInfo;
import static org.mule.runtime.core.api.processor.MessageProcessors.processToApply;
import static org.mule.runtime.core.api.rx.Exceptions.UNEXPECTED_EXCEPTION_PREDICATE;
import static org.mule.runtime.core.api.rx.Exceptions.checkedConsumer;
import static org.mule.runtime.core.config.i18n.CoreMessages.asyncDoesNotSupportTransactions;
import static org.mule.runtime.core.config.i18n.CoreMessages.objectIsNull;
import static org.mule.runtime.core.context.notification.AsyncMessageNotification.PROCESS_ASYNC_COMPLETE;
import static org.mule.runtime.core.context.notification.AsyncMessageNotification.PROCESS_ASYNC_SCHEDULED;
import static org.mule.runtime.core.transaction.TransactionCoordination.isTransactionActive;
import static reactor.core.publisher.Flux.from;
import static reactor.core.publisher.Flux.just;
import static reactor.core.scheduler.Schedulers.fromExecutorService;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.lifecycle.Initialisable;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.lifecycle.Startable;
import org.mule.runtime.api.lifecycle.Stoppable;
import org.mule.runtime.api.scheduler.Scheduler;
import org.mule.runtime.core.DefaultEventContext;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.construct.FlowConstruct;
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.strategy.ProcessingStrategy;
import org.mule.runtime.core.api.routing.RoutingException;
import org.mule.runtime.core.api.scheduler.SchedulerService;
import org.mule.runtime.core.context.notification.AsyncMessageNotification;
import org.mule.runtime.core.exception.MessagingException;
import org.mule.runtime.core.session.DefaultMuleSession;
import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes {@link Event}'s asynchronously using a {@link ProcessingStrategy} to schedule asynchronous processing of
* MessageProcessor delegate configured the next {@link Processor}. The next {@link Processor} is therefore be executed in a
* different thread regardless of the exchange-pattern configured on the inbound endpoint. If a transaction is present then an
* exception is thrown.
*/
public class AsyncDelegateMessageProcessor extends AbstractMessageProcessorOwner
implements Processor, Initialisable, Startable, Stoppable, MessagingExceptionHandlerAware {
@Inject
private SchedulerService schedulerService;
protected Logger logger = LoggerFactory.getLogger(getClass());
protected MessageProcessorChain delegate;
private Scheduler scheduler;
protected String name;
private MessagingExceptionHandler messagingExceptionHandler;
public AsyncDelegateMessageProcessor(MessageProcessorChain delegate) {
this.delegate = delegate;
}
public AsyncDelegateMessageProcessor(MessageProcessorChain delegate, String name) {
this.delegate = delegate;
this.name = name;
}
@Override
public void initialise() throws InitialisationException {
if (delegate == null) {
throw new InitialisationException(objectIsNull("delegate message processor"), this);
}
super.initialise();
}
@Override
public void start() throws MuleException {
scheduler = schedulerService.cpuLightScheduler(muleContext.getSchedulerBaseConfig().withName(name));
super.start();
}
@Override
public void stop() throws MuleException {
scheduler.stop();
scheduler = null;
super.stop();
}
@Override
public Event process(Event event) throws MuleException {
return processToApply(event, this);
}
private void assertNotTransactional(Event event) throws RoutingException {
if (isTransactionActive()) {
throw new RoutingException(asyncDoesNotSupportTransactions(), delegate);
}
}
@Override
public Publisher<Event> apply(Publisher<Event> publisher) {
return from(publisher)
.doOnNext(checkedConsumer(event -> assertNotTransactional(event)))
.doOnNext(request -> just(request)
.map(event -> asyncEvent(request))
.transform(innerPublisher -> from(innerPublisher)
.doOnNext(fireAsyncScheduledNotification(flowConstruct))
.doOnNext(asyncRequest -> just(asyncRequest)
.publishOn(fromExecutorService(scheduler))
.transform(delegate)
.doOnNext(event -> fireAsyncCompleteNotification(event, flowConstruct, null))
.doOnError(MessagingException.class, e -> fireAsyncCompleteNotification(e.getEvent(), flowConstruct, e))
.onErrorResume(MessagingException.class, messagingExceptionHandler)
.doOnError(UNEXPECTED_EXCEPTION_PREDICATE,
exception -> logger.error("Unhandled exception in async processing.",
exception))
// Even though no response is ever used with this processor, complete the EventContext anyway, so
// parent context completes when async processing is complete.
.doOnNext(event -> asyncRequest.getContext().success(event))
.doOnError(throwable -> asyncRequest.getContext().error(throwable))
.subscribe()))
.onErrorResume(MessagingException.class, messagingExceptionHandler)
.doOnError(UNEXPECTED_EXCEPTION_PREDICATE,
exception -> logger.error("Unhandled exception in async processing.", exception))
.subscribe());
}
private Event asyncEvent(Event event) {
// Clone event, make it async and remove ReplyToHandler
return Event.builder(DefaultEventContext.child(event.getContext()), event)
.replyToHandler(null)
.session(new DefaultMuleSession(event.getSession())).build();
}
private Consumer<Event> fireAsyncScheduledNotification(FlowConstruct flowConstruct) {
return event -> muleContext.getNotificationManager()
.fireNotification(new AsyncMessageNotification(createInfo(event, null, this), flowConstruct,
PROCESS_ASYNC_SCHEDULED));
}
private void fireAsyncCompleteNotification(Event event, FlowConstruct flowConstruct, MessagingException exception) {
muleContext.getNotificationManager()
.fireNotification(new AsyncMessageNotification(createInfo(event, exception, this), flowConstruct,
PROCESS_ASYNC_COMPLETE));
}
@Override
protected List<Processor> getOwnedMessageProcessors() {
return singletonList(delegate);
}
@Override
public void setMessagingExceptionHandler(MessagingExceptionHandler messagingExceptionHandler) {
this.messagingExceptionHandler = messagingExceptionHandler;
delegate.setMessagingExceptionHandler(messagingExceptionHandler);
}
}