/* * 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.source; import static java.lang.String.format; import static java.util.Optional.empty; import static java.util.Optional.ofNullable; import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded; import static org.mule.runtime.extension.api.ExtensionConstants.TRANSACTIONAL_ACTION_PARAMETER_NAME; import static org.mule.runtime.module.extension.internal.runtime.resolver.ValueResolvingContext.from; import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getSourceName; import static org.mule.runtime.module.extension.internal.util.MuleExtensionUtils.getInitialiserEvent; import static org.reflections.ReflectionUtils.getAllFields; import static org.reflections.ReflectionUtils.withAnnotation; import org.mule.runtime.api.connection.ConnectionException; import org.mule.runtime.api.connection.ConnectionHandler; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.api.lifecycle.Initialisable; import org.mule.runtime.api.lifecycle.InitialisationException; import org.mule.runtime.api.lifecycle.Startable; import org.mule.runtime.api.lifecycle.Stoppable; import org.mule.runtime.api.meta.model.ExtensionModel; import org.mule.runtime.api.meta.model.source.SourceModel; 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.ConnectionManager; import org.mule.runtime.core.api.construct.FlowConstruct; import org.mule.runtime.core.api.construct.FlowConstructAware; import org.mule.runtime.core.exception.MessagingException; import org.mule.runtime.core.execution.ExceptionCallback; import org.mule.runtime.core.util.func.CheckedRunnable; import org.mule.runtime.extension.api.annotation.param.Config; import org.mule.runtime.extension.api.annotation.param.Connection; import org.mule.runtime.extension.api.exception.IllegalModelDefinitionException; import org.mule.runtime.extension.api.runtime.ConfigurationInstance; import org.mule.runtime.extension.api.runtime.source.Source; import org.mule.runtime.extension.api.runtime.source.SourceCallback; import org.mule.runtime.extension.api.runtime.source.SourceCallbackContext; import org.mule.runtime.extension.api.tx.SourceTransactionalAction; import org.mule.runtime.extension.internal.property.TransactionalActionModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.DeclaringMemberModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.SourceCallbackModelProperty; import org.mule.runtime.module.extension.internal.runtime.resolver.ResolverSet; import org.mule.runtime.module.extension.internal.runtime.resolver.ResolverSetResult; import org.mule.runtime.module.extension.internal.runtime.resolver.ValueResolver; import org.mule.runtime.module.extension.internal.util.FieldSetter; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.inject.Inject; import org.apache.commons.collections.CollectionUtils; /** * An adapter for {@link Source} which acts as a bridge with {@link ExtensionMessageSource}. It also propagates lifecycle and * performs injection of both, dependencies and parameters * * @since 4.0 */ public final class SourceAdapter implements Startable, Stoppable, Initialisable, FlowConstructAware { private final ExtensionModel extensionModel; private final SourceModel sourceModel; private final Source source; private final Optional<ConfigurationInstance> configurationInstance; private final Optional<FieldSetter<Object, Object>> configurationSetter; private final Optional<FieldSetter<Object, Object>> connectionSetter; private final SourceCallbackFactory sourceCallbackFactory; private final ResolverSet nonCallbackParameters; private final ResolverSet successCallbackParameters; private final ResolverSet errorCallbackParameters; private ConnectionHandler<Object> connectionHandler; private FlowConstruct flowConstruct; @Inject private ConnectionManager connectionManager; @Inject private MuleContext muleContext; public SourceAdapter(ExtensionModel extensionModel, SourceModel sourceModel, Source source, Optional<ConfigurationInstance> configurationInstance, SourceCallbackFactory sourceCallbackFactory, ResolverSet nonCallbackParameters, ResolverSet successCallbackParameters, ResolverSet errorCallbackParameters) { this.extensionModel = extensionModel; this.sourceModel = sourceModel; this.source = source; this.configurationInstance = configurationInstance; this.sourceCallbackFactory = sourceCallbackFactory; this.nonCallbackParameters = nonCallbackParameters; this.successCallbackParameters = successCallbackParameters; this.errorCallbackParameters = errorCallbackParameters; this.configurationSetter = fetchField(Config.class); this.connectionSetter = fetchField(Connection.class); } private SourceCallback createSourceCallback() { return sourceCallbackFactory.createSourceCallback(createCompletionHandlerFactory()); } private SourceCompletionHandlerFactory createCompletionHandlerFactory() { return sourceModel.getModelProperty(SourceCallbackModelProperty.class) .map(this::doCreateCompletionHandler) .orElse(context -> new NullSourceCompletionHandler()); } private SourceCompletionHandlerFactory doCreateCompletionHandler(SourceCallbackModelProperty modelProperty) { final SourceCallbackExecutor onSuccessExecutor = getMethodExecutor(modelProperty.getOnSuccessMethod(), modelProperty); final SourceCallbackExecutor onErrorExecutor = getMethodExecutor(modelProperty.getOnErrorMethod(), modelProperty); return context -> new DefaultSourceCompletionHandler(onSuccessExecutor, onErrorExecutor, context); } private SourceCallbackExecutor getMethodExecutor(Optional<Method> method, SourceCallbackModelProperty sourceCallbackModel) { return method.map(m -> (SourceCallbackExecutor) new ReflectiveSourceCallbackExecutor(extensionModel, configurationInstance, sourceModel, source, m, muleContext, sourceCallbackModel)) .orElse(new NullSourceCallbackExecutor()); } @Override public void initialise() throws InitialisationException { initialiseIfNeeded(this.nonCallbackParameters, true, muleContext); initialiseIfNeeded(this.errorCallbackParameters, true, muleContext); initialiseIfNeeded(this.successCallbackParameters, true, muleContext); } public class DefaultSourceCompletionHandler implements SourceCompletionHandler { private final SourceCallbackExecutor onSuccessExecutor; private final SourceCallbackExecutor onErrorExecutor; private final SourceCallbackContext context; public DefaultSourceCompletionHandler(SourceCallbackExecutor onSuccessExecutor, SourceCallbackExecutor onErrorExecutor, SourceCallbackContext context) { this.onSuccessExecutor = onSuccessExecutor; this.onErrorExecutor = onErrorExecutor; this.context = context; } @Override public void onCompletion(Event event, Map<String, Object> parameters, ExceptionCallback<Throwable> exceptionCallback) { safely(() -> onSuccessExecutor.execute(event, parameters, context), exceptionCallback); } @Override public void onFailure(MessagingException exception, Map<String, Object> parameters) { safely(() -> onErrorExecutor.execute(exception.getEvent(), parameters, context), callbackException -> { throw new MuleRuntimeException(createStaticMessage(format("Found exception trying to handle error from source '%s'", sourceModel.getName())), callbackException); }); } private void safely(CheckedRunnable task, ExceptionCallback exceptionCallback) { try { task.run(); } catch (Throwable e) { exceptionCallback.onException(e); } } public Map<String, Object> createResponseParameters(Event event) { try { ResolverSetResult parameters = SourceAdapter.this.successCallbackParameters.resolve(from(event, configurationInstance)); return parameters.asMap(); } catch (MuleException e) { throw new MuleRuntimeException(e); } } public Map<String, Object> createFailureResponseParameters(Event event) { try { ResolverSetResult parameters = SourceAdapter.this.errorCallbackParameters.resolve(from(event, configurationInstance)); return parameters.asMap(); } catch (MuleException e) { throw new MuleRuntimeException(e); } } } @Override public void start() throws MuleException { if (source instanceof FlowConstructAware) { ((FlowConstructAware) source).setFlowConstruct(flowConstruct); } try { setConfiguration(configurationInstance); setConnection(); muleContext.getInjector().inject(source); source.onStart(createSourceCallback()); } catch (Exception e) { throw new DefaultMuleException(e); } } @Override public void stop() throws MuleException { try { source.onStop(); } catch (Exception e) { throw new DefaultMuleException(e); } finally { releaseConnection(); } } private void setConfiguration(Optional<ConfigurationInstance> configuration) { if (configurationSetter.isPresent() && configuration.isPresent()) { configurationSetter.get().set(source, configuration.get().getValue()); } } private void setConnection() { if (connectionSetter.isPresent() && configurationInstance.isPresent()) { try { connectionHandler = connectionManager.getConnection(configurationInstance.get().getValue()); connectionSetter.get().set(source, connectionHandler.getConnection()); } catch (ConnectionException e) { throw new MuleRuntimeException(createStaticMessage(format( "Could not obtain connection for message source '%s' on flow '%s'", getName(), flowConstruct.getName())), e); } } } Optional<ConfigurationInstance> getConfigurationInstance() { return configurationInstance; } Optional<ConnectionHandler> getConnectionHandler() { return ofNullable(connectionHandler); } private void releaseConnection() { if (connectionHandler != null) { try { connectionHandler.release(); } finally { connectionHandler = null; } } } private <T> Optional<FieldSetter<Object, T>> fetchField(Class<? extends Annotation> annotation) { Set<Field> fields = getAllFields(source.getClass(), withAnnotation(annotation)); if (CollectionUtils.isEmpty(fields)) { return empty(); } if (fields.size() > 1) { // TODO: MULE-9220 Move this to a syntax validator throw new IllegalModelDefinitionException( format("Message Source defined on class '%s' has more than one field annotated with '@%s'. " + "Only one field in the class can bare such annotation", source.getClass().getName(), annotation.getSimpleName())); } return Optional.of(new FieldSetter<>(fields.iterator().next())); } public String getName() { return getSourceName(source.getClass()); } public Source getDelegate() { return source; } public SourceTransactionalAction getTransactionalAction() { ValueResolver valueResolver = nonCallbackParameters.getResolvers().get(getTransactionalActionFieldName()); Object transactionalAction; try { transactionalAction = valueResolver.resolve(from(getInitialiserEvent(muleContext))); } catch (MuleException e) { throw new MuleRuntimeException(createStaticMessage("Unable to get the Transactional Action value for Message Source"), e); } if (!(transactionalAction instanceof SourceTransactionalAction)) { throw new IllegalStateException("The resolved value is not a Transactional Action"); } return (SourceTransactionalAction) transactionalAction; } private String getTransactionalActionFieldName() { return sourceModel.getAllParameterModels() .stream() .filter(param -> param.getModelProperty(TransactionalActionModelProperty.class).isPresent()) .filter(param -> param.getModelProperty(DeclaringMemberModelProperty.class).isPresent()) .map(param -> param.getModelProperty(DeclaringMemberModelProperty.class).get()) .findAny() .map(modelProperty -> modelProperty.getDeclaringField().getName()).orElse(TRANSACTIONAL_ACTION_PARAMETER_NAME); } @Override public void setFlowConstruct(FlowConstruct flowConstruct) { this.flowConstruct = flowConstruct; } }