/* * 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.config; import static java.lang.Boolean.valueOf; import static java.lang.String.format; import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage; import static org.mule.runtime.api.util.Preconditions.checkState; import static org.mule.runtime.core.api.config.ConfigurationInstanceNotification.CONFIGURATION_STOPPED; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.disposeIfNeeded; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.startIfNeeded; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.stopIfNeeded; import static org.slf4j.LoggerFactory.getLogger; import org.mule.runtime.api.connection.ConnectionException; import org.mule.runtime.api.connection.ConnectionProvider; import org.mule.runtime.api.connection.ConnectionValidationResult; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.lifecycle.InitialisationException; import org.mule.runtime.api.lock.LockFactory; import org.mule.runtime.api.meta.model.config.ConfigurationModel; import org.mule.runtime.api.scheduler.Scheduler; import org.mule.runtime.core.api.DefaultMuleException; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.config.ConfigurationInstanceNotification; import org.mule.runtime.core.api.connector.ConnectionManager; import org.mule.runtime.core.api.retry.RetryCallback; import org.mule.runtime.core.api.retry.RetryContext; import org.mule.runtime.core.api.retry.RetryPolicyTemplate; import org.mule.runtime.core.api.scheduler.SchedulerService; import org.mule.runtime.core.api.time.TimeSupplier; import org.mule.runtime.core.internal.connection.ConnectionManagerAdapter; import org.mule.runtime.extension.api.runtime.ConfigurationInstance; import org.mule.runtime.extension.api.runtime.ConfigurationStats; import org.mule.runtime.extension.api.runtime.Interceptable; import org.mule.runtime.extension.api.runtime.operation.Interceptor; import org.mule.runtime.module.extension.internal.loader.AbstractInterceptable; import org.mule.runtime.module.extension.internal.runtime.connectivity.NoConnectivityTest; import java.util.List; import java.util.Optional; import java.util.concurrent.locks.Lock; import javax.inject.Inject; import org.slf4j.Logger; /** * Implementation of {@link ConfigurationInstance} which propagates dependency injection and lifecycle phases into the contained * configuration {@link #value} and {@link #connectionProvider} (if present). * <p> * It also implements the {@link Interceptable} interface which means that it contains a list of {@link Interceptor interceptors}, * on which IoC and lifecycle is propagated as well. * <p> * In the case of the {@link #connectionProvider} being present, then it also binds the {@link #value} to the * {@link ConnectionProvider} by the means of {@link ConnectionManager#bind(Object, ConnectionProvider)} when the * {@link #initialise()} phase is executed. That bound will be broken on the {@link #stop()} phase by using * {@link ConnectionManager#unbind(Object)} * * @since 4.0 */ public final class LifecycleAwareConfigurationInstance extends AbstractInterceptable implements ConfigurationInstance { private static final Logger LOGGER = getLogger(LifecycleAwareConfigurationInstance.class); private static final String DO_TEST_CONNECTIVITY_PROPERTY_NAME = "doTestConnectivity"; private final String name; private final ConfigurationModel model; private final Object value; private final Optional<ConnectionProvider> connectionProvider; private ConfigurationStats configurationStats; @Inject private TimeSupplier timeSupplier; @Inject private MuleContext muleContext; @Inject private LockFactory lockFactory; @Inject private SchedulerService schedulerService; @Inject private ConnectionManagerAdapter connectionManager; private Lock testConnectivityLock; private Scheduler retryScheduler; private volatile boolean initialized = false; private volatile boolean started = false; private boolean doTestConnectivity = getDoTestConnectivityProperty(); /** * Creates a new instance * * @param name this configuration's name * @param model the {@link ConfigurationModel} for this instance * @param value the actual configuration instance * @param interceptors the {@link List} of {@link Interceptor interceptors} that applies * @param connectionProvider an {@link Optional} containing the {@link ConnectionProvider} to use */ public LifecycleAwareConfigurationInstance(String name, ConfigurationModel model, Object value, List<Interceptor> interceptors, Optional<ConnectionProvider> connectionProvider) { super(interceptors); this.name = name; this.model = model; this.value = value; this.connectionProvider = connectionProvider; } /** * Initialises this instance by * <ul> * <li>Initialising the {@link #configurationStats}</li> * <li>Performs dependency injection on the {@link #value} and each item in {@link #getInterceptors()}</li> * <li>Propagates this lifecycle phase into the the {@link #value} and each item in {@link #getInterceptors()}</li> * </ul> * * @throws InitialisationException if an exception is found */ @Override public synchronized void initialise() throws InitialisationException { if (!initialized) { initialized = true; try { initStats(); doInitialise(); super.initialise(); } catch (Exception e) { if (e instanceof InitialisationException) { throw (InitialisationException) e; } else { throw new InitialisationException(e, this); } } } } /** * Propagates this lifecycle phase into the the {@link #value} and each item in {@link #getInterceptors()} * * @throws MuleException if an exception is found */ @Override public synchronized void start() throws MuleException { if (!started) { started = true; testConnectivityLock = lockFactory.createLock(this.getClass().getName() + "-testConnectivity-" + getName()); if (connectionProvider.isPresent()) { startIfNeeded(connectionProvider); if (!connectionManager.hasBinding(value)) { connectionManager.bind(value, connectionProvider.get()); } if (doTestConnectivity) { testConnectivity(); } } startIfNeeded(value); super.start(); } } private void testConnectivity() throws MuleException { ConnectionProvider provider = connectionProvider.get(); if (provider instanceof NoConnectivityTest) { return; } RetryPolicyTemplate retryTemplate = connectionManager.getRetryTemplateFor(provider); RetryCallback retryCallback = new RetryCallback() { @Override public void doWork(RetryContext context) throws Exception { final boolean lockAcquired = testConnectivityLock.tryLock(); if (lockAcquired) { LOGGER.info("Doing testConnectivity() for config " + getName()); try { ConnectionValidationResult result = connectionManager.testConnectivity(LifecycleAwareConfigurationInstance.this); if (result.isValid()) { context.setOk(); } else { context.setFailed(result.getException()); throw new ConnectionException(format("Connectivity test failed for config '%s'", getName()), result.getException()); } } finally { testConnectivityLock.unlock(); } } else { LOGGER.warn("There is a testConnectivity() already running for config " + getName()); } } @Override public String getWorkDescription() { return format("Testing connectivity for config '%s'", getName()); } @Override public Object getWorkOwner() { return value; } }; try { retryTemplate.execute(retryCallback, retryScheduler); } catch (Exception e) { throw new DefaultMuleException(createStaticMessage(format("Could not perform connectivity testing for config '%s'", getName())), e); } } /** * Propagates this lifecycle phase into the the {@link #value} and each item in {@link #getInterceptors()}. Also triggers a * {@link ConfigurationInstanceNotification} that is being stopped. * * @throws MuleException if an exception is found */ @Override public synchronized void stop() throws MuleException { if (started) { started = false; try { stopIfNeeded(value); if (connectionProvider.isPresent()) { testConnectivityLock.lock(); try { connectionManager.unbind(value); stopIfNeeded(connectionProvider); } finally { testConnectivityLock.unlock(); } } super.stop(); } finally { muleContext.fireNotification(new ConfigurationInstanceNotification(this, CONFIGURATION_STOPPED)); } } } /** * Propagates this lifecycle phase into the the {@link #value} and each item in {@link #getInterceptors()} */ @Override public synchronized void dispose() { if (initialized) { initialized = false; if (retryScheduler != null) { retryScheduler.stop(); } disposeIfNeeded(value, LOGGER); disposeIfNeeded(connectionProvider, LOGGER); configurationStats = null; testConnectivityLock = null; super.dispose(); } } private void doInitialise() throws InitialisationException { if (connectionProvider.isPresent()) { initialiseIfNeeded(connectionProvider, true, muleContext); connectionManager.bind(value, connectionProvider.get()); } initialiseIfNeeded(value, true, muleContext); retryScheduler = schedulerService.ioScheduler(); } /** * {@inheritDoc} */ @Override public String getName() { return name; } /** * {@inheritDoc} */ @Override public Optional<ConnectionProvider> getConnectionProvider() { return connectionProvider; } /** * {@inheritDoc} */ @Override public ConfigurationModel getModel() { return model; } /** * {@inheritDoc} */ @Override public Object getValue() { return value; } /** * {@inheritDoc} * * @throws IllegalStateException if invoked before {@link #initialise()} */ @Override public ConfigurationStats getStatistics() { checkState(configurationStats != null, "can't get statistics before initialise() is invoked"); return configurationStats; } private void initStats() { if (timeSupplier == null) { timeSupplier = new TimeSupplier(); } configurationStats = new DefaultMutableConfigurationStats(timeSupplier); } private boolean getDoTestConnectivityProperty() { return System.getProperty(DO_TEST_CONNECTIVITY_PROPERTY_NAME) != null ? valueOf(System.getProperty(DO_TEST_CONNECTIVITY_PROPERTY_NAME)) : true; } }