/*
* 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.module.extension.internal.runtime.client;
import static java.lang.String.format;
import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage;
import static org.mule.runtime.module.extension.internal.runtime.resolver.ValueResolvingContext.from;
import static org.mule.runtime.module.extension.internal.util.MuleExtensionUtils.getInitialiserEvent;
import static reactor.core.publisher.Mono.from;
import static reactor.core.publisher.Mono.just;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.exception.MuleRuntimeException;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.meta.model.ExtensionModel;
import org.mule.runtime.api.meta.model.operation.OperationModel;
import org.mule.runtime.api.meta.model.util.ExtensionWalker;
import org.mule.runtime.api.meta.model.util.IdempotentExtensionWalker;
import org.mule.runtime.api.util.Reference;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.extension.ExtensionManager;
import org.mule.runtime.core.api.lifecycle.LifecycleUtils;
import org.mule.runtime.core.api.util.Pair;
import org.mule.runtime.core.policy.PolicyManager;
import org.mule.runtime.core.util.TemplateParser;
import org.mule.runtime.extension.api.client.ExtensionsClient;
import org.mule.runtime.extension.api.client.OperationParameters;
import org.mule.runtime.extension.api.runtime.ConfigurationProvider;
import org.mule.runtime.extension.api.runtime.operation.Result;
import org.mule.runtime.extension.internal.client.ComplexParameter;
import org.mule.runtime.module.extension.internal.runtime.objectbuilder.DefaultObjectBuilder;
import org.mule.runtime.module.extension.internal.runtime.operation.OperationMessageProcessor;
import org.mule.runtime.module.extension.internal.runtime.operation.OperationMessageProcessorBuilder;
import org.mule.runtime.module.extension.internal.runtime.resolver.ExpressionValueResolver;
import org.mule.runtime.module.extension.internal.runtime.resolver.StaticValueResolver;
import org.mule.runtime.module.extension.internal.runtime.resolver.ValueResolver;
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
/**
* This is the default implementation for a {@link ExtensionsClient}, it uses the {@link ExtensionManager} in the
* {@link MuleContext} to search for the extension that wants to execute the operation from.
* <p>
* The concrete execution of the operation is handled by an {@link OperationMessageProcessor} instance.
* <p>
* This implementation can only execute extensions that were built using the SDK, Smart Connectors operations can't be executed.
*
* @since 4.0
*/
public final class DefaultExtensionsClient implements ExtensionsClient {
@Inject
private MuleContext muleContext;
@Inject
private PolicyManager policyManager;
@Inject
private ExtensionManager extensionManager;
private final Map<Pair<String, String>, OperationModel> operations = new LinkedHashMap<>();
private final TemplateParser parser = TemplateParser.createMuleStyleParser();
/**
* {@inheritDoc}
*/
@Override
public <T, A> CompletableFuture<Result<T, A>> executeAsync(String extension,
String operation,
OperationParameters parameters) {
OperationMessageProcessor processor = createProcessor(extension, operation, parameters);
Mono<Result<T, A>> resultMono = from(processor.apply(just(getInitialiserEvent(muleContext))))
.map(event -> Result.<T, A>builder(event.getMessage()).build())
.mapError(Exceptions::unwrap)
.doAfterTerminate((r, t) -> disposeProcessor(processor));
return resultMono.toFuture();
}
/**
* {@inheritDoc}
*/
@Override
public <T, A> Result<T, A> execute(String extension, String operation, OperationParameters params)
throws MuleException {
OperationMessageProcessor processor = createProcessor(extension, operation, params);
try {
Event process = processor.process(getInitialiserEvent(muleContext));
return Result.<T, A>builder(process.getMessage()).build();
} finally {
disposeProcessor(processor);
}
}
/**
* Creates a new {@link OperationMessageProcessor} for the required operation and parses all the parameters passed by the client
* user.
*/
private OperationMessageProcessor createProcessor(String extensionName, String operationName, OperationParameters parameters) {
ExtensionModel extension = findExtension(extensionName);
OperationModel operation = findOperation(extension, operationName);
ConfigurationProvider config = parameters.getConfigName().map(this::findConfiguration).orElse(null);
Map<String, ValueResolver> resolvedParams = resolveParameters(parameters.get(), getInitialiserEvent(muleContext));
try {
OperationMessageProcessor processor = new OperationMessageProcessorBuilder(extension, operation, policyManager, muleContext)
.setConfigurationProvider(config)
.setParameters(resolvedParams)
.build();
processor.initialise();
processor.start();
return processor;
} catch (Exception e) {
throw new MuleRuntimeException(createStaticMessage("Could not create Operation Message Processor"), e);
}
}
private Map<String, ValueResolver> resolveParameters(Map<String, Object> parameters, Event event) {
LinkedHashMap<String, ValueResolver> values = new LinkedHashMap<>();
parameters.forEach((name, value) -> {
if (value instanceof ComplexParameter) {
ComplexParameter complex = (ComplexParameter) value;
DefaultObjectBuilder<?> builder = new DefaultObjectBuilder<>(complex.getType());
resolveParameters(complex.getParameters(), event).forEach((propertyName, valueResolver) -> {
try {
LifecycleUtils.initialiseIfNeeded(valueResolver, true, muleContext);
builder.addPropertyResolver(propertyName, valueResolver);
} catch (InitialisationException e) {
throw new MuleRuntimeException(e);
}
});
try {
values.put(name, new StaticValueResolver<>(builder.build(from(event))));
} catch (MuleException e) {
throw new MuleRuntimeException(createStaticMessage(format("Could not construct parameter [%s]", name)), e);
}
} else {
if (value instanceof String && parser.isContainsTemplate((String) value)) {
values.put(name, new ExpressionValueResolver((String) value));
} else {
values.put(name, new StaticValueResolver<>(value));
}
}
});
return values;
}
private OperationModel findOperation(ExtensionModel extensionModel, String operationName) {
return operations.computeIfAbsent(new Pair<>(extensionModel.getName(), operationName), ope -> {
Reference<OperationModel> operation = new Reference<>();
ExtensionWalker walker = new IdempotentExtensionWalker() {
@Override
protected void onOperation(OperationModel operationModel) {
if (operationName.equals(operationModel.getName())) {
operation.set(operationModel);
stop();
}
}
};
walker.walk(extensionModel);
if (operation.get() == null) {
throw new MuleRuntimeException(createStaticMessage("No Operation [" + operationName + "] Found"));
}
return operation.get();
});
}
private ConfigurationProvider findConfiguration(String configName) {
return extensionManager.getConfigurationProvider(configName)
.orElseThrow(() -> new MuleRuntimeException(createStaticMessage("No configuration [" + configName + "] found")));
}
private ExtensionModel findExtension(String extensionName) {
return extensionManager.getExtension(extensionName)
.orElseThrow(() -> new MuleRuntimeException(createStaticMessage("No Extension [" + extensionName + "] Found")));
}
private void disposeProcessor(OperationMessageProcessor processor) {
try {
processor.stop();
processor.dispose();
} catch (MuleException e) {
throw new MuleRuntimeException(createStaticMessage("Error while disposing the executing operation"), e);
}
}
}