/* * 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.core.api.lifecycle.LifecycleUtils.assertNotStopping; 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.core.api.MuleContext; import org.mule.runtime.api.exception.MuleException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link ConnectionHandlerAdapter} which always returns the same connection (therefore cached), which is not established until * {@link #getConnection()} is first invoked. * <p/> * This implementation is thread-safe. * * @param <C> the generic type of the connection being wrapped * @since 4.0 */ final class CachedConnectionHandler<C> implements ConnectionHandlerAdapter<C> { private static final Logger LOGGER = LoggerFactory.getLogger(CachedConnectionHandler.class); private final ConnectionProvider<C> connectionProvider; private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final Lock readLock = readWriteLock.readLock(); private final Lock writeLock = readWriteLock.writeLock(); private final MuleContext muleContext; private C connection; /** * Creates a new instance * * @param connectionProvider the {@link ConnectionProvider} to be used to managed the connection * @param muleContext the owning {@link MuleContext} */ public CachedConnectionHandler(ConnectionProvider<C> connectionProvider, MuleContext muleContext) { this.connectionProvider = connectionProvider; this.muleContext = muleContext; } /** * On the first invocation to this method, a connection is established using the provided {@link #connectionProvider}. That * connection is cached and returned. * <p/> * Following invocations simply return the same connection. * * @return a {@code Connection} * @throws ConnectionException if a {@code Connection} could not be obtained * @throws IllegalStateException if the first invocation is executed while the {@link #muleContext} is stopping or stopped */ @Override public C getConnection() throws ConnectionException { C oldConnection; readLock.lock(); try { if (connection != null && validateConnection(connection).isValid()) { return connection; } oldConnection = connection; } finally { readLock.unlock(); } writeLock.lock(); try { // check another thread didn't beat us to it if (connection != null) { if (connection != oldConnection) { return connection; } disconnectAndCleanConnection(connection); } connection = createConnection(); return connection; } finally { writeLock.unlock(); } } private C createConnection() throws ConnectionException { assertNotStopping(muleContext, "Mule is shutting down... Cannot establish new connections"); connection = connectionProvider.connect(); return connection; } /** * This implementation doesn't require the concept of release. This method does nothing */ @Override public void release() { // no-op } /** * Disconnects the wrapped connection and clears the cache * * @throws MuleException in case of error */ @Override public void close() throws MuleException { writeLock.lock(); try { if (connectionProvider != null && connection != null) { connectionProvider.disconnect(connection); } } finally { connection = null; writeLock.unlock(); } } protected ConnectionValidationResult validateConnection(C connection) { ConnectionValidationResult validationResult = null; try { validationResult = connectionProvider.validate(connection); } catch (Exception e) { validationResult = ConnectionValidationResult.failure( "Error validating connection. Unexpected exception was thrown by the extension when validating the connection", e); } if (validationResult == null) { String errorMessage = "Error validating connection. validate() method from the connection provider can not return a null ConnectionValidationResult"; validationResult = ConnectionValidationResult.failure(errorMessage, new ConnectionException(errorMessage)); } return validationResult; } private void disconnectAndCleanConnection(C connection) { try { connectionProvider.disconnect(connection); } catch (Exception e) { LOGGER.debug("Error disconnecting the extension's connection", e); } finally { this.connection = null; } } }