/* * 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.interceptor; import static java.lang.String.valueOf; import static java.util.Optional.empty; import static java.util.Optional.of; import static org.mule.runtime.core.component.ComponentAnnotations.ANNOTATION_PARAMETERS; import static reactor.core.publisher.Mono.from; import static reactor.core.publisher.Mono.fromFuture; import org.mule.runtime.api.component.location.ComponentLocation; import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.api.interception.InterceptionAction; import org.mule.runtime.api.interception.InterceptionEvent; import org.mule.runtime.api.interception.ProcessorInterceptor; import org.mule.runtime.api.interception.ProcessorInterceptorFactory; import org.mule.runtime.api.meta.AnnotatedObject; import org.mule.runtime.core.api.Event; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.construct.FlowConstruct; import org.mule.runtime.core.api.construct.FlowConstructAware; import org.mule.runtime.core.api.interception.DefaultInterceptionEvent; import org.mule.runtime.core.api.processor.ParametersResolverProcessor; import org.mule.runtime.core.api.processor.Processor; import org.mule.runtime.core.api.processor.ReactiveProcessor; import org.mule.runtime.core.exception.MessagingException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.function.BiFunction; import java.util.function.Function; /** * Hooks the {@link ProcessorInterceptor}s for a {@link Processor} into the {@code Reactor} pipeline. * * @since 4.0 */ public class ReactiveInterceptorAdapter implements BiFunction<Processor, ReactiveProcessor, ReactiveProcessor>, FlowConstructAware { private static final String AROUND_METHOD_NAME = "around"; private ProcessorInterceptorFactory interceptorFactory; private FlowConstruct flowConstruct; public ReactiveInterceptorAdapter(ProcessorInterceptorFactory interceptorFactory) { this.interceptorFactory = interceptorFactory; } @Override public void setFlowConstruct(FlowConstruct flowConstruct) { this.flowConstruct = flowConstruct; } @Override public ReactiveProcessor apply(Processor component, ReactiveProcessor next) { if (!isInterceptable(component) || !interceptorFactory.intercept(((AnnotatedObject) component).getLocation())) { return next; } final ProcessorInterceptor interceptor = interceptorFactory.get(); Map<String, String> dslParameters = (Map<String, String>) ((AnnotatedObject) component).getAnnotation(ANNOTATION_PARAMETERS); if (implementsAround(interceptor)) { return publisher -> from(publisher) .map(doBefore(interceptor, component, dslParameters)) .flatMap(event -> fromFuture(doAround(event, interceptor, component, dslParameters, next)) .onErrorMap(CompletionException.class, completionException -> completionException.getCause())) .doOnError(MessagingException.class, error -> { interceptor.after(new DefaultInterceptionEvent(error.getEvent()), of(error.getCause())); }) .map(doAfter(interceptor)); } else { return publisher -> from(publisher) .map(doBefore(interceptor, component, dslParameters)) .transform(next) .doOnError(MessagingException.class, error -> interceptor.after(new DefaultInterceptionEvent(error.getEvent()), of(error.getCause()))) .map(doAfter(interceptor)); } } private boolean implementsAround(ProcessorInterceptor interceptor) { try { return !interceptor.getClass().getMethod(AROUND_METHOD_NAME, Map.class, InterceptionEvent.class, InterceptionAction.class) .isDefault(); } catch (NoSuchMethodException | SecurityException e) { throw new MuleRuntimeException(e); } } private Function<Event, Event> doBefore(ProcessorInterceptor interceptor, Processor component, Map<String, String> dslParameters) { return event -> { DefaultInterceptionEvent interceptionEvent = new DefaultInterceptionEvent(event); interceptor.before(resolveParameters(event, component, dslParameters), interceptionEvent); return interceptionEvent.resolve(); }; } private CompletableFuture<Event> doAround(Event event, ProcessorInterceptor interceptor, Processor component, Map<String, String> dslParameters, ReactiveProcessor next) { DefaultInterceptionEvent interceptionEvent = new DefaultInterceptionEvent(event); final ReactiveInterceptionAction reactiveInterceptionAction = new ReactiveInterceptionAction(interceptionEvent, next); return interceptor.around(resolveParameters(event, component, dslParameters), interceptionEvent, reactiveInterceptionAction) .exceptionally(t -> { throw new CompletionException(new MessagingException(event, t.getCause(), component)); }) .thenApply(interceptedEvent -> ((DefaultInterceptionEvent) interceptedEvent).resolve()); } private Function<Event, Event> doAfter(ProcessorInterceptor interceptor) { return event -> { DefaultInterceptionEvent interceptionEvent = new DefaultInterceptionEvent(event); interceptor.after(interceptionEvent, empty()); return interceptionEvent.resolve(); }; } private boolean isInterceptable(Processor component) { if (component instanceof AnnotatedObject) { ComponentLocation componentLocation = ((AnnotatedObject) component).getLocation(); if (componentLocation != null) { return true; } } return false; } private Map<String, Object> resolveParameters(Event event, Processor processor, Map<String, String> parameters) { Map<String, Object> resolvedParameters = new HashMap<>(); for (Map.Entry<String, String> entry : parameters.entrySet()) { Object value; String paramValue = entry.getValue(); final MuleContext muleContext = flowConstruct.getMuleContext(); if (muleContext.getExpressionManager().isExpression(paramValue)) { value = muleContext.getExpressionManager().evaluate(paramValue, event, flowConstruct).getValue(); } else { value = valueOf(paramValue); } resolvedParameters.put(entry.getKey(), value); } // TODO MULE-11527 avoid doing unnecesary evaluations if (processor instanceof ParametersResolverProcessor) { resolvedParameters.putAll(((ParametersResolverProcessor) processor).resolveParameters(event)); } return resolvedParameters; } }