001    /** 
002     * 
003     * Copyright 2004 Protique Ltd
004     * 
005     * Licensed under the Apache License, Version 2.0 (the "License"); 
006     * you may not use this file except in compliance with the License. 
007     * You may obtain a copy of the License at 
008     * 
009     * http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS, 
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
014     * See the License for the specific language governing permissions and 
015     * limitations under the License. 
016     * 
017     **/
018    
019    package org.activemq.transport.tcp;
020    
021    import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
022    import EDU.oswego.cs.dl.util.concurrent.BoundedChannel;
023    import EDU.oswego.cs.dl.util.concurrent.BoundedLinkedQueue;
024    import EDU.oswego.cs.dl.util.concurrent.Executor;
025    import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
026    import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    import org.activemq.io.WireFormat;
030    import org.activemq.io.WireFormatLoader;
031    import org.activemq.message.Packet;
032    import org.activemq.transport.TransportChannelSupport;
033    import org.activemq.transport.TransportStatusEvent;
034    import org.activemq.util.JMSExceptionHelper;
035    
036    import javax.jms.JMSException;
037    import java.io.BufferedInputStream;
038    import java.io.DataInputStream;
039    import java.io.DataOutputStream;
040    import java.io.EOFException;
041    import java.io.IOException;
042    import java.io.InterruptedIOException;
043    import java.net.InetAddress;
044    import java.net.InetSocketAddress;
045    import java.net.Socket;
046    import java.net.SocketAddress;
047    import java.net.SocketException;
048    import java.net.SocketTimeoutException;
049    import java.net.URI;
050    import java.net.UnknownHostException;
051    
052    /**
053     * A tcp implementation of a TransportChannel
054     *
055     * @version $Revision: 1.2 $
056     */
057    public class TcpTransportChannel extends TransportChannelSupport implements Runnable {
058        private static final int DEFAULT_SOCKET_BUFFER_SIZE = 64 * 1024;
059        private static final Log log = LogFactory.getLog(TcpTransportChannel.class);
060        protected Socket socket;
061        protected DataOutputStream dataOut;
062        protected DataInputStream dataIn;
063    
064        private WireFormatLoader wireFormatLoader;
065        private SynchronizedBoolean closed;
066        private SynchronizedBoolean started;
067        private Object outboundLock;
068        private Executor executor;
069        private Thread thread;
070        private boolean useAsyncSend = false;
071        private int soTimeout = 10000;
072        private int socketBufferSize = DEFAULT_SOCKET_BUFFER_SIZE;
073        private BoundedChannel exceptionsList;
074        private TcpTransportServerChannel serverChannel;
075    
076        /**
077         * Construct basic helpers
078         *
079         * @param wireFormat
080         */
081        protected TcpTransportChannel(WireFormat wireFormat) {
082            super(wireFormat);
083            this.wireFormatLoader = new WireFormatLoader(wireFormat);
084            closed = new SynchronizedBoolean(false);
085            started = new SynchronizedBoolean(false);
086            // there's not much point logging all exceptions, lets just keep a few around
087            exceptionsList = new BoundedLinkedQueue(10);
088            outboundLock = new Object();
089            setUseAsyncSend(useAsyncSend);
090            super.setCachingEnabled(true);
091        }
092    
093        /**
094         * Connect to a remote Node - e.g. a Broker
095         *
096         * @param wireFormat
097         * @param remoteLocation
098         * @throws JMSException
099         */
100        public TcpTransportChannel(WireFormat wireFormat, URI remoteLocation) throws JMSException {
101            this(wireFormat);
102            try {
103                this.socket = createSocket(remoteLocation);
104                initializeStreams();
105            }
106            catch (Exception ioe) {
107                throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed. " + "URI was: "
108                        + remoteLocation + " Reason: " + ioe, ioe);
109            }
110        }
111    
112        /**
113         * Connect to a remote Node - e.g. a Broker
114         *
115         * @param wireFormat
116         * @param remoteLocation
117         * @param localLocation  - e.g. local InetAddress and local port
118         * @throws JMSException
119         */
120        public TcpTransportChannel(WireFormat wireFormat, URI remoteLocation, URI localLocation) throws JMSException {
121            this(wireFormat);
122            try {
123                this.socket = createSocket(remoteLocation, localLocation);
124                initializeStreams();
125            }
126            catch (Exception ioe) {
127                throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
128            }
129        }
130    
131       /**
132        * Initialize from a ServerSocket
133        * @param serverChannel
134        * @param wireFormat
135        * @param socket
136        * @param executor
137        * @throws JMSException
138        */
139        public TcpTransportChannel(TcpTransportServerChannel serverChannel,WireFormat wireFormat, Socket socket, Executor executor) throws JMSException {
140            this(wireFormat);
141            this.socket = socket;
142            this.executor = executor;
143            this.serverChannel = serverChannel;
144            setServerSide(true);
145            try {
146                initialiseSocket(socket);
147                initializeStreams();
148            }
149            catch (IOException ioe) {
150                throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
151            }
152        }
153    
154        public TcpTransportChannel(WireFormat wireFormat, Socket socket, Executor executor) throws JMSException {
155            this(wireFormat);
156            this.socket = socket;
157            this.executor = executor;
158            try {
159                initialiseSocket(socket);
160                initializeStreams();
161            }
162            catch (IOException ioe) {
163                throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
164            }
165        }
166    
167        /**
168         * start listeneing for events
169         *
170         * @throws JMSException if an error occurs
171         */
172        public void start() throws JMSException {
173            if (started.commit(false, true)) {
174                thread = new Thread(this, toString());
175                try {
176                    if (isServerSide()) {
177                        thread.setDaemon(true);
178                        WireFormat wf = wireFormatLoader.getWireFormat(dataIn);
179                        if (wf != null) {
180                            setWireFormat(wf);
181                        }
182                        getWireFormat().registerTransportStreams(dataOut, dataIn);
183                        getWireFormat().initiateServerSideProtocol();
184                    }
185                    else {
186                        getWireFormat().registerTransportStreams(dataOut, dataIn);
187                        thread.setPriority(Thread.NORM_PRIORITY + 2);
188                    }
189                    //enable caching on the wire format
190                    currentWireFormat.setCachingEnabled(isCachingEnabled());
191                    thread.start();
192                    //send the wire format
193                    if (!isServerSide()) {
194                        getWireFormat().initiateClientSideProtocol();
195                    }
196                                    fireStatusEvent(new TransportStatusEvent(this,TransportStatusEvent.CONNECTED));
197                }
198                catch (EOFException e) {
199                    doClose(e);
200                }
201                catch (IOException e) {
202                    JMSException jmsEx = new JMSException("start failed: " + e.getMessage());
203                    jmsEx.initCause(e);
204                    jmsEx.setLinkedException(e);
205                    throw jmsEx;
206                }
207            }
208        }
209    
210        /**
211         * close the channel
212         */
213        public void stop() {
214            if (closed.commit(false, true)) {
215                super.stop();
216                try {
217                    if (executor != null) {
218                        stopExecutor(executor);
219                    }
220                    closeStreams();
221                    socket.close();
222                }
223                catch (Exception e) {
224                    log.warn("Caught while closing: " + e + ". Now Closed", e);
225                }
226            }
227            closed.set(true);
228            if (this.serverChannel != null){
229                this.serverChannel.removeClient(this);
230            }
231        }
232    
233        public void forceDisconnect() {
234            log.debug("Forcing disconnect");
235            if (socket != null && socket.isConnected()) {
236                try {
237                    socket.close();
238                }
239                catch (IOException e) {
240                    // Ignore
241                }
242            }
243        }
244    
245        /**
246         * Asynchronously send a Packet
247         *
248         * @param packet
249         * @throws JMSException
250         */
251        public void asyncSend(final Packet packet) throws JMSException {
252            if (executor != null) {
253                try {
254                    executor.execute(new Runnable() {
255                        public void run() {
256                            try {
257                                if (!isClosed()) {
258                                    doAsyncSend(packet);
259                                }
260                            }
261                            catch (JMSException e) {
262                                try {
263                                    exceptionsList.put(e);
264                                }
265                                catch (InterruptedException e1) {
266                                    log.warn("Failed to add element to exception list: " + e1);
267                                }
268                            }
269                        }
270                    });
271                }
272                catch (InterruptedException e) {
273                    log.info("Caught: " + e, e);
274                }
275                try {
276                    JMSException e = (JMSException) exceptionsList.poll(0);
277                    if (e != null) {
278                        throw e;
279                    }
280                }
281                catch (InterruptedException e1) {
282                    log.warn("Failed to remove element to exception list: " + e1);
283                }
284            }
285            else {
286                doAsyncSend(packet);
287            }
288        }
289    
290        /**
291         * @return false
292         */
293        public boolean isMulticast() {
294            return false;
295        }
296    
297        /**
298         * reads packets from a Socket
299         */
300        public void run() {
301            log.trace("TCP consumer thread starting");
302            int count = 0;
303            while (!isClosed()) {
304                if (isServerSide() && ++count > 500) {
305                    count = 0;
306                    Thread.yield();
307                }
308                try {
309                    Packet packet = getWireFormat().readPacket(dataIn);
310                    if (packet != null) {
311                        doConsumePacket(packet);
312                    }
313                }
314                catch (SocketTimeoutException e) {
315                    //onAsyncException(JMSExceptionHelper.newJMSException(e));
316                }
317                catch (InterruptedIOException e) {
318                    // TODO confirm that this really is a bug in the AS/400 JVM
319                    // Patch for AS/400 JVM
320                    // lets ignore these exceptions
321                    // as they typically just indicate the thread was interupted
322                    // while waiting for input, not that the socket is in error
323                    //onAsyncException(JMSExceptionHelper.newJMSException(e));
324                }
325                catch (IOException e) {
326                    doClose(e);
327                }
328            }
329        }
330    
331        public boolean isClosed() {
332            return closed.get();
333        }
334    
335        /**
336         * pretty print for object
337         *
338         * @return String representation of this object
339         */
340        public String toString() {
341            return "TcpTransportChannel: " + socket;
342        }
343    
344        /**
345         * @return the socket used by the TcpTransportChannel
346         */
347        public Socket getSocket() {
348            return socket;
349        }
350    
351        /**
352         * Can this wireformat process packets of this version
353         *
354         * @param version the version number to test
355         * @return true if can accept the version
356         */
357        public boolean canProcessWireFormatVersion(int version) {
358            return getWireFormat().canProcessWireFormatVersion(version);
359        }
360    
361        /**
362         * @return the current version of this wire format
363         */
364        public int getCurrentWireFormatVersion() {
365            return getWireFormat().getCurrentWireFormatVersion();
366        }
367    
368        // Properties
369        //-------------------------------------------------------------------------
370    
371        /**
372         * @return true if packets are enqueued to a separate queue before dispatching
373         */
374        public boolean isUseAsyncSend() {
375            return useAsyncSend;
376        }
377    
378        /**
379         * set the useAsync flag
380         *
381         * @param useAsyncSend
382         */    
383         public void setUseAsyncSend(boolean useAsyncSend) {
384            this.useAsyncSend = useAsyncSend;
385            try {
386                if (useAsyncSend && executor==null ) {
387                    PooledExecutor pe = new PooledExecutor(new BoundedBuffer(10), 1);
388                    pe.waitWhenBlocked();
389                    pe.setKeepAliveTime(1000);
390                    executor = pe;
391                }
392                else if (!useAsyncSend && executor != null) {
393                    stopExecutor(executor);
394                }
395            }
396            catch (Exception e) {
397                log.warn("problem closing executor", e);
398            }
399        }
400    
401        
402    
403        /**
404         * @return the current so timeout used on the socket
405         */
406        public int getSoTimeout() {
407            return soTimeout;
408        }
409    
410        /**
411         * set the socket so timeout
412         *
413         * @param soTimeout
414         * @throws JMSException
415         */
416        public void setSoTimeout(int soTimeout) throws JMSException {
417            this.soTimeout = soTimeout;
418            if (this.socket != null){
419                try {
420                    socket.setSoTimeout(soTimeout);
421                }
422                catch (SocketException e) {
423                    JMSException jmsEx = new JMSException("Failed to set soTimeout: ", e.getMessage());
424                    jmsEx.setLinkedException(e);
425                    throw jmsEx;
426                }
427            }
428        }
429        
430        /**
431         * @param noDelay The noDelay to set.
432         */
433        public void setNoDelay(boolean noDelay) {
434            super.setNoDelay(noDelay);
435            if (socket != null){
436                try {
437                    socket.setTcpNoDelay(noDelay);
438                }
439                catch (SocketException e) {
440                   log.warn("failed to set noDelay on the socket");//should never happen
441                }
442            }
443        }
444    
445        /**
446         * @return Returns the socketBufferSize.
447         */
448        public int getSocketBufferSize() {
449            return socketBufferSize;
450        }
451        /**
452         * @param socketBufferSize The socketBufferSize to set.
453         */
454        public void setSocketBufferSize(int socketBufferSize) {
455            this.socketBufferSize = socketBufferSize;
456        }
457        // Implementation methods
458        //-------------------------------------------------------------------------
459        /**
460         * Actually performs the async send of a packet
461         *
462         * @param packet
463         * @return a response or null
464         * @throws JMSException
465         */
466        protected Packet doAsyncSend(Packet packet) throws JMSException {
467            Packet response = null;
468            try {
469                synchronized (outboundLock) {
470                    response = getWireFormat().writePacket(packet, dataOut);
471                    dataOut.flush();
472                }
473            }
474            catch (IOException e) {
475    //            if (closed.get()) {
476    //                log.trace("Caught exception while closed: " + e, e);
477    //            }
478    //            else {
479                    JMSException exception = JMSExceptionHelper.newJMSException("asyncSend failed: " + e, e);
480                    onAsyncException(exception);
481                    throw exception;
482    //            }
483            }
484            catch (JMSException e) {
485                if (isClosed()) {
486                    log.trace("Caught exception while closed: " + e, e);
487                }
488                else {
489                    throw e;
490                }
491            }
492            return response;
493        }
494    
495        protected void doClose(Exception ex) {
496            if (!isClosed()) {
497                if (!pendingStop) {
498                    setPendingStop(true);
499                    setTransportConnected(false);
500                    if (ex instanceof EOFException) {
501                        if (!isServerSide() && !isUsedInternally()){
502                            log.warn("Peer closed connection", ex);
503                        }
504                        fireStatusEvent(new TransportStatusEvent(this,TransportStatusEvent.DISCONNECTED));
505                        onAsyncException(JMSExceptionHelper.newJMSException("Error reading socket: " + ex, ex));
506                    }
507                    else {
508                        fireStatusEvent(new TransportStatusEvent(this,TransportStatusEvent.DISCONNECTED));
509                        onAsyncException(JMSExceptionHelper.newJMSException("Error reading socket: " + ex, ex));
510                    }
511                }
512                stop();
513            }
514        }
515    
516        /**
517         * Configures the socket for use
518         * @param sock
519         * @throws SocketException
520         */
521        protected void initialiseSocket(Socket sock) throws SocketException {
522            try {
523                sock.setReceiveBufferSize(socketBufferSize);
524                sock.setSendBufferSize(socketBufferSize);
525            }
526            catch (SocketException se) {
527                log.debug("Cannot set socket buffer size = " + socketBufferSize, se);
528            }
529            sock.setSoTimeout(soTimeout);
530            sock.setTcpNoDelay(isNoDelay());
531        }
532        
533        protected void initializeStreams() throws IOException{
534            BufferedInputStream buffIn = new BufferedInputStream(socket.getInputStream(),8192);
535            this.dataIn = new DataInputStream(buffIn);
536            TcpBufferedOutputStream buffOut = new TcpBufferedOutputStream(socket.getOutputStream(),8192);
537            this.dataOut = new DataOutputStream(buffOut);
538        }
539    
540        protected void closeStreams() throws IOException {
541            if (dataOut != null) {
542                dataOut.close();
543            }
544            if (dataIn != null) {
545                dataIn.close();
546            }
547        }
548    
549        /**
550         * Factory method to create a new socket
551         *
552         * @param remoteLocation the URI to connect to
553         * @return the newly created socket
554         * @throws UnknownHostException
555         * @throws IOException
556         */
557        protected Socket createSocket(URI remoteLocation) throws UnknownHostException, IOException {
558            SocketAddress sockAddress = new InetSocketAddress(remoteLocation.getHost(), remoteLocation.getPort());
559            Socket sock = new Socket();
560            initialiseSocket(sock);
561            sock.connect(sockAddress);
562            return sock;
563        }
564    
565        /**
566         * Factory method to create a new socket
567         *
568         * @param remoteLocation
569         * @param localLocation
570         * @return @throws IOException
571         * @throws IOException
572         * @throws UnknownHostException
573         */
574        protected Socket createSocket(URI remoteLocation, URI localLocation) throws IOException, UnknownHostException {
575            SocketAddress sockAddress = new InetSocketAddress(remoteLocation.getHost(), remoteLocation.getPort());
576            SocketAddress localAddress = new InetSocketAddress(InetAddress.getByName(localLocation.getHost()), localLocation.getPort());
577            Socket sock = new Socket();
578            initialiseSocket(sock);
579            sock.bind(localAddress);
580            sock.connect(sockAddress);
581            return sock;
582        }
583    
584    }