View Javadoc

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