/*
* 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.internal.connection;
import static org.mule.runtime.api.connection.ConnectionValidationResult.failure;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.assertNotStopping;
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.slf4j.LoggerFactory.getLogger;
import org.mule.runtime.api.config.PoolingProfile;
import org.mule.runtime.api.connection.ConnectionException;
import org.mule.runtime.api.connection.ConnectionHandler;
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.lifecycle.Lifecycle;
import org.mule.runtime.api.util.Reference;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.connector.ConnectionManager;
import org.mule.runtime.core.api.retry.RetryPolicyTemplate;
import org.mule.runtime.core.retry.policies.NoRetryPolicyTemplate;
import org.mule.runtime.extension.api.runtime.ConfigurationInstance;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.inject.Inject;
import org.slf4j.Logger;
/**
* Implementation of {@link ConnectionManager} which manages connections opened on a specific application.
*
* @since 4.0
*/
public final class DefaultConnectionManager implements ConnectionManagerAdapter, Lifecycle {
private static final Logger LOGGER = getLogger(DefaultConnectionManager.class);
private final Map<Reference<Object>, ConnectionManagementStrategy> connections = new HashMap<>();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private final MuleContext muleContext;
private final RetryPolicyTemplate retryPolicyTemplate;
private final PoolingProfile defaultPoolingProfile;
private final ConnectionManagementStrategyFactory managementStrategyFactory;
/**
* Creates a new instance
*
* @param muleContext the {@link MuleContext} of the owned application
*/
@Inject
public DefaultConnectionManager(MuleContext muleContext) {
this.muleContext = muleContext;
this.defaultPoolingProfile = new PoolingProfile();
this.retryPolicyTemplate = new NoRetryPolicyTemplate();
managementStrategyFactory = new ConnectionManagementStrategyFactory(defaultPoolingProfile, muleContext);
}
/**
* {@inheritDoc}
*
* @throws IllegalStateException if invoked while the {@link #muleContext} is stopped or stopping
*/
@Override
public <C> void bind(Object owner, ConnectionProvider<C> connectionProvider) {
assertNotStopping(muleContext, "Mule is shutting down... cannot bind new connections");
connectionProvider = new LifecycleAwareConnectionProviderWrapper<>(connectionProvider, muleContext);
ConnectionManagementStrategy<C> managementStrategy = managementStrategyFactory.getStrategy(connectionProvider);
ConnectionManagementStrategy<C> previous = null;
writeLock.lock();
try {
previous = connections.put(new Reference<>(owner), managementStrategy);
} finally {
writeLock.unlock();
}
if (previous != null) {
close(previous);
}
}
/**
* {@inheritDoc}
*/
@Override
public <C> RetryPolicyTemplate getRetryTemplateFor(ConnectionProvider<C> connectionProvider) {
return connectionProvider instanceof ConnectionProviderWrapper
? ((ConnectionProviderWrapper) connectionProvider).getRetryPolicyTemplate()
: getDefaultRetryPolicyTemplate();
}
/**
* {@inheritDoc}
*/
@Override
public <C> ConnectionValidationResult testConnectivity(ConnectionProvider<C> connectionProvider) {
return doTestConnectivity(() -> testConnectivity(connectionProvider, managementStrategyFactory.getStrategy(connectionProvider)
.getConnectionHandler()));
}
/**
* {@inheritDoc}
*/
@Override
public ConnectionValidationResult testConnectivity(ConfigurationInstance configurationInstance)
throws IllegalArgumentException {
if (!configurationInstance.getConnectionProvider().isPresent()) {
throw new IllegalArgumentException("The component does not support connectivity testing");
}
return doTestConnectivity(() -> {
ConnectionProvider<Object> connectionProvider = configurationInstance.getConnectionProvider().get();
final Object config = configurationInstance.getValue();
ConnectionHandler<Object> connectionHandler;
try {
readLock.lock();
try {
connectionHandler = hasBinding(config)
? getConnection(config)
: managementStrategyFactory.getStrategy(connectionProvider).getConnectionHandler();
} finally {
readLock.unlock();
}
} catch (ConnectionException e) {
return failure(e.getMessage(), e.getErrorType().orElse(null), e);
}
return testConnectivity(connectionProvider, connectionHandler);
});
}
private ConnectionValidationResult doTestConnectivity(Callable<ConnectionValidationResult> callable) {
try {
return callable.call();
} catch (Exception e) {
return failure("Exception was found trying to test connectivity", e);
}
}
private <C> ConnectionValidationResult testConnectivity(ConnectionProvider<C> connectionProvider,
ConnectionHandler<C> connectionHandler)
throws Exception {
try {
return connectionProvider.validate(connectionHandler.getConnection());
} catch (ConnectionException e) {
return failure(e.getMessage(), e.getErrorType().orElse(null), e);
} finally {
if (connectionHandler != null) {
connectionHandler.release();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasBinding(Object config) {
return connections.containsKey(new Reference<>(config));
}
/**
* {@inheritDoc}
*/
// TODO: MULE-9082
@Override
public void unbind(Object config) {
ConnectionManagementStrategy managementStrategy;
writeLock.lock();
try {
managementStrategy = connections.remove(new Reference<>(config));
} finally {
writeLock.unlock();
}
if (managementStrategy != null) {
close(managementStrategy);
}
}
/**
* {@inheritDoc}
*/
@Override
public <C> ConnectionHandler<C> getConnection(Object config) throws ConnectionException {
ConnectionManagementStrategy<C> handlingStrategy = null;
readLock.lock();
try {
handlingStrategy = connections.get(new Reference<>(config));
} finally {
readLock.unlock();
}
if (handlingStrategy == null) {
throw new ConnectionException("No ConnectionProvider has been registered for owner " + config);
}
return handlingStrategy.getConnectionHandler();
}
/**
* Breaks all bindings and closes all connections
*
* @throws MuleException in case of error.
*/
@Override
public void stop() throws MuleException {
writeLock.lock();
try {
connections.values().stream().forEach(this::close);
connections.clear();
} finally {
writeLock.unlock();
}
}
// TODO: MULE-9082
private void close(ConnectionManagementStrategy managementStrategy) {
try {
managementStrategy.close();
} catch (Exception e) {
LOGGER.warn("An error was found trying to release connections", e);
}
}
@Override
public void dispose() {
disposeIfNeeded(retryPolicyTemplate, LOGGER);
}
@Override
public void initialise() throws InitialisationException {
initialiseIfNeeded(retryPolicyTemplate, true, muleContext);
}
@Override
public void start() throws MuleException {
startIfNeeded(retryPolicyTemplate);
}
/**
* {@inheritDoc}
*/
@Override
public RetryPolicyTemplate getDefaultRetryPolicyTemplate() {
return retryPolicyTemplate;
}
/**
* {@inheritDoc}
*/
@Override
public PoolingProfile getDefaultPoolingProfile() {
return defaultPoolingProfile;
}
}