/*
* 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 java.util.Optional.empty;
import static java.util.Optional.of;
import static org.mule.runtime.core.internal.message.InternalMessage.builder;
import static org.mule.runtime.extension.api.ExtensionConstants.TARGET_PARAMETER_NAME;
import static reactor.core.publisher.Flux.from;
import static reactor.core.publisher.Flux.just;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.el.ExpressionManager;
import org.mule.runtime.core.api.processor.MessageProcessorChain;
import org.mule.runtime.core.api.processor.Processor;
import org.reactivestreams.Publisher;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Creates a chain for any operation, where it parametrizes two type of values (parameter and property) to the inner processors
* through the {@link Event}.
*
* <p>
* Both parameter and property could be simple literals or expressions that will be evaluated before passing the new {@link Event}
* to the child processors.
*
* <p>
* Taking the following sample where the current event passed to {@link ModuleOperationProcessorChain#doProcess(Event)} has a flow
* variable under "person" with a value of "stranger!", the result of executing the above processor will be "howdy stranger!":
*
* <pre>
* <module-operation-chain returnsVoid="false">
* <module-operation-properties/>
* <module-operation-parameters>
* <module-operation-parameter-entry value="howdy" key="value1"/>
* <module-operation-parameter-entry value="#[mel:flowVars.person]" key="value2"/>
* </module-operation-parameters>
* <set-payload value="#[param.value1 ++ param.value2]"/>
* </module-operation-chain>
* </pre>
*/
public class ModuleOperationMessageProcessorChainBuilder extends ExplicitMessageProcessorChainBuilder {
private Map<String, String> properties;
private Map<String, String> parameters;
private boolean returnsVoid;
private ExpressionManager expressionManager;
public ModuleOperationMessageProcessorChainBuilder(Map<String, String> properties,
Map<String, String> parameters, boolean returnsVoid,
ExpressionManager expressionManager) {
this.properties = properties;
this.parameters = parameters;
this.returnsVoid = returnsVoid;
this.expressionManager = expressionManager;
}
@Override
protected MessageProcessorChain createInterceptingChain(Processor head, List<Processor> processors,
List<Processor> processorForLifecycle) {
return new ModuleOperationProcessorChain("wrapping-operation-module-chain", head, processors, processorForLifecycle,
properties, parameters, returnsVoid,
expressionManager);
}
/**
* Generates message processor for a specific set of parameters & properties to be added in a new event.
*/
static class ModuleOperationProcessorChain extends ExplicitMessageProcessorChain
implements Processor {
private Map<String, String> properties;
private Map<String, String> parameters;
private boolean returnsVoid;
private ExpressionManager expressionManager;
private Optional<String> target;
ModuleOperationProcessorChain(String name, Processor head, List<Processor> processors,
List<Processor> processorsForLifecycle,
Map<String, String> properties, Map<String, String> parameters,
boolean returnsVoid,
ExpressionManager expressionManager) {
super(name, head, processors, processorsForLifecycle);
this.properties = properties;
this.target = parameters.containsKey(TARGET_PARAMETER_NAME) ? of(parameters.remove(TARGET_PARAMETER_NAME)) : empty();
this.parameters = parameters;
this.returnsVoid = returnsVoid;
this.expressionManager = expressionManager;
}
/**
* Given an {@code event}, it will consume from it ONLY the defined properties and parameters that were set when initializing
* this class to provide scoping for the inner list of processors.
*
* @param event parameter to consume elements from
* @return a modified {@link Event} if the output of the operation was not void, the same event otherwise.
* @throws MuleException
*/
@Override
protected Event doProcess(Event event) throws MuleException {
Event eventResponse = super.doProcess(createEventWithParameters(event));
return processResult(event, eventResponse);
}
@Override
public Publisher<Event> apply(Publisher<Event> publisher) {
return from(publisher)
.concatMap(request -> just(request)
.map(this::createEventWithParameters)
.transform(super::apply)
.map(eventResult -> processResult(request, eventResult)));
}
private Event processResult(Event originalEvent, Event chainEvent) {
if (!returnsVoid) {
originalEvent = createNewEventFromJustMessage(originalEvent, chainEvent);
}
return originalEvent;
}
private Event createNewEventFromJustMessage(Event request, Event response) {
final Event.Builder builder = Event.builder(request);
if (target.isPresent()) {
builder.addVariable(target.get(), response.getMessage());
} else {
builder.message(builder(response.getMessage()).build());
}
return builder.build();
}
private Event createEventWithParameters(Event event) {
Event.Builder builder = Event.builder(event.getContext());
builder.message(builder().nullPayload().build());
builder.parameters(evaluateParameters(event, parameters));
builder.properties(evaluateParameters(event, properties));
return builder.build();
}
private Map<String, Object> evaluateParameters(Event event, Map<String, String> unevaluatedMap) {
return unevaluatedMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
entry -> getEvaluatedValue(event, entry.getValue())));
}
private Object getEvaluatedValue(Event event, String value) {
return expressionManager.isExpression(value)
? expressionManager.evaluate(value, event, flowConstruct).getValue()
: value;
}
}
}