View Javadoc

1   /*
2    * $Id: TcpConnector.java 20570 2010-12-09 20:04:42Z aperepel $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.transport.tcp;
12  
13  import org.mule.api.MessagingException;
14  import org.mule.api.MuleContext;
15  import org.mule.api.MuleException;
16  import org.mule.api.MuleMessage;
17  import org.mule.api.endpoint.ImmutableEndpoint;
18  import org.mule.api.lifecycle.InitialisationException;
19  import org.mule.api.transport.Connector;
20  import org.mule.api.transport.MessageDispatcherFactory;
21  import org.mule.config.i18n.CoreMessages;
22  import org.mule.model.streaming.CallbackOutputStream;
23  import org.mule.transport.AbstractConnector;
24  import org.mule.transport.ConfigurableKeyedObjectPool;
25  import org.mule.transport.tcp.protocols.SafeProtocol;
26  import org.mule.util.concurrent.ThreadNameHelper;
27  import org.mule.util.monitor.ExpiryMonitor;
28  
29  import java.io.BufferedOutputStream;
30  import java.io.DataOutputStream;
31  import java.io.IOException;
32  import java.io.OutputStream;
33  import java.net.ServerSocket;
34  import java.net.Socket;
35  import java.net.SocketException;
36  import java.net.URI;
37  
38  import org.apache.commons.pool.impl.GenericKeyedObjectPool;
39  
40  /**
41   * <code>TcpConnector</code> can bind or sent to a given TCP port on a given host.
42   * Other socket-based transports can be built on top of this class by providing the
43   * appropriate socket factories and application level protocols as required (see
44   * the constructor and the SSL transport for examples).
45   */
46  public class TcpConnector extends AbstractConnector
47  {
48      public static final String TCP = "tcp";
49  
50      /** Property can be set on the endpoint to configure how the socket is managed */
51      public static final String KEEP_SEND_SOCKET_OPEN_PROPERTY = "keepSendSocketOpen";
52      public static final int DEFAULT_SOCKET_TIMEOUT = INT_VALUE_NOT_SET;
53      public static final int DEFAULT_SO_LINGER = INT_VALUE_NOT_SET;
54      public static final int DEFAULT_BUFFER_SIZE = INT_VALUE_NOT_SET;
55      public static final int DEFAULT_BACKLOG = INT_VALUE_NOT_SET;
56  
57      // to clarify arg to configureSocket
58      public static final boolean SERVER = false;
59      public static final boolean CLIENT = true;
60  
61      private int clientSoTimeout = DEFAULT_SOCKET_TIMEOUT;
62      private int serverSoTimeout = DEFAULT_SOCKET_TIMEOUT;
63      private int sendBufferSize = DEFAULT_BUFFER_SIZE;
64      private int receiveBufferSize = DEFAULT_BUFFER_SIZE;
65      private int receiveBacklog = DEFAULT_BACKLOG;
66      private boolean sendTcpNoDelay;
67      private Boolean reuseAddress = Boolean.TRUE; // this could be null for Java default
68      private int socketSoLinger = DEFAULT_SO_LINGER;
69      private TcpProtocol tcpProtocol;
70      private AbstractTcpSocketFactory socketFactory;
71      private SimpleServerSocketFactory serverSocketFactory;
72      private GenericKeyedObjectPool socketsPool = new GenericKeyedObjectPool();
73      private int keepAliveTimeout = 0;
74      private ExpiryMonitor keepAliveMonitor;
75  
76      /** 
77       * If set, the socket is not closed after sending a message.  This attribute 
78       * only applies when sending data over a socket (Client).
79       */
80      private boolean keepSendSocketOpen = false;
81  
82      /**
83       * Enables SO_KEEPALIVE behavior on open sockets. This automatically checks 
84       * socket connections that are open but unused for long periods and closes 
85       * them if the connection becomes unavailable.  This is a property on the 
86       * socket itself and is used by a server socket to control whether 
87       * connections to the server are kept alive before they are recycled.
88       */
89      private boolean keepAlive = false;
90  
91      //TODO MULE-2300 remove once fixed
92      private TcpSocketKey lastSocketKey;
93  
94      public TcpConnector(MuleContext context)
95      {
96          super(context);
97          setSocketFactory(new TcpSocketFactory());
98          setServerSocketFactory(new TcpServerSocketFactory());
99          setTcpProtocol(new SafeProtocol());
100     }
101 
102     public void configureSocket(boolean client, Socket socket) throws SocketException
103     {
104         // There is some overhead in setting socket timeout and buffer size, so we're
105         // careful here only to set if needed
106 
107         if (newValue(getReceiveBufferSize(), socket.getReceiveBufferSize()))
108         {
109             socket.setReceiveBufferSize(getReceiveBufferSize());
110         }
111         if (newValue(getSendBufferSize(), socket.getSendBufferSize()))
112         {
113             socket.setSendBufferSize(getSendBufferSize());
114         }
115         if (client)
116         {
117             if (newValue(getClientSoTimeout(), socket.getSoTimeout()))
118             {
119                 socket.setSoTimeout(getClientSoTimeout());
120             }
121         }
122         else
123         {
124             if (newValue(getServerSoTimeout(), socket.getSoTimeout()))
125             {
126                 socket.setSoTimeout(getServerSoTimeout());
127             }
128         }
129         if (newValue(getSocketSoLinger(), socket.getSoLinger()))
130         {
131             socket.setSoLinger(true, getSocketSoLinger());
132         }
133         try
134         {
135             socket.setTcpNoDelay(isSendTcpNoDelay());
136         }
137         catch (SocketException e)
138         {
139             // MULE-2800 - Bug in Solaris
140         }
141         socket.setKeepAlive(isKeepAlive());
142     }
143 
144     private boolean newValue(int parameter, int socketValue)
145     {
146         return parameter != Connector.INT_VALUE_NOT_SET && parameter != socketValue;
147     }
148 
149     @Override
150     protected void doInitialise() throws InitialisationException
151     {
152         socketsPool.setFactory(getSocketFactory());
153         socketsPool.setTestOnBorrow(true);
154         socketsPool.setTestOnReturn(true);
155         //There should only be one pooled instance per socket (key)
156         socketsPool.setMaxActive(1);
157         socketsPool.setWhenExhaustedAction(GenericKeyedObjectPool.WHEN_EXHAUSTED_BLOCK);
158 
159         // Use connector's classloader so that other temporary classloaders
160         // aren't used when things are started lazily or from elsewhere.
161         final String monitorName = String.format("%s%s.socket",
162                                                  ThreadNameHelper.getPrefix(muleContext),
163                                                  getName());
164         keepAliveMonitor = new ExpiryMonitor(monitorName, 1000, this.getClass().getClassLoader());
165     }
166 
167     @Override
168     protected void doDispose()
169     {
170         logger.debug("Closing TCP connector");
171         try
172         {
173             socketsPool.close();
174         }
175         catch (Exception e)
176         {
177             logger.warn("Failed to close dispatcher socket pool: " + e.getMessage());
178         }
179         
180         keepAliveMonitor.dispose();
181     }
182 
183     /**
184      * Lookup a socket in the list of dispatcher sockets but don't create a new
185      * socket
186      */
187     protected Socket getSocket(ImmutableEndpoint endpoint) throws Exception
188     {
189         TcpSocketKey socketKey = new TcpSocketKey(endpoint);
190         if (logger.isDebugEnabled())
191         {
192             logger.debug("borrowing socket for " + socketKey + "/" + socketKey.hashCode());
193             if (null != lastSocketKey)
194             {
195                 logger.debug("same as " + lastSocketKey.hashCode() + "? " + lastSocketKey.equals(socketKey));
196             }
197         }
198         Socket socket = (Socket) socketsPool.borrowObject(socketKey);
199         if (logger.isDebugEnabled())
200         {
201             logger.debug("borrowed socket, "
202                     + (socket.isClosed() ? "closed" : "open") 
203                     + "; debt " + socketsPool.getNumActive());
204         }
205         return socket;
206     }
207 
208     void releaseSocket(Socket socket, ImmutableEndpoint endpoint) throws Exception
209     {
210         TcpSocketKey socketKey = new TcpSocketKey(endpoint);
211         lastSocketKey = socketKey;
212         socketsPool.returnObject(socketKey, socket);
213         if (logger.isDebugEnabled())
214         {
215             logger.debug("returning socket for " + socketKey.hashCode());
216             logger.debug("returned socket; debt " + socketsPool.getNumActive());
217         }
218     }
219 
220     public OutputStream getOutputStream(final ImmutableEndpoint endpoint, MuleMessage message)
221             throws MuleException
222     {
223         final Socket socket;
224         try
225         {
226             socket = getSocket(endpoint);
227         }
228         catch (Exception e)
229         {
230             throw new MessagingException(CoreMessages.failedToGetOutputStream(), message, e);
231         }
232         if (socket == null)
233         {
234             // This shouldn't happen
235             throw new IllegalStateException("could not get socket for endpoint: "
236                     + endpoint.getEndpointURI().getAddress());
237         }
238         try
239         {
240             return new CallbackOutputStream(
241                     new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())),
242                     new CallbackOutputStream.Callback()
243                     {
244                         public void onClose() throws Exception
245                         {
246                             releaseSocket(socket, endpoint);
247                         }
248                     });
249         }
250         catch (IOException e)
251         {
252             throw new MessagingException(CoreMessages.failedToGetOutputStream(), message, e);
253         }
254     }
255 
256     @Override
257     protected void doConnect() throws Exception
258     {
259         // template method
260     }
261 
262     @Override
263     protected void doDisconnect() throws Exception
264     {
265         socketsPool.clear();
266     }
267 
268     @Override
269     protected void doStart() throws MuleException
270     {
271         // template method
272     }
273 
274     @Override
275     protected void doStop() throws MuleException
276     {
277         // template method
278     }
279 
280     public String getProtocol()
281     {
282         return TCP;
283     }
284 
285     // getters and setters ---------------------------------------------------------
286 
287     public boolean isKeepSendSocketOpen()
288     {
289         return keepSendSocketOpen;
290     }
291 
292     public void setKeepSendSocketOpen(boolean keepSendSocketOpen)
293     {
294         this.keepSendSocketOpen = keepSendSocketOpen;
295     }
296 
297     /**
298      * A shorthand property setting timeout for both SEND and RECEIVE sockets.
299      *
300      * @deprecated The time out should be set explicitly for each
301      */
302     @Deprecated
303     public void setTimeout(int timeout)
304     {
305         setClientSoTimeout(timeout);
306         setServerSoTimeout(timeout);
307     }
308 
309     public int getClientSoTimeout()
310     {
311         return this.clientSoTimeout;
312     }
313 
314     public void setClientSoTimeout(int timeout)
315     {
316         this.clientSoTimeout = valueOrDefault(timeout, 0, DEFAULT_SOCKET_TIMEOUT);
317     }
318 
319     public int getServerSoTimeout()
320     {
321         return serverSoTimeout;
322     }
323 
324     public void setServerSoTimeout(int timeout)
325     {
326         this.serverSoTimeout = valueOrDefault(timeout, 0, DEFAULT_SOCKET_TIMEOUT);
327     }
328 
329     /** @deprecated Should use {@link #getSendBufferSize()} or {@link #getReceiveBufferSize()} */
330     @Deprecated
331     public int getBufferSize()
332     {
333         return sendBufferSize;
334     }
335 
336     /** @deprecated Should use {@link #setSendBufferSize(int)} or {@link #setReceiveBufferSize(int)} */
337     @Deprecated
338     public void setBufferSize(int bufferSize)
339     {
340         sendBufferSize = valueOrDefault(bufferSize, 1, DEFAULT_BUFFER_SIZE);
341     }
342 
343     public int getSendBufferSize()
344     {
345         return sendBufferSize;
346     }
347 
348     public void setSendBufferSize(int bufferSize)
349     {
350         sendBufferSize = valueOrDefault(bufferSize, 1, DEFAULT_BUFFER_SIZE);
351     }
352 
353     public int getReceiveBufferSize()
354     {
355         return receiveBufferSize;
356     }
357 
358     public void setReceiveBufferSize(int bufferSize)
359     {
360         receiveBufferSize = valueOrDefault(bufferSize, 1, DEFAULT_BUFFER_SIZE);
361     }
362 
363     public int getReceiveBacklog()
364     {
365         return receiveBacklog;
366     }
367 
368     public void setReceiveBacklog(int receiveBacklog)
369     {
370         this.receiveBacklog = valueOrDefault(receiveBacklog, 0, DEFAULT_BACKLOG);
371     }
372 
373     public int getSocketSoLinger()
374     {
375         return socketSoLinger;
376     }
377 
378     public void setSocketSoLinger(int soLinger)
379     {
380         this.socketSoLinger = valueOrDefault(soLinger, 0, INT_VALUE_NOT_SET);
381     }
382 
383     /**
384      * @deprecated should use {@link #getReceiveBacklog()}
385      */
386     @Deprecated
387     public int getBacklog()
388     {
389         return receiveBacklog;
390     }
391 
392     /**
393      * @param backlog
394      * @deprecated should use {@link #setReceiveBacklog(int)}
395      */
396     @Deprecated
397     public void setBacklog(int backlog)
398     {
399         this.receiveBacklog = backlog;
400     }
401 
402     public TcpProtocol getTcpProtocol()
403     {
404         return tcpProtocol;
405     }
406 
407     public void setTcpProtocol(TcpProtocol tcpProtocol)
408     {
409         this.tcpProtocol = tcpProtocol;
410     }
411 
412     @Override
413     public boolean isResponseEnabled()
414     {
415         return true;
416     }
417 
418     public boolean isKeepAlive()
419     {
420         return keepAlive;
421     }
422 
423     public void setKeepAlive(boolean keepAlive)
424     {
425         this.keepAlive = keepAlive;
426     }
427 
428     public boolean isSendTcpNoDelay()
429     {
430         return sendTcpNoDelay;
431     }
432 
433     public void setSendTcpNoDelay(boolean sendTcpNoDelay)
434     {
435         this.sendTcpNoDelay = sendTcpNoDelay;
436     }
437 
438     protected void setSocketFactory(AbstractTcpSocketFactory socketFactory)
439     {
440         this.socketFactory = socketFactory;
441     }
442 
443     protected AbstractTcpSocketFactory getSocketFactory()
444     {
445         return socketFactory;
446     }
447 
448     public SimpleServerSocketFactory getServerSocketFactory()
449     {
450         return serverSocketFactory;
451     }
452 
453     public void setServerSocketFactory(SimpleServerSocketFactory serverSocketFactory)
454     {
455         this.serverSocketFactory = serverSocketFactory;
456     }
457 
458     protected ServerSocket getServerSocket(URI uri) throws IOException
459     {
460         return getServerSocketFactory().createServerSocket(uri, getReceiveBacklog(), isReuseAddress());
461     }
462 
463     private static int valueOrDefault(int value, int threshhold, int deflt)
464     {
465         if (value < threshhold)
466         {
467             return deflt;
468         }
469         else
470         {
471             return value;
472         }
473     }
474 
475     /**
476      * @return true if the server socket sets SO_REUSEADDRESS before opening
477      */
478     public Boolean isReuseAddress()
479     {
480         return reuseAddress;
481     }
482 
483     /**
484      * This allows closed sockets to be reused while they are still in TIME_WAIT state
485      *
486      * @param reuseAddress Whether the server socket sets SO_REUSEADDRESS before opening
487      */
488     public void setReuseAddress(Boolean reuseAddress)
489     {
490         this.reuseAddress = reuseAddress;
491     }
492 
493     public ExpiryMonitor getKeepAliveMonitor()
494     {
495         return keepAliveMonitor;
496     }
497     
498     /**
499      * @return keep alive timeout in Milliseconds
500      */
501     public int getKeepAliveTimeout()
502     {
503         return keepAliveTimeout;
504     }
505     
506     /**
507      * Sets the keep alive timeout (in Milliseconds)
508      */
509     public void setKeepAliveTimeout(int keepAliveTimeout)
510     {
511         this.keepAliveTimeout = keepAliveTimeout;
512     }
513     
514     @Override
515     public void setDispatcherFactory(MessageDispatcherFactory dispatcherFactory)
516     {
517         if (this.dispatcherFactory == null) {
518             super.setDispatcherFactory(dispatcherFactory);
519         }
520     }
521 
522     public ConfigurableKeyedObjectPool getDispatchers()
523     {
524         return dispatchers;
525     }
526 }