org.springframework.integration.ip.tcp.TcpSendingMessageHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.ip.tcp.TcpSendingMessageHandler.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;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.Lifecycle;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
import org.springframework.integration.ip.tcp.connection.AbstractConnectionFactory;
import org.springframework.integration.ip.tcp.connection.ClientModeCapable;
import org.springframework.integration.ip.tcp.connection.ClientModeConnectionManager;
import org.springframework.integration.ip.tcp.connection.ConnectionFactory;
import org.springframework.integration.ip.tcp.connection.TcpConnection;
import org.springframework.integration.ip.tcp.connection.TcpConnectionFailedCorrelationEvent;
import org.springframework.integration.ip.tcp.connection.TcpSender;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;

/**
 * Tcp outbound channel adapter using a TcpConnection to
 * send data - if the connection factory is a server
 * factory, the TcpListener owns the connections. If it is
 * a client factory, this object owns the connection.
 *
 * @author Gary Russell
 * @author Artem Bilan
 *
 * @since 2.0
 *
 */
public class TcpSendingMessageHandler extends AbstractMessageHandler
        implements TcpSender, Lifecycle, ClientModeCapable {

    /**
     * A default retry interval for the {@link ClientModeConnectionManager} rescheduling.
     */
    public static final long DEFAULT_RETRY_INTERVAL = 60000;

    protected final Object lifecycleMonitor = new Object(); // NOSONAR

    private final Map<String, TcpConnection> connections = new ConcurrentHashMap<>();

    private AbstractConnectionFactory clientConnectionFactory;

    private AbstractConnectionFactory serverConnectionFactory;

    private boolean isClientMode;

    private boolean isSingleUse;

    private long retryInterval = DEFAULT_RETRY_INTERVAL;

    private volatile ScheduledFuture<?> scheduledFuture;

    private volatile ClientModeConnectionManager clientModeConnectionManager;

    private volatile boolean active;

    protected TcpConnection obtainConnection(Message<?> message) {
        TcpConnection connection;
        Assert.notNull(this.clientConnectionFactory, "'clientConnectionFactory' cannot be null");
        try {
            connection = this.clientConnectionFactory.getConnection();
        } catch (Exception e) {
            logger.error("Error creating connection", e);
            throw new MessageHandlingException(message, "Failed to obtain a connection in the [" + this + ']', e);
        }
        return connection;
    }

    /**
     * Writes the message payload to the underlying socket, using the specified
     * message format.
     * @see org.springframework.messaging.MessageHandler#handleMessage(org.springframework.messaging.Message)
     */
    @Override
    public void handleMessageInternal(final Message<?> message) {
        if (this.serverConnectionFactory != null) {
            handleMessageAsServer(message);
        } else {
            handleMessageAsClient(message);
        }
    }

    private void handleMessageAsServer(Message<?> message) {
        // We don't own the connection, we are asynchronously replying
        String connectionId = message.getHeaders().get(IpHeaders.CONNECTION_ID, String.class);
        TcpConnection connection = null;
        if (connectionId != null) {
            connection = this.connections.get(connectionId);
        }
        if (connection != null) {
            try {
                connection.send(message);
            } catch (Exception ex) {
                logger.error("Error sending message", ex);
                connection.close();
                throw IntegrationUtils.wrapInHandlingExceptionIfNecessary(message,
                        () -> "Error sending message in the [" + this + ']', ex);
            } finally {
                if (this.isSingleUse) { // close after replying
                    connection.close();
                }
            }
        } else {
            logger.error("Unable to find outbound socket for " + message);
            MessageHandlingException messageHandlingException = new MessageHandlingException(message,
                    "Unable to find outbound socket in the [" + this + ']');
            publishNoConnectionEvent(messageHandlingException, connectionId);
            throw messageHandlingException;
        }
    }

    private void handleMessageAsClient(Message<?> message) {
        // we own the connection
        TcpConnection connection = null;
        try {
            connection = doWrite(message);
        } catch (MessageHandlingException e) {
            // retry - socket may have closed
            if (e.getCause() instanceof IOException) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Fail on first write attempt", e);
                }
                connection = doWrite(message);
            } else {
                throw e;
            }
        } finally {
            if (connection != null && this.isSingleUse && this.clientConnectionFactory.getListener() == null) {
                // if there's no collaborating inbound adapter, close immediately, otherwise
                // it will close after receiving the reply.
                connection.close();
            }
        }
    }

    /**
     * Method that actually does the write.
     * @param message The message to write.
     * @return the connection.
     */
    protected TcpConnection doWrite(Message<?> message) {
        TcpConnection connection = null;
        try {
            connection = obtainConnection(message);
            if (logger.isDebugEnabled()) {
                logger.debug("Got Connection " + connection.getConnectionId());
            }
            connection.send(message);
        } catch (Exception ex) {
            final String connectionId;
            if (connection != null) {
                connectionId = connection.getConnectionId();
            } else {
                connectionId = null;
            }

            throw IntegrationUtils.wrapInHandlingExceptionIfNecessary(message,
                    () -> "Failed to handle message in the [" + this + "] using " + connectionId, ex);
        }
        return connection;
    }

    private void publishNoConnectionEvent(MessageHandlingException messageHandlingException, String connectionId) {
        AbstractConnectionFactory cf = this.serverConnectionFactory != null ? this.serverConnectionFactory
                : this.clientConnectionFactory;
        ApplicationEventPublisher applicationEventPublisher = cf.getApplicationEventPublisher();
        if (applicationEventPublisher != null) {
            applicationEventPublisher.publishEvent(
                    new TcpConnectionFailedCorrelationEvent(this, connectionId, messageHandlingException));
        }
    }

    /**
     * Sets the client or server connection factory; for this (an outbound adapter), if
     * the factory is a server connection factory, the sockets are owned by a receiving
     * channel adapter and this adapter is used to send replies.
     *
     * @param connectionFactory the connectionFactory to set
     */
    public void setConnectionFactory(AbstractConnectionFactory connectionFactory) {
        if (connectionFactory instanceof AbstractClientConnectionFactory) {
            this.clientConnectionFactory = connectionFactory;
        } else {
            this.serverConnectionFactory = connectionFactory;
            connectionFactory.registerSender(this);
        }
        this.isSingleUse = connectionFactory.isSingleUse();
    }

    @Override
    public void addNewConnection(TcpConnection connection) {
        this.connections.put(connection.getConnectionId(), connection);
    }

    @Override
    public void removeDeadConnection(TcpConnection connection) {
        this.connections.remove(connection.getConnectionId());
    }

    @Override
    public String getComponentType() {
        return "ip:tcp-outbound-channel-adapter";
    }

    @Override
    protected void onInit() {
        super.onInit();
        if (this.isClientMode) {
            Assert.notNull(this.clientConnectionFactory,
                    "For client-mode, connection factory must be type='client'");
            Assert.isTrue(!this.clientConnectionFactory.isSingleUse(),
                    "For client-mode, connection factory must have single-use='false'");
        }
    }

    @Override
    public void start() {
        synchronized (this.lifecycleMonitor) {
            if (!this.active) {
                this.active = true;
                if (this.clientConnectionFactory != null) {
                    this.clientConnectionFactory.start();
                }
                if (this.serverConnectionFactory != null) {
                    this.serverConnectionFactory.start();
                }
                if (this.isClientMode) {
                    Assert.notNull(this.clientConnectionFactory,
                            "For client-mode, connection factory must be type='client'");
                    ClientModeConnectionManager manager = new ClientModeConnectionManager(
                            this.clientConnectionFactory);
                    this.clientModeConnectionManager = manager;
                    TaskScheduler taskScheduler = getTaskScheduler();
                    Assert.state(taskScheduler != null, "Client mode requires a task scheduler");
                    this.scheduledFuture = taskScheduler.scheduleAtFixedRate(manager, this.retryInterval);
                }
            }
        }
    }

    @Override
    public void stop() {
        synchronized (this.lifecycleMonitor) {
            if (this.active) {
                this.active = false;
                if (this.scheduledFuture != null) {
                    this.scheduledFuture.cancel(true);
                }
                this.clientModeConnectionManager = null;
                if (this.clientConnectionFactory != null) {
                    this.clientConnectionFactory.stop();
                }
                if (this.serverConnectionFactory != null) {
                    this.serverConnectionFactory.stop();
                }
            }
        }
    }

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

    /**
     * @return the clientConnectionFactory
     */
    protected ConnectionFactory getClientConnectionFactory() {
        return this.clientConnectionFactory;
    }

    /**
     * @return the serverConnectionFactory
     */
    protected ConnectionFactory getServerConnectionFactory() {
        return this.serverConnectionFactory;
    }

    /**
     * @return the connections
     */
    protected Map<String, TcpConnection> getConnections() {
        return this.connections;
    }

    /**
     * @return the isClientMode
     */
    @Override
    public boolean isClientMode() {
        return this.isClientMode;
    }

    /**
     * @param isClientMode the isClientMode to set
     */
    public void setClientMode(boolean isClientMode) {
        this.isClientMode = isClientMode;
    }

    /**
     * @return the retryInterval
     */
    public long getRetryInterval() {
        return this.retryInterval;
    }

    /**
     * @param retryInterval the retryInterval to set
     */
    public void setRetryInterval(long retryInterval) {
        this.retryInterval = retryInterval;
    }

    @Override
    public boolean isClientModeConnected() {
        if (this.isClientMode && this.clientModeConnectionManager != null) {
            return this.clientModeConnectionManager.isConnected();
        } else {
            return false;
        }
    }

    @Override
    public void retryConnection() {
        if (this.active && this.isClientMode && this.clientModeConnectionManager != null) {
            this.clientModeConnectionManager.run();
        }
    }

}