/* * Copyright 2001-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.connection; import java.net.InetAddress; import java.net.Socket; import java.util.Collections; import java.util.Map; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.serializer.Deserializer; import org.springframework.core.serializer.Serializer; import org.springframework.integration.ip.IpHeaders; import org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer; import org.springframework.messaging.MessagingException; import org.springframework.messaging.support.ErrorMessage; import org.springframework.util.Assert; /** * Base class for TcpConnections. TcpConnections are established by * client connection factories (outgoing) or server connection factories * (incoming). * * @author Gary Russell * @since 2.0 * */ public abstract class TcpConnectionSupport implements TcpConnection { protected final Log logger = LogFactory.getLog(this.getClass()); private final CountDownLatch listenerRegisteredLatch = new CountDownLatch(1); private final boolean server; private final AtomicLong sequence = new AtomicLong(); private final ApplicationEventPublisher applicationEventPublisher; private final AtomicBoolean closePublished = new AtomicBoolean(); private final AtomicBoolean exceptionSent = new AtomicBoolean(); private final SocketInfo socketInfo; @SuppressWarnings("rawtypes") private volatile Deserializer deserializer; @SuppressWarnings("rawtypes") private volatile Serializer serializer; private volatile TcpMessageMapper mapper; private volatile TcpListener listener; private volatile TcpSender sender; private volatile String connectionId; private volatile String hostName = "unknown"; private volatile String hostAddress = "unknown"; private volatile String connectionFactoryName = "unknown"; private volatile boolean noReadErrorOnClose; private volatile boolean manualListenerRegistration; public TcpConnectionSupport() { this(null); } public TcpConnectionSupport(ApplicationEventPublisher applicationEventPublisher) { this.server = false; this.applicationEventPublisher = applicationEventPublisher; this.socketInfo = null; } /** * Creates a {@link TcpConnectionSupport} object and publishes a * {@link TcpConnectionOpenEvent}, if an event publisher is provided. * @param socket the underlying socket. * @param server true if this connection is a server connection * @param lookupHost true if reverse lookup of the host name should be performed, * otherwise, the ip address will be used for identification purposes. * @param applicationEventPublisher the publisher to which open, close and exception events will * be sent; may be null if event publishing is not required. * @param connectionFactoryName the name of the connection factory creating this connection; used * during event publishing, may be null, in which case "unknown" will be used. */ public TcpConnectionSupport(Socket socket, boolean server, boolean lookupHost, ApplicationEventPublisher applicationEventPublisher, String connectionFactoryName) { this.socketInfo = new SocketInfo(socket); this.server = server; InetAddress inetAddress = socket.getInetAddress(); if (inetAddress != null) { this.hostAddress = inetAddress.getHostAddress(); if (lookupHost) { this.hostName = inetAddress.getHostName(); } else { this.hostName = this.hostAddress; } } int port = socket.getPort(); int localPort = socket.getLocalPort(); this.connectionId = this.hostName + ":" + port + ":" + localPort + ":" + UUID.randomUUID().toString(); this.applicationEventPublisher = applicationEventPublisher; if (connectionFactoryName != null) { this.connectionFactoryName = connectionFactoryName; } if (this.logger.isDebugEnabled()) { this.logger.debug("New connection " + this.getConnectionId()); } } /** * Closes this connection. */ @Override public void close() { if (this.sender != null) { this.sender.removeDeadConnection(this); } // close() may be called multiple times; only publish once if (!this.closePublished.getAndSet(true)) { this.publishConnectionCloseEvent(); } } /** * If we have been intercepted, propagate the close from the outermost interceptor; * otherwise, just call close(). * * @param isException true when this call is the result of an Exception. */ protected void closeConnection(boolean isException) { TcpListener listener = getListener(); if (!(listener instanceof TcpConnectionInterceptor)) { close(); } else { TcpConnectionInterceptor outerInterceptor = (TcpConnectionInterceptor) listener; while (outerInterceptor.getListener() instanceof TcpConnectionInterceptor) { outerInterceptor = (TcpConnectionInterceptor) outerInterceptor.getListener(); } outerInterceptor.close(); if (isException) { // ensure physical close in case the interceptor did not close this.close(); } } } /** * @return the mapper */ public TcpMessageMapper getMapper() { return this.mapper; } /** * @param mapper the mapper to set */ public void setMapper(TcpMessageMapper mapper) { Assert.notNull(mapper, this.getClass().getName() + " Mapper may not be null"); this.mapper = mapper; if (this.serializer != null && !(this.serializer instanceof AbstractByteArraySerializer)) { mapper.setStringToBytes(false); } } /** * * @return the deserializer */ @Override public Deserializer<?> getDeserializer() { return this.deserializer; } /** * @param deserializer the deserializer to set */ public void setDeserializer(Deserializer<?> deserializer) { this.deserializer = deserializer; } /** * * @return the serializer */ @Override public Serializer<?> getSerializer() { return this.serializer; } /** * @param serializer the serializer to set */ public void setSerializer(Serializer<?> serializer) { this.serializer = serializer; if (!(serializer instanceof AbstractByteArraySerializer)) { this.mapper.setStringToBytes(false); } } /** * Set the listener that will receive incoming Messages. * @param listener The listener. */ public void registerListener(TcpListener listener) { this.listener = listener; this.listenerRegisteredLatch.countDown(); } /** * Set whether or not automatic or manual registration of the {@link TcpListener} is to be * used. (Default automatic). When manual registration is in place, incoming messages will * be delayed until the listener is registered. * @since 1.4.5 */ public void enableManualListenerRegistration() { this.manualListenerRegistration = true; this.listener = message -> getListener().onMessage(message); } /** * Registers a sender. Used on server side connections so a * sender can determine which connection to send a reply * to. * @param sender the sender. */ public void registerSender(TcpSender sender) { this.sender = sender; if (sender != null) { sender.addNewConnection(this); } } /** * @return the listener */ @Override public TcpListener getListener() { if (this.manualListenerRegistration) { if (this.logger.isDebugEnabled()) { this.logger.debug(getConnectionId() + " Waiting for listener registration"); } waitForListenerRegistration(); } return this.listener; } private void waitForListenerRegistration() { try { Assert.state(this.listenerRegisteredLatch.await(1, TimeUnit.MINUTES), "TcpListener not registered"); this.manualListenerRegistration = false; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new MessagingException("Interrupted while waiting for listener registration", e); } } /** * @return the sender */ public TcpSender getSender() { return this.sender; } @Override public boolean isServer() { return this.server; } @Override public long incrementAndGetConnectionSequence() { return this.sequence.incrementAndGet(); } @Override public String getHostAddress() { return this.hostAddress; } @Override public String getHostName() { return this.hostName; } @Override public String getConnectionId() { return this.connectionId; } /** * @since 4.2.5 */ @Override public SocketInfo getSocketInfo() { return this.socketInfo; } protected boolean isNoReadErrorOnClose() { return this.noReadErrorOnClose; } protected void setNoReadErrorOnClose(boolean noReadErrorOnClose) { this.noReadErrorOnClose = noReadErrorOnClose; } protected final void sendExceptionToListener(Exception e) { if (!this.exceptionSent.getAndSet(true) && this.getListener() != null) { Map<String, Object> headers = Collections.singletonMap(IpHeaders.CONNECTION_ID, (Object) this.getConnectionId()); ErrorMessage errorMessage = new ErrorMessage(e, headers); this.getListener().onMessage(errorMessage); } } protected void publishConnectionOpenEvent() { TcpConnectionEvent event = new TcpConnectionOpenEvent(this, this.connectionFactoryName); doPublish(event); } protected void publishConnectionCloseEvent() { TcpConnectionEvent event = new TcpConnectionCloseEvent(this, this.connectionFactoryName); doPublish(event); } protected void publishConnectionExceptionEvent(Throwable t) { TcpConnectionEvent event = new TcpConnectionExceptionEvent(this, this.connectionFactoryName, t); doPublish(event); } /** * Allow interceptors etc to publish events, perhaps subclasses of * TcpConnectionEvent. The event source must be this connection. * @param event the event to publish. */ public void publishEvent(TcpConnectionEvent event) { Assert.isTrue(event.getSource() == this, "Can only publish events with this as the source"); this.doPublish(event); } private void doPublish(TcpConnectionEvent event) { try { if (this.applicationEventPublisher == null) { this.logger.warn("No publisher available to publish " + event); } else { this.applicationEventPublisher.publishEvent(event); if (this.logger.isTraceEnabled()) { this.logger.trace("Published: " + event); } } } catch (Exception e) { if (this.logger.isDebugEnabled()) { this.logger.debug("Failed to publish " + event, e); } else if (this.logger.isWarnEnabled()) { this.logger.warn("Failed to publish " + event + ":" + e.getMessage()); } } } }