View Javadoc

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