/* * Copyright 2002-2016 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 * * http://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.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; import org.springframework.context.ApplicationEventPublisher; import org.springframework.integration.context.OrderlyShutdownCapable; import org.springframework.integration.gateway.MessagingGatewaySupport; 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.AbstractServerConnectionFactory; import org.springframework.integration.ip.tcp.connection.ClientModeCapable; import org.springframework.integration.ip.tcp.connection.ClientModeConnectionManager; import org.springframework.integration.ip.tcp.connection.TcpConnection; import org.springframework.integration.ip.tcp.connection.TcpConnectionFailedCorrelationEvent; import org.springframework.integration.ip.tcp.connection.TcpListener; import org.springframework.integration.ip.tcp.connection.TcpSender; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.messaging.support.ErrorMessage; import org.springframework.util.Assert; /** * Inbound Gateway using a server connection factory - threading is controlled by the * factory. For java.net connections, each socket can process only one message at a time. * For java.nio connections, messages may be multiplexed but the client will need to * provide correlation logic. If the client is a {@link TcpOutboundGateway} multiplexing * is not used, but multiple concurrent connections can be used if the connection factory uses * single-use connections. For true asynchronous bi-directional communication, a pair of * inbound / outbound channel adapters should be used. * * @author Gary Russell * @since 2.0 */ public class TcpInboundGateway extends MessagingGatewaySupport implements TcpListener, TcpSender, ClientModeCapable, OrderlyShutdownCapable { private volatile AbstractServerConnectionFactory serverConnectionFactory; private volatile AbstractClientConnectionFactory clientConnectionFactory; private final Map<String, TcpConnection> connections = new ConcurrentHashMap<String, TcpConnection>(); private volatile boolean isClientMode; private volatile boolean isSingleUse; private volatile long retryInterval = 60000; private volatile ScheduledFuture<?> scheduledFuture; private volatile ClientModeConnectionManager clientModeConnectionManager; private volatile boolean active; private volatile boolean shuttingDown; private final AtomicInteger activeCount = new AtomicInteger(); @Override public boolean onMessage(Message<?> message) { boolean isErrorMessage = message instanceof ErrorMessage; try { if (this.shuttingDown) { if (logger.isInfoEnabled()) { logger.info("Inbound message ignored; shutting down; " + message.toString()); } } else { if (isErrorMessage) { /* * Socket errors are sent here so they can be conveyed to any waiting thread. * There's not one here; simply ignore. */ return false; } this.activeCount.incrementAndGet(); try { return doOnMessage(message); } finally { this.activeCount.decrementAndGet(); } } return false; } finally { String connectionId = (String) message.getHeaders().get(IpHeaders.CONNECTION_ID); if (connectionId != null && !isErrorMessage && this.isSingleUse) { if (this.serverConnectionFactory != null) { this.serverConnectionFactory.closeConnection(connectionId); } else { this.clientConnectionFactory.closeConnection(connectionId); } } } } private boolean doOnMessage(Message<?> message) { Message<?> reply = this.sendAndReceiveMessage(message); if (reply == null) { if (logger.isDebugEnabled()) { logger.debug("null reply received for " + message + " nothing to send"); } return false; } String connectionId = (String) message.getHeaders().get(IpHeaders.CONNECTION_ID); TcpConnection connection = null; if (connectionId != null) { connection = this.connections.get(connectionId); } if (connection == null) { publishNoConnectionEvent(message, connectionId); logger.error("Connection not found when processing reply " + reply + " for " + message); return false; } try { connection.send(reply); } catch (Exception e) { logger.error("Failed to send reply", e); } return false; } private void publishNoConnectionEvent(Message<?> message, String connectionId) { AbstractConnectionFactory cf = this.serverConnectionFactory != null ? this.serverConnectionFactory : this.clientConnectionFactory; ApplicationEventPublisher applicationEventPublisher = cf.getApplicationEventPublisher(); if (applicationEventPublisher != null) { applicationEventPublisher.publishEvent( new TcpConnectionFailedCorrelationEvent(this, connectionId, new MessagingException(message, "Connection not found to process reply."))); } } /** * @return true if the associated connection factory is listening. */ public boolean isListening() { return this.serverConnectionFactory != null && this.serverConnectionFactory.isListening(); } /** * Must be {@link AbstractClientConnectionFactory} or {@link AbstractServerConnectionFactory}. * * @param connectionFactory the Connection Factory */ public void setConnectionFactory(AbstractConnectionFactory connectionFactory) { Assert.notNull(connectionFactory, "Connection factory must not be null"); if (connectionFactory instanceof AbstractServerConnectionFactory) { this.serverConnectionFactory = (AbstractServerConnectionFactory) connectionFactory; } else if (connectionFactory instanceof AbstractClientConnectionFactory) { this.clientConnectionFactory = (AbstractClientConnectionFactory) connectionFactory; } else { throw new IllegalArgumentException("Connection factory must be either an " + "AbstractServerConnectionFactory or an AbstractClientConnectionFactory"); } connectionFactory.registerListener(this); 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-inbound-gateway"; } @Override protected void onInit() throws Exception { 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 // protected by super#lifecycleLock protected void doStart() { super.doStart(); if (!this.active) { this.active = true; this.shuttingDown = false; if (this.serverConnectionFactory != null) { this.serverConnectionFactory.start(); } if (this.clientConnectionFactory != null) { this.clientConnectionFactory.start(); } if (this.isClientMode) { ClientModeConnectionManager manager = new ClientModeConnectionManager( this.clientConnectionFactory); this.clientModeConnectionManager = manager; Assert.state(this.getTaskScheduler() != null, "Client mode requires a task scheduler"); this.scheduledFuture = this.getTaskScheduler().scheduleAtFixedRate(manager, this.retryInterval); } } } @Override // protected by super#lifecycleLock protected void doStop() { super.doStop(); 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(); } } } /** * @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(); } } @Override public int beforeShutdown() { this.shuttingDown = true; return this.activeCount.get(); } @Override public int afterShutdown() { this.stop(); return this.activeCount.get(); } }