/* * 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.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.context.notification.PipelineMessageNotification.PROCESS_COMPLETE; import static org.mule.runtime.core.context.notification.PipelineMessageNotification.PROCESS_END; import static org.mule.runtime.core.context.notification.PipelineMessageNotification.PROCESS_START; import static org.mule.runtime.core.internal.util.rx.Operators.requestUnbounded; import static org.mule.runtime.core.transaction.TransactionCoordination.isTransactionActive; import static reactor.core.Exceptions.propagate; import static reactor.core.publisher.Flux.from; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.lifecycle.LifecycleException; import org.mule.runtime.api.meta.AbstractAnnotatedObject; 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.config.MuleProperties; import org.mule.runtime.core.api.connector.ConnectException; import org.mule.runtime.core.api.construct.Pipeline; import org.mule.runtime.core.api.processor.InternalMessageProcessor; import org.mule.runtime.core.api.processor.MessageProcessorBuilder; import org.mule.runtime.core.api.processor.MessageProcessorChain; import org.mule.runtime.core.api.processor.MessageProcessorChainBuilder; import org.mule.runtime.core.api.processor.Processor; import org.mule.runtime.core.api.processor.ReactiveProcessor; import org.mule.runtime.core.api.processor.Sink; import org.mule.runtime.core.api.processor.strategy.ProcessingStrategy; import org.mule.runtime.core.api.processor.strategy.ProcessingStrategyFactory; import org.mule.runtime.core.api.scheduler.SchedulerService; import org.mule.runtime.core.api.source.ClusterizableMessageSource; import org.mule.runtime.core.api.source.MessageSource; import org.mule.runtime.core.config.i18n.CoreMessages; import org.mule.runtime.core.context.notification.PipelineMessageNotification; import org.mule.runtime.core.exception.MessagingException; import org.mule.runtime.core.processor.IdempotentRedeliveryPolicy; import org.mule.runtime.core.processor.chain.DefaultMessageProcessorChainBuilder; import org.mule.runtime.core.processor.strategy.DefaultFlowProcessingStrategyFactory; import org.mule.runtime.core.processor.strategy.DirectProcessingStrategyFactory; import org.mule.runtime.core.source.ClusterizableMessageSourceWrapper; import org.mule.runtime.core.streaming.StreamingManager; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; /** * Abstract implementation of {@link AbstractFlowConstruct} that allows a list of {@link Processor}s that will be used to process * messages to be configured. These MessageProcessors are chained together using the {@link DefaultMessageProcessorChainBuilder}. * <p/> * If no message processors are configured then the source message is simply returned. */ public abstract class AbstractPipeline extends AbstractFlowConstruct implements Pipeline { protected MessageSource messageSource; protected MessageProcessorChain pipeline; protected final SchedulerService schedulerService; protected StreamingManager streamingManager; protected List<Processor> messageProcessors = Collections.emptyList(); protected ProcessingStrategyFactory processingStrategyFactory; protected ProcessingStrategy processingStrategy; private boolean canProcessMessage = false; private Cache<String, EventContext> eventContextCache = CacheBuilder.newBuilder().weakValues().build(); protected Sink sink; public AbstractPipeline(String name, MuleContext muleContext) { super(name, muleContext); this.schedulerService = muleContext.getSchedulerService(); initialiseProcessingStrategy(); } /** * Creates a {@link Processor} that will process messages from the configured {@link MessageSource} . * <p> * The default implementation of this methods uses a {@link DefaultMessageProcessorChainBuilder} and allows a chain of * {@link Processor}s to be configured using the * {@link #configureMessageProcessors(org.mule.runtime.core.api.processor.MessageProcessorChainBuilder)} method but if you wish * to use another {@link MessageProcessorBuilder} or just a single {@link Processor} then this method can be overridden and * return a single {@link Processor} instead. */ protected MessageProcessorChain createPipeline() throws MuleException { DefaultMessageProcessorChainBuilder builder = new DefaultMessageProcessorChainBuilder(); builder.setName("'" + getName() + "' processor chain"); configurePreProcessors(builder); configureMessageProcessors(builder); configurePostProcessors(builder); return builder.build(); } /** * A fallback method for creating a {@link ProcessingStrategyFactory} to be used in case the user hasn't specified one through * either {@link #setProcessingStrategyFactory(ProcessingStrategyFactory)}, through * {@link MuleConfiguration#getDefaultProcessingStrategyFactory()} or the * {@link MuleProperties#MULE_DEFAULT_PROCESSING_STRATEGY} system property * * @return a {@link DirectProcessingStrategyFactory} */ protected ProcessingStrategyFactory createDefaultProcessingStrategyFactory() { return new DirectProcessingStrategyFactory(); } private void initialiseProcessingStrategy() { if (processingStrategy == null) { if (processingStrategyFactory == null) { final ProcessingStrategyFactory defaultProcessingStrategyFactory = muleContext.getConfiguration().getDefaultProcessingStrategyFactory(); if (defaultProcessingStrategyFactory == null) { processingStrategyFactory = createDefaultProcessingStrategyFactory(); } else { processingStrategyFactory = defaultProcessingStrategyFactory; } } processingStrategy = processingStrategyFactory.create(muleContext, getName()); } } protected void configurePreProcessors(MessageProcessorChainBuilder builder) throws MuleException { builder.chain(new ProcessorStartCompleteProcessor()); } protected void configurePostProcessors(MessageProcessorChainBuilder builder) throws MuleException { builder.chain(new ProcessEndProcessor()); } @Override public void setMessageProcessors(List<Processor> messageProcessors) { this.messageProcessors = messageProcessors; } @Override public List<Processor> getMessageProcessors() { return messageProcessors; } @Override public MessageSource getMessageSource() { return messageSource; } @Override public void setMessageSource(MessageSource messageSource) { if (messageSource instanceof ClusterizableMessageSource) { this.messageSource = new ClusterizableMessageSourceWrapper(muleContext, (ClusterizableMessageSource) messageSource, this); } else { this.messageSource = messageSource; } } @Override public boolean isSynchronous() { return processingStrategy.isSynchronous(); } @Override public ProcessingStrategyFactory getProcessingStrategyFactory() { return processingStrategyFactory; } @Override public void setProcessingStrategyFactory(ProcessingStrategyFactory processingStrategyFactory) { this.processingStrategyFactory = processingStrategyFactory; this.processingStrategy = null; } @Override public ProcessingStrategy getProcessingStrategy() { return processingStrategy; } @Override protected void doInitialise() throws MuleException { super.doInitialise(); initialiseProcessingStrategy(); streamingManager = muleContext.getRegistry().lookupObject(StreamingManager.class); pipeline = createPipeline(); if (messageSource != null) { messageSource.setListener(new Processor() { @Override public Event process(Event event) throws MuleException { if (useBlockingCodePath()) { return pipeline.process(event); } else { return processToApply(event, this); } } @Override public Publisher<Event> apply(Publisher<Event> publisher) { return from(publisher) .doOnNext(assertStarted()) .doOnNext(event -> sink.accept(event)) .flatMap(event -> Mono.from(event.getContext().getResponsePublisher())); } }); } injectFlowConstructMuleContext(messageSource); injectExceptionHandler(messageSource); injectFlowConstructMuleContext(pipeline); injectExceptionHandler(pipeline); initialiseIfInitialisable(messageSource); initialiseIfInitialisable(pipeline); } protected ReactiveProcessor processFlowFunction() { return stream -> from(stream) .transform(processingStrategy.onPipeline(pipeline)) .doOnNext(response -> response.getContext().success(response)) .doOnError(UNEXPECTED_EXCEPTION_PREDICATE, throwable -> LOGGER.error("Unhandled exception in async processing " + throwable)); } protected void configureMessageProcessors(MessageProcessorChainBuilder builder) throws MuleException { for (Object processor : getMessageProcessors()) { if (processor instanceof Processor) { builder.chain((Processor) processor); } else if (processor instanceof MessageProcessorBuilder) { builder.chain((MessageProcessorBuilder) processor); } else { throw new IllegalArgumentException( "MessageProcessorBuilder should only have MessageProcessor's or MessageProcessorBuilder's configured"); } } } protected boolean isRedeliveryPolicyConfigured() { if (getMessageProcessors().isEmpty()) { return false; } return getMessageProcessors().get(0) instanceof IdempotentRedeliveryPolicy; } @Override protected void doStart() throws MuleException { super.doStart(); startIfStartable(processingStrategy); sink = processingStrategy.createSink(this, processFlowFunction()); startIfStartable(pipeline); canProcessMessage = true; if (muleContext.isStarted()) { try { startIfStartable(messageSource); } catch (ConnectException ce) { // Let connection exceptions bubble up to trigger the reconnection strategy. throw ce; } catch (MuleException e) { // If the messageSource couldn't be started we would need to stop the pipeline (if possible) in order to leave // its LifecycleManager also as initialise phase so the flow can be disposed later doStop(); throw e; } } } public Consumer<Event> assertStarted() { return event -> { if (!canProcessMessage) { throw propagate(new MessagingException(event, new LifecycleException(CoreMessages.isStopped(getName()), event.getMessage()))); } }; } @Override protected void doStop() throws MuleException { try { stopIfStoppable(messageSource); } finally { canProcessMessage = false; } disposeIfDisposable(sink); sink = null; stopIfStoppable(processingStrategy); stopIfStoppable(pipeline); super.doStop(); } @Override protected void doDispose() { disposeIfDisposable(pipeline); disposeIfDisposable(messageSource); super.doDispose(); } /** * Determines is blocking synchronous code path should be used. This is used in the following cases: * <ol> * <li>If a transaction is active and a processing strategy supporting transactions is configured. (synchronous or default * strategies)</li> * </ol> * * @return true if blocking synchronous code path should be used, false otherwise. */ protected boolean useBlockingCodePath() { return isTransactionActive() && (processingStrategy.isSynchronous() || processingStrategyFactory instanceof DefaultFlowProcessingStrategyFactory); } @Override public Map<String, EventContext> getSerializationEventContextCache() { return eventContextCache.asMap(); } private class ProcessEndProcessor extends AbstractAnnotatedObject implements Processor, InternalMessageProcessor { @Override public Event process(Event event) throws MuleException { muleContext.getNotificationManager() .fireNotification(new PipelineMessageNotification(createInfo(event, null, AbstractPipeline.this), AbstractPipeline.this, PROCESS_END)); return event; } } private class ProcessorStartCompleteProcessor implements Processor, InternalMessageProcessor { @Override public Event process(Event event) throws MuleException { muleContext.getNotificationManager() .fireNotification(new PipelineMessageNotification(createInfo(event, null, AbstractPipeline.this), AbstractPipeline.this, PROCESS_START)); // Fire COMPLETE notification on async response Mono.from(event.getContext().getBeforeResponsePublisher()) .doOnNext(result -> fireCompleteNotification(result, null)) .doOnError(MessagingException.class, messagingException -> fireCompleteNotification(null, messagingException)) .doOnError(UNEXPECTED_EXCEPTION_PREDICATE, throwable -> fireCompleteNotification(null, new MessagingException(event, throwable, this instanceof Processor ? this : null)) ).subscribe(requestUnbounded()); return event; } private void fireCompleteNotification(Event event, MessagingException messagingException) { muleContext.getNotificationManager() .fireNotification(new PipelineMessageNotification(createInfo(event, messagingException, AbstractPipeline.this), AbstractPipeline.this, PROCESS_COMPLETE)); } } }