/* * 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.internal.construct; import static org.apache.commons.lang.StringUtils.isNotEmpty; import static org.mule.runtime.api.util.Preconditions.checkArgument; import static org.mule.runtime.core.api.Event.setCurrentEvent; import static org.mule.runtime.core.api.processor.MessageProcessors.processToApply; import static org.mule.runtime.core.execution.ErrorHandlingExecutionTemplate.createErrorHandlingExecutionTemplate; import static org.mule.runtime.core.util.ExceptionUtils.updateMessagingExceptionWithError; import static reactor.core.publisher.Flux.from; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.message.Error; import org.mule.runtime.core.api.DefaultMuleException; import org.mule.runtime.core.api.Event; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.connector.ReplyToHandler; import org.mule.runtime.core.api.construct.Flow; import org.mule.runtime.core.api.construct.Flow.Builder; import org.mule.runtime.core.api.exception.MessagingExceptionHandler; import org.mule.runtime.core.api.execution.ExecutionTemplate; import org.mule.runtime.core.api.processor.MessageProcessorChainBuilder; import org.mule.runtime.core.api.processor.Processor; import org.mule.runtime.core.api.processor.strategy.ProcessingStrategyFactory; import org.mule.runtime.core.api.source.MessageSource; import org.mule.runtime.core.config.i18n.CoreMessages; import org.mule.runtime.core.exception.MessagingException; import org.mule.runtime.core.interceptor.ProcessingTimeInterceptor; import org.mule.runtime.core.internal.construct.processor.FlowConstructStatisticsMessageProcessor; import org.mule.runtime.core.management.stats.FlowConstructStatistics; import org.mule.runtime.core.processor.strategy.DefaultFlowProcessingStrategyFactory; import org.mule.runtime.core.routing.requestreply.AsyncReplyToPropertyRequestReplyReplier; import java.util.List; import java.util.Optional; import java.util.concurrent.RejectedExecutionException; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; /** * Creates instances of {@link Flow} with a default implementation * * <p/> * Builder instance can be configured using the methods that follow the builder pattern until the flow is built. After that point, * builder methods will fail to update the builder state. */ public class DefaultFlowBuilder implements Builder { private final String name; private final MuleContext muleContext; private MessageSource messageSource; private List<Processor> messageProcessors; private MessagingExceptionHandler exceptionListener; private ProcessingStrategyFactory processingStrategyFactory; private DefaultFlow flow; /** * Creates a new builder * * @param name name of the flow to be created. Non empty. * @param muleContext context where the flow will be associated with. Non null. */ public DefaultFlowBuilder(String name, MuleContext muleContext) { checkArgument(isNotEmpty(name), "name cannot be empty"); checkArgument(muleContext != null, "muleContext cannot be null"); this.name = name; this.muleContext = muleContext; } /** * Configures the message source for the flow. * * @param messageSource message source to use. Non null. * @return same builder instance. */ @Override public Builder messageSource(MessageSource messageSource) { checkImmutable(); checkArgument(messageSource != null, "messageSource cannot be null"); this.messageSource = messageSource; return this; } /** * Configures the message processors to execute as part of flow. * * @param messageProcessors message processors to execute. Non null. * @return same builder instance. */ @Override public Builder messageProcessors(List<Processor> messageProcessors) { checkImmutable(); checkArgument(messageProcessors != null, "messageProcessors cannot be null"); this.messageProcessors = messageProcessors; return this; } /** * Configures the exception listener to manage exceptions thrown on the flow execution. * * @param exceptionListener exception listener to use on the flow. * @return same builder instance */ @Override public Builder messagingExceptionHandler(MessagingExceptionHandler exceptionListener) { checkImmutable(); this.exceptionListener = exceptionListener; return this; } /** * Configures the factory used to create processing strategies on the created flow. * * @param processingStrategyFactory factory to create processing strategies. Non null. * @return same builder instance. */ @Override public Builder processingStrategyFactory(ProcessingStrategyFactory processingStrategyFactory) { checkImmutable(); checkArgument(processingStrategyFactory != null, "processingStrategyFactory cannot be null"); this.processingStrategyFactory = processingStrategyFactory; return this; } /** * Builds a flow with the provided configuration. * * @return a new flow instance. */ @Override public Flow build() { checkImmutable(); flow = new DefaultFlow(name, muleContext); if (messageSource != null) { flow.setMessageSource(messageSource); } if (messageProcessors != null) { flow.setMessageProcessors(messageProcessors); } if (exceptionListener != null) { flow.setExceptionListener(exceptionListener); } if (processingStrategyFactory != null) { flow.setProcessingStrategyFactory(processingStrategyFactory); } return flow; } protected final void checkImmutable() { if (flow != null) { throw new IllegalStateException("Cannot change attributes once the flow was built"); } } /** * Default implementation of {@link Flow} */ public static class DefaultFlow extends AbstractPipeline implements Flow { protected DefaultFlow(String name, MuleContext muleContext) { super(name, muleContext); } @Override public Event process(final Event event) throws MuleException { if (useBlockingCodePath()) { return processBlockingSynchronous(event); } else { return processToApply(event, this); } } /* * Process flow using blocking {@link Processor#process(Event)} methods synchronously without apply any processing strategy. * Note that individual processors internally may still you asynchronous non-blocking behaviour to achieved required * functionality. */ private Event processBlockingSynchronous(Event event) throws MessagingException, DefaultMuleException { // TODO MULE-11023 Migrate transaction execution template mechanism to use non-blocking API final Event newEvent = createMuleEventForCurrentFlow(event, event.getReplyToDestination(), event.getReplyToHandler()); try { ExecutionTemplate<Event> executionTemplate = createErrorHandlingExecutionTemplate(muleContext, this, getExceptionListener()); Event result = executionTemplate.execute(() -> pipeline.process(newEvent)); newEvent.getContext().success(result); return createReturnEventForParentFlowConstruct(result, event); } catch (MessagingException e) { e.setProcessedEvent(createReturnEventForParentFlowConstruct(e.getEvent(), event)); newEvent.getContext().error(e); throw e; } catch (Exception e) { newEvent.getContext().error(e); resetRequestContextEvent(event); throw new DefaultMuleException(CoreMessages.createStaticMessage("Flow execution exception"), e); } } @Override public Publisher<Event> apply(Publisher<Event> publisher) { return from(publisher) .doOnNext(assertStarted()) .flatMap(event -> { Event request = createMuleEventForCurrentFlow(event, event.getReplyToDestination(), event.getReplyToHandler()); // Use sink and potentially shared stream in Flow by dispatching incoming event via sink and then using // response publisher to operate of the result of flow processing before returning try { sink.accept(request); } catch (RejectedExecutionException ree) { request.getContext() .error(updateMessagingExceptionWithError(new MessagingException(event, ree, this), this, this)); } return Mono.from(request.getContext().getResponsePublisher()) .map(r -> { Event result = createReturnEventForParentFlowConstruct(r, event); return result; }) .onErrorMap(MessagingException.class, me -> { me.setProcessedEvent(createReturnEventForParentFlowConstruct(me.getEvent(), event)); return me; }); }); } private Event createMuleEventForCurrentFlow(Event event, Object replyToDestination, ReplyToHandler replyToHandler) { // DefaultReplyToHandler is used differently and should only be invoked by the first flow and not any // referenced flows. If it is passed on they two replyTo responses are sent. replyToHandler = null; // TODO MULE-10013 // Create new event for current flow with current flowConstruct, replyToHandler etc. event = Event.builder(event).flow(this).replyToHandler(replyToHandler).replyToDestination(replyToDestination).build(); resetRequestContextEvent(event); return event; } private Event createReturnEventForParentFlowConstruct(Event result, Event original) { if (result != null) { Optional<Error> errorOptional = result.getError(); // TODO MULE-10013 // Create new event with original FlowConstruct, ReplyToHandler and synchronous result = Event.builder(result).flow(original.getFlowConstruct()) .replyToHandler(original.getReplyToHandler()) .replyToDestination(original.getReplyToDestination()) .error(errorOptional.orElse(null)).build(); } resetRequestContextEvent(result); return result; } private void resetRequestContextEvent(Event event) { // Update RequestContext ThreadLocal for backwards compatibility setCurrentEvent(event); } @Override protected void configurePreProcessors(MessageProcessorChainBuilder builder) throws MuleException { super.configurePreProcessors(builder); builder.chain(new ProcessingTimeInterceptor()); builder.chain(new FlowConstructStatisticsMessageProcessor()); } @Override protected void configurePostProcessors(MessageProcessorChainBuilder builder) throws MuleException { builder.chain(new AsyncReplyToPropertyRequestReplyReplier()); super.configurePostProcessors(builder); } /** * {@inheritDoc} * * @return a {@link DefaultFlowProcessingStrategyFactory} */ @Override protected ProcessingStrategyFactory createDefaultProcessingStrategyFactory() { return new DefaultFlowProcessingStrategyFactory(); } @Override public String getConstructType() { return "Flow"; } @Override protected void configureStatistics() { statistics = new FlowConstructStatistics(getConstructType(), name); statistics.setEnabled(muleContext.getStatistics().isEnabled()); muleContext.getStatistics().add(statistics); } @Override public boolean isSynchronous() { return getProcessingStrategy() != null ? getProcessingStrategy().isSynchronous() : true; } } }