org.springframework.integration.ip.tcp.connection.CachingClientConnectionFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.ip.tcp.connection.CachingClientConnectionFactory.java

Source

/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.integration.ip.tcp.connection;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;

import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.util.SimplePool;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ErrorMessage;

/**
 * Connection factory that caches connections from the underlying target factory. The underlying
 * factory will be reconfigured to have {@code singleUse=true} in order for the connection to be
 * returned to the cache after use. Users should not subsequently set the underlying property to
 * false, or cache starvation will result.
 *
 * @author Gary Russell
 * @since 2.2
 *
 */
public class CachingClientConnectionFactory extends AbstractClientConnectionFactory {

    private final AbstractClientConnectionFactory targetConnectionFactory;

    private final SimplePool<TcpConnectionSupport> pool;

    /**
     * Construct a caching connection factory that delegates to the provided factory, with
     * the provided pool size.
     * @param target the target factory.
     * @param poolSize the number of connections to allow.
     */
    public CachingClientConnectionFactory(AbstractClientConnectionFactory target, int poolSize) {
        super("", 0);
        // override single-use to true so the target creates multiple connections
        target.setSingleUse(true);
        this.targetConnectionFactory = target;
        class Callback implements SimplePool.PoolItemCallback<TcpConnectionSupport> {

            @Override
            public TcpConnectionSupport createForPool() {
                try {
                    return CachingClientConnectionFactory.this.targetConnectionFactory.getConnection();
                } catch (Exception e) {
                    throw new MessagingException("Failed to obtain connection", e);
                }
            }

            @Override
            public boolean isStale(TcpConnectionSupport connection) {
                return !connection.isOpen();
            }

            @Override
            public void removedFromPool(TcpConnectionSupport connection) {
                connection.close();
            }

        }
        this.pool = new SimplePool<TcpConnectionSupport>(poolSize, new Callback());
    }

    /**
     * @param connectionWaitTimeout the new timeout.
     * @see SimplePool#setWaitTimeout(long)
     */
    public void setConnectionWaitTimeout(int connectionWaitTimeout) {
        this.pool.setWaitTimeout(connectionWaitTimeout);
    }

    /**
     * @param poolSize the new pool size.
     * @see SimplePool#setPoolSize(int)
     */
    public void setPoolSize(int poolSize) {
        this.pool.setPoolSize(poolSize);
    }

    /**
     * @see SimplePool#getPoolSize()
     * @return the pool size.
     */
    public int getPoolSize() {
        return this.pool.getPoolSize();
    }

    /**
     * @see SimplePool#getIdleCount()
     * @return the idle count.
     */
    public int getIdleCount() {
        return this.pool.getIdleCount();
    }

    /**
     * @see SimplePool#getActiveCount()
     * @return the active count.
     */
    public int getActiveCount() {
        return this.pool.getActiveCount();
    }

    /**
     * @see SimplePool#getAllocatedCount()
     * @return the allocated count.
     */
    public int getAllocatedCount() {
        return this.pool.getAllocatedCount();
    }

    @Override
    public TcpConnectionSupport obtainConnection() {
        return new CachedConnection(this.pool.getItem(), getListener());
    }

    ///////////////// DELEGATE METHODS ///////////////////////

    @Override
    public boolean isRunning() {
        return this.targetConnectionFactory.isRunning();
    }

    @Override
    public int hashCode() {
        return this.targetConnectionFactory.hashCode();
    }

    @Override
    public void setComponentName(String componentName) {
        this.targetConnectionFactory.setComponentName(componentName);
    }

    @Override
    public String getComponentType() {
        return this.targetConnectionFactory.getComponentType();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        CachingClientConnectionFactory that = (CachingClientConnectionFactory) o;

        return this.targetConnectionFactory.equals(that.targetConnectionFactory);

    }

    @Override
    public int getSoTimeout() {
        return this.targetConnectionFactory.getSoTimeout();
    }

    @Override
    public void setSoTimeout(int soTimeout) {
        this.targetConnectionFactory.setSoTimeout(soTimeout);
    }

    @Override
    public int getSoReceiveBufferSize() {
        return this.targetConnectionFactory.getSoReceiveBufferSize();
    }

