/**
*
* Copyright 2004 Protique Ltd
*
* 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.activemq.transport.tcp;
import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
import EDU.oswego.cs.dl.util.concurrent.BoundedChannel;
import EDU.oswego.cs.dl.util.concurrent.BoundedLinkedQueue;
import EDU.oswego.cs.dl.util.concurrent.Executor;
import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.io.WireFormat;
import org.activemq.io.WireFormatLoader;
import org.activemq.message.Packet;
import org.activemq.transport.TransportChannelSupport;
import org.activemq.transport.TransportStatusEvent;
import org.activemq.util.JMSExceptionHelper;
import javax.jms.JMSException;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
/**
* A tcp implementation of a TransportChannel
*
* @version $Revision: 1.2 $
*/
public class TcpTransportChannel extends TransportChannelSupport implements Runnable {
private static final int DEFAULT_SOCKET_BUFFER_SIZE = 64 * 1024;
private static final Log log = LogFactory.getLog(TcpTransportChannel.class);
protected Socket socket;
protected DataOutputStream dataOut;
protected DataInputStream dataIn;
private WireFormatLoader wireFormatLoader;
private SynchronizedBoolean closed;
private SynchronizedBoolean started;
private Object outboundLock;
private Executor executor;
private Thread thread;
private boolean useAsyncSend = false;
private int soTimeout = 10000;
private int socketBufferSize = DEFAULT_SOCKET_BUFFER_SIZE;
private BoundedChannel exceptionsList;
private TcpTransportServerChannel serverChannel;
/**
* Construct basic helpers
*
* @param wireFormat
*/
protected TcpTransportChannel(WireFormat wireFormat) {
super(wireFormat);
this.wireFormatLoader = new WireFormatLoader(wireFormat);
closed = new SynchronizedBoolean(false);
started = new SynchronizedBoolean(false);
// there's not much point logging all exceptions, lets just keep a few around
exceptionsList = new BoundedLinkedQueue(10);
outboundLock = new Object();
setUseAsyncSend(useAsyncSend);
super.setCachingEnabled(true);
}
/**
* Connect to a remote Node - e.g. a Broker
*
* @param wireFormat
* @param remoteLocation
* @throws JMSException
*/
public TcpTransportChannel(WireFormat wireFormat, URI remoteLocation) throws JMSException {
this(wireFormat);
try {
this.socket = createSocket(remoteLocation);
initializeStreams();
}
catch (Exception ioe) {
throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed. " + "URI was: "
+ remoteLocation + " Reason: " + ioe, ioe);
}
}
/**
* Connect to a remote Node - e.g. a Broker
*
* @param wireFormat
* @param remoteLocation
* @param localLocation - e.g. local InetAddress and local port
* @throws JMSException
*/
public TcpTransportChannel(WireFormat wireFormat, URI remoteLocation, URI localLocation) throws JMSException {
this(wireFormat);
try {
this.socket = createSocket(remoteLocation, localLocation);
initializeStreams();
}
catch (Exception ioe) {
throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
}
}
/**
* Initialize from a ServerSocket
* @param serverChannel
* @param wireFormat
* @param socket
* @param executor
* @throws JMSException
*/
public TcpTransportChannel(TcpTransportServerChannel serverChannel,WireFormat wireFormat, Socket socket, Executor executor) throws JMSException {
this(wireFormat);
this.socket = socket;
this.executor = executor;
this.serverChannel = serverChannel;
setServerSide(true);
try {
initialiseSocket(socket);
initializeStreams();
}
catch (IOException ioe) {
throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
}
}
public TcpTransportChannel(WireFormat wireFormat, Socket socket, Executor executor) throws JMSException {
this(wireFormat);
this.socket = socket;
this.executor = executor;
try {
initialiseSocket(socket);
initializeStreams();
}
catch (IOException ioe) {
throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
}
}
/**
* start listeneing for events
*
* @throws JMSException if an error occurs
*/
public void start() throws JMSException {
if (started.commit(false, true)) {
thread = new Thread(this, toString());
try {
if (isServerSide()) {
thread.setDaemon(true);
WireFormat wf = wireFormatLoader.getWireFormat(dataIn);
if (wf != null) {
setWireFormat(wf);
}
getWireFormat().registerTransportStreams(dataOut, dataIn);
getWireFormat().initiateServerSideProtocol();
}
else {
getWireFormat().registerTransportStreams(dataOut, dataIn);
thread.setPriority(Thread.NORM_PRIORITY + 2);
}
//enable caching on the wire format
currentWireFormat.setCachingEnabled(isCachingEnabled());
thread.start();
//send the wire format
if (!isServerSide()) {
getWireFormat().initiateClientSideProtocol();
}
fireStatusEvent(new TransportStatusEvent(this,TransportStatusEvent.CONNECTED));
}
catch (EOFException e) {
doClose(e);
}
catch (IOException e) {
JMSException jmsEx = new JMSException("start failed: " + e.getMessage());
jmsEx.initCause(e);
jmsEx.setLinkedException(e);
throw jmsEx;
}
}
}
/**
* close the channel
*/
public void stop() {
if (closed.commit(false, true)) {
super.stop();
try {
if (executor != null) {
stopExecutor(executor);
}
closeStreams();
socket.close();
}
catch (Exception e) {
log.warn("Caught while closing: " + e + ". Now Closed", e);
}
}
closed.set(true);
if (this.serverChannel != null){
this.serverChannel.removeClient(this);
}
}
public void forceDisconnect() {
log.debug("Forcing disconnect");
if (socket != null && socket.isConnected()) {
try {
socket.close();
}
catch (IOException e) {
// Ignore
}
}
}
/**
* Asynchronously send a Packet
*
* @param packet
* @throws JMSException
*/
public void asyncSend(final Packet packet) throws JMSException {
if (executor != null) {
try {
executor.execute(new Runnable() {
public void run() {
try {
if (!isClosed()) {
doAsyncSend(packet);
}
}
catch (JMSException e) {
try {
exceptionsList.put(e);
}
catch (InterruptedException e1) {
log.warn("Failed to add element to exception list: " + e1);
}
}
}
});
}
catch (InterruptedException e) {
log.info("Caught: " + e, e);
}
try {
JMSException e = (JMSException) exceptionsList.poll(0);
if (e != null) {
throw e;
}
}
catch (InterruptedException e1) {
log.warn("Failed to remove element to exception list: " + e1);
}
}
else {
doAsyncSend(packet);
}
}
/**
* @return false
*/
public boolean isMulticast() {
return false;
}
/**
* reads packets from a Socket
*/
public void run() {
log.trace("TCP consumer thread starting");
int count = 0;
while (!isClosed()) {
if (isServerSide() && ++count > 500) {
count = 0;
Thread.yield();
}
try {
Packet packet = getWireFormat().readPacket(dataIn);
if (packet != null) {
doConsumePacket(packet);
}
}
catch (SocketTimeoutException e) {
//onAsyncException(JMSExceptionHelper.newJMSException(e));
}
catch (InterruptedIOException e) {
// TODO confirm that this really is a bug in the AS/400 JVM
// Patch for AS/400 JVM
// lets ignore these exceptions
// as they typically just indicate the thread was interupted
// while waiting for input, not that the socket is in error
//onAsyncException(JMSExceptionHelper.newJMSException(e));
}
catch (IOException e) {
doClose(e);
}
}
}
public boolean isClosed() {
return closed.get();
}
/**
* pretty print for object
*
* @return String representation of this object
*/
public String toString() {
return "TcpTransportChannel: " + socket;
}
/**
* @return the socket used by the TcpTransportChannel
*/
public Socket getSocket() {
return socket;
}
/**
* Can this wireformat process packets of this version
*
* @param version the version number to test
* @return true if can accept the version
*/
public boolean canProcessWireFormatVersion(int version) {
return getWireFormat().canProcessWireFormatVersion(version);
}
/**
* @return the current version of this wire format
*/
public int getCurrentWireFormatVersion() {
return getWireFormat().getCurrentWireFormatVersion();
}
// Properties
//-------------------------------------------------------------------------
/**
* @return true if packets are enqueued to a separate queue before dispatching
*/
public boolean isUseAsyncSend() {
return useAsyncSend;
}
/**
* set the useAsync flag
*
* @param useAsyncSend
*/
public void setUseAsyncSend(boolean useAsyncSend) {
this.useAsyncSend = useAsyncSend;
try {
if (useAsyncSend && executor==null ) {
PooledExecutor pe = new PooledExecutor(new BoundedBuffer(10), 1);
pe.waitWhenBlocked();
pe.setKeepAliveTime(1000);
executor = pe;
}
else if (!useAsyncSend && executor != null) {
stopExecutor(executor);
}
}
catch (Exception e) {
log.warn("problem closing executor", e);
}
}
/**
* @return the current so timeout used on the socket
*/
public int getSoTimeout() {
return soTimeout;
}
/**
* set the socket so timeout
*
* @param soTimeout
* @throws JMSException
*/
public void setSoTimeout(int soTimeout) throws JMSException {
this.soTimeout = soTimeout;
if (this.socket != null){
try {
socket.setSoTimeout(soTimeout);
}
catch (SocketException e) {
JMSException jmsEx = new JMSException("Failed to set soTimeout: ", e.getMessage());
jmsEx.setLinkedException(e);
throw jmsEx;
}
}
}
/**
* @param noDelay The noDelay to set.
*/
public void setNoDelay(boolean noDelay) {
super.setNoDelay(noDelay);
if (socket != null){
try {
socket.setTcpNoDelay(noDelay);
}
catch (SocketException e) {
log.warn("failed to set noDelay on the socket");//should never happen
}
}
}
/**
* @return Returns the socketBufferSize.
*/
public int getSocketBufferSize() {
return socketBufferSize;
}
/**
* @param socketBufferSize The socketBufferSize to set.
*/
public void setSocketBufferSize(int socketBufferSize) {
this.socketBufferSize = socketBufferSize;
}
// Implementation methods
//-------------------------------------------------------------------------
/**
* Actually performs the async send of a packet
*
* @param packet
* @return a response or null
* @throws JMSException
*/
protected Packet doAsyncSend(Packet packet) throws JMSException {
Packet response = null;
try {
synchronized (outboundLock) {
response = getWireFormat().writePacket(packet, dataOut);
dataOut.flush();
}
}
catch (IOException e) {
// if (closed.get()) {
// log.trace("Caught exception while closed: " + e, e);
// }
// else {
JMSException exception = JMSExceptionHelper.newJMSException("asyncSend failed: " + e, e);
onAsyncException(exception);
throw exception;
// }
}
catch (JMSException e) {
if (isClosed()) {
log.trace("Caught exception while closed: " + e, e);
}
else {
throw e;
}
}
return response;
}
protected void doClose(Exception ex) {
if (!isClosed()) {
if (!pendingStop) {
setPendingStop(true);
setTransportConnected(false);
if (ex instanceof EOFException) {
if (!isServerSide() && !isUsedInternally()){
log.warn("Peer closed connection", ex);
}
fireStatusEvent(new TransportStatusEvent(this,TransportStatusEvent.DISCONNECTED));
onAsyncException(JMSExceptionHelper.newJMSException("Error reading socket: " + ex, ex));
}
else {
fireStatusEvent(new TransportStatusEvent(this,TransportStatusEvent.DISCONNECTED));
onAsyncException(JMSExceptionHelper.newJMSException("Error reading socket: " + ex, ex));
}
}
stop();
}
}
/**
* Configures the socket for use
* @param sock
* @throws SocketException
*/
protected void initialiseSocket(Socket sock) throws SocketException {
try {
sock.setReceiveBufferSize(socketBufferSize);
sock.setSendBufferSize(socketBufferSize);
}
catch (SocketException se) {
log.debug("Cannot set socket buffer size = " + socketBufferSize, se);
}
sock.setSoTimeout(soTimeout);
sock.setTcpNoDelay(isNoDelay());
}
protected void initializeStreams() throws IOException{
BufferedInputStream buffIn = new BufferedInputStream(socket.getInputStream(),8192);
this.dataIn = new DataInputStream(buffIn);
TcpBufferedOutputStream buffOut = new TcpBufferedOutputStream(socket.getOutputStream(),8192);
this.dataOut = new DataOutputStream(buffOut);
}
protected void closeStreams() throws IOException {
if (dataOut != null) {
dataOut.close();
}
if (dataIn != null) {
dataIn.close();
}
}
/**
* Factory method to create a new socket
*
* @param remoteLocation the URI to connect to
* @return the newly created socket
* @throws UnknownHostException
* @throws IOException
*/
protected Socket createSocket(URI remoteLocation) throws UnknownHostException, IOException {
SocketAddress sockAddress = new InetSocketAddress(remoteLocation.getHost(), remoteLocation.getPort());
Socket sock = new Socket();
initialiseSocket(sock);
sock.connect(sockAddress);
return sock;
}
/**
* Factory method to create a new socket
*
* @param remoteLocation
* @param localLocation
* @return @throws IOException
* @throws IOException
* @throws UnknownHostException
*/
protected Socket createSocket(URI remoteLocation, URI localLocation) throws IOException, UnknownHostException {
SocketAddress sockAddress = new InetSocketAddress(remoteLocation.getHost(), remoteLocation.getPort());
SocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(localLocation.getHost()), localLocation.getPort());
Socket sock = new Socket();
initialiseSocket(sock);
sock.bind(localAddress);
sock.connect(sockAddress);
return sock;
}
}