    @Override
    public void setSoReceiveBufferSize(int soReceiveBufferSize) {
        this.targetConnectionFactory.setSoReceiveBufferSize(soReceiveBufferSize);
    }

    @Override
    public int getSoSendBufferSize() {
        return this.targetConnectionFactory.getSoSendBufferSize();
    }

    @Override
    public void setSoSendBufferSize(int soSendBufferSize) {
        this.targetConnectionFactory.setSoSendBufferSize(soSendBufferSize);
    }

    @Override
    public boolean isSoTcpNoDelay() {
        return this.targetConnectionFactory.isSoTcpNoDelay();
    }

    @Override
    public void setSoTcpNoDelay(boolean soTcpNoDelay) {
        this.targetConnectionFactory.setSoTcpNoDelay(soTcpNoDelay);
    }

    @Override
    public int getSoLinger() {
        return this.targetConnectionFactory.getSoLinger();
    }

    @Override
    public void setSoLinger(int soLinger) {
        this.targetConnectionFactory.setSoLinger(soLinger);
    }

    @Override
    public boolean isSoKeepAlive() {
        return this.targetConnectionFactory.isSoKeepAlive();
    }

    @Override
    public void setSoKeepAlive(boolean soKeepAlive) {
        this.targetConnectionFactory.setSoKeepAlive(soKeepAlive);
    }

    @Override
    public int getSoTrafficClass() {
        return this.targetConnectionFactory.getSoTrafficClass();
    }

    @Override
    public void setSoTrafficClass(int soTrafficClass) {
        this.targetConnectionFactory.setSoTrafficClass(soTrafficClass);
    }

    @Override
    public String getHost() {
        return this.targetConnectionFactory.getHost();
    }

    @Override
    public int getPort() {
        return this.targetConnectionFactory.getPort();
    }

    @Override
    public TcpSender getSender() {
        return this.targetConnectionFactory.getSender();
    }

    @Override
    public Serializer<?> getSerializer() {
        return this.targetConnectionFactory.getSerializer();
    }

    @Override
    public Deserializer<?> getDeserializer() {
        return this.targetConnectionFactory.getDeserializer();
    }

    @Override
    public TcpMessageMapper getMapper() {
        return this.targetConnectionFactory.getMapper();
    }

    /**
     * Delegate TCP Client Connection factories that are used to receive
     * data need a Listener to send the messages to.
     * This applies to client factories used for outbound gateways
     * or for a pair of collaborating channel adapters.
     * <p>
     * During initialization, if a factory detects it has no listener
     * it's listening logic (active thread) is terminated.
     * <p>
     * The listener registered with a factory is provided to each
     * connection it creates so it can call the onMessage() method.
     * <p>
     * This code satisfies the first requirement in that this
     * listener signals to the factory that it needs to run
     * its listening logic.
     * <p>
     * When we wrap actual connections with CachedConnections,
     * the connection is given the wrapper as a listener, so it
     * can enhance the headers in onMessage(); the wrapper then invokes
     * the real listener supplied here, with the modified message.
     */
    @Override
    public void registerListener(TcpListener listener) {
        super.registerListener(listener);
        this.targetConnectionFactory.enableManualListenerRegistration();
    }

    @Override
    public void registerSender(TcpSender sender) {
        this.targetConnectionFactory.registerSender(sender);
    }

    @Override
    public void setTaskExecutor(Executor taskExecutor) {
        this.targetConnectionFactory.setTaskExecutor(taskExecutor);
    }

    @Override
    public void setDeserializer(Deserializer<?> deserializer) {
        this.targetConnectionFactory.setDeserializer(deserializer);
    }

    @Override
    public void setSerializer(Serializer<?> serializer) {
        this.targetConnectionFactory.setSerializer(serializer);
    }

    @Override
    public void setMapper(TcpMessageMapper mapper) {
        this.targetConnectionFactory.setMapper(mapper);
    }

    @Override
    public boolean isSingleUse() {
        return true;
    }

    /**
     * Ignored on this factory; connections are always cached in the pool. The underlying
     * connection factory will have its singleUse property coerced to true (causing the
     * connection to be returned). Setting it to false on the underlying factory after initialization
     * will cause cache starvation.
     * @param singleUse the singleUse.
     */
    @Override
    public void setSingleUse(boolean singleUse) {
        if (!singleUse && logger.isDebugEnabled()) {
            logger.debug("singleUse=false is not supported; cached connections are never closed");
        }
    }

    @Override
    public void setInterceptorFactoryChain(TcpConnectionInterceptorFactoryChain interceptorFactoryChain) {
        this.targetConnectionFactory.setInterceptorFactoryChain(interceptorFactoryChain);
    }

    @Override
    public void setLookupHost(boolean lookupHost) {
        this.targetConnectionFactory.setLookupHost(lookupHost);
    }

    @Override
    public boolean isLookupHost() {
        return this.targetConnectionFactory.isLookupHost();
    }

    @Override
    public void forceClose(TcpConnection connection) {
        if (connection instanceof CachedConnection) {
            ((CachedConnection) connection).physicallyClose();
        }
        // will be returned to pool but stale, so will be re-established
        super.forceClose(connection);
    }

    @Override
    public void enableManualListenerRegistration() {
        super.enableManualListenerRegistration();
        this.targetConnectionFactory.enableManualListenerRegistration();
    }

    @Override
    public void start() {
        setActive(true);
        this.targetConnectionFactory.start();
        super.start();
    }

    @Override
    public synchronized void stop() {
        this.targetConnectionFactory.stop();
        this.pool.removeAllIdleItems();
    }

    private final class CachedConnection extends TcpConnectionInterceptorSupport {

        private final AtomicBoolean released = new AtomicBoolean();

        CachedConnection(TcpConnectionSupport connection, @Nullable TcpListener tcpListener) { // NOSONAR false positive, connection not marked @Nullable

            super.setTheConnection(connection);
            super.registerListener(tcpListener);
        }

        @Override
        public void close() {
            if (!this.released.compareAndSet(false, true)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Connection " + getConnectionId() + " has already been released");
                }
            } else {
                /*
                 * If the delegate is stopped, actually close the connection, but still release
                 * it to the pool, it will be discarded/renewed the next time it is retrieved.
                 */
                if (!isRunning()) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Factory not running - closing " + getConnectionId());
                    }
                    super.close();
                }
                CachingClientConnectionFactory.this.pool.releaseItem(getTheConnection());
            }
        }

        @Override
        public String getConnectionId() {
            return "Cached:" + super.getConnectionId();
        }

        @Override
        public String toString() {
            return getConnectionId();
        }

        /**
         * We have to intercept the message to replace the connectionId header with
         * ours so the listener can correlate a response with a request. We supply
         * the actual connectionId in another header for convenience and tracing
         * purposes.
         */
        @Override
        public boolean onMessage(Message<?> message) {
            Message<?> modifiedMessage;
            if (message instanceof ErrorMessage) {
                Map<String, Object> headers = new HashMap<String, Object>(message.getHeaders());
                headers.put(IpHeaders.CONNECTION_ID, getConnectionId());
                if (headers.get(IpHeaders.ACTUAL_CONNECTION_ID) == null) {
                    headers.put(IpHeaders.ACTUAL_CONNECTION_ID, message.getHeaders().get(IpHeaders.CONNECTION_ID));
                }
                modifiedMessage = new ErrorMessage((Throwable) message.getPayload(), headers);
            } else {
                AbstractIntegrationMessageBuilder<?> messageBuilder = CachingClientConnectionFactory.this
                        .getMessageBuilderFactory().fromMessage(message)
                        .setHeader(IpHeaders.CONNECTION_ID, getConnectionId());
                if (message.getHeaders().get(IpHeaders.ACTUAL_CONNECTION_ID) == null) {
                    messageBuilder.setHeader(IpHeaders.ACTUAL_CONNECTION_ID,
                            message.getHeaders().get(IpHeaders.CONNECTION_ID));
                }
                modifiedMessage = messageBuilder.build();
            }
            TcpListener listener = getListener();
            if (listener != null) {
                listener.onMessage(modifiedMessage);
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Message discarded; no listener: " + message);
                }
            }
            return true;
        }

        private void physicallyClose() {
            getTheConnection().close();
        }

    }

}