View Javadoc

1   /*
2    * $Id: TcpMessageReceiver.java 21939 2011-05-18 13:32:09Z 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  package org.mule.transport.tcp;
11  
12  import org.mule.api.MuleException;
13  import org.mule.api.MuleMessage;
14  import org.mule.api.config.MuleProperties;
15  import org.mule.api.construct.FlowConstruct;
16  import org.mule.api.endpoint.InboundEndpoint;
17  import org.mule.api.lifecycle.CreateException;
18  import org.mule.api.lifecycle.Disposable;
19  import org.mule.api.lifecycle.DisposeException;
20  import org.mule.api.retry.RetryCallback;
21  import org.mule.api.retry.RetryContext;
22  import org.mule.api.transaction.Transaction;
23  import org.mule.api.transaction.TransactionException;
24  import org.mule.api.transport.Connector;
25  import org.mule.config.i18n.CoreMessages;
26  import org.mule.transport.AbstractMessageReceiver;
27  import org.mule.transport.AbstractReceiverResourceWorker;
28  import org.mule.transport.ConnectException;
29  import org.mule.transport.tcp.i18n.TcpMessages;
30  import org.mule.util.monitor.Expirable;
31  
32  import java.io.BufferedInputStream;
33  import java.io.BufferedOutputStream;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.OutputStream;
37  import java.net.ServerSocket;
38  import java.net.Socket;
39  import java.net.SocketAddress;
40  import java.net.SocketTimeoutException;
41  import java.net.URI;
42  import java.util.Iterator;
43  import java.util.List;
44  import java.util.concurrent.TimeUnit;
45  import java.util.concurrent.atomic.AtomicBoolean;
46  
47  import javax.resource.spi.work.Work;
48  import javax.resource.spi.work.WorkException;
49  import javax.resource.spi.work.WorkManager;
50  
51  /**
52   * <code>TcpMessageReceiver</code> acts like a TCP server to receive socket
53   * requests.
54   */
55  public class TcpMessageReceiver extends AbstractMessageReceiver implements Work
56  {
57      private ServerSocket serverSocket = null;
58  
59      protected final AtomicBoolean disposing = new AtomicBoolean(false);
60      
61      public TcpMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
62              throws CreateException
63      {
64          super(connector, flowConstruct, endpoint);
65      }
66  
67      @Override
68      protected void doConnect() throws ConnectException
69      {
70          disposing.set(false);
71  
72          URI uri = endpoint.getEndpointURI().getUri();
73  
74          try
75          {
76              serverSocket = ((TcpConnector) connector).getServerSocket(uri);
77          }
78          catch (Exception e)
79          {
80              throw new ConnectException(TcpMessages.failedToBindToUri(uri), e, this);
81          }
82  
83          try
84          {
85              getWorkManager().scheduleWork(this, WorkManager.INDEFINITE, null, connector);
86          }
87          catch (WorkException e)
88          {
89              throw new ConnectException(CoreMessages.failedToScheduleWork(), e, this);
90          }
91      }
92  
93      @Override
94      protected void doDisconnect() throws ConnectException
95      {
96          // this will cause the server thread to quit
97          disposing.set(true);
98  
99          try
100         {
101             if (serverSocket != null)
102             {
103                 if (logger.isDebugEnabled())
104                 {
105                     logger.debug("Closing: " + serverSocket);
106                 }
107                 serverSocket.close();
108             }
109         }
110         catch (IOException e)
111         {
112             logger.warn("Failed to close server socket: " + e.getMessage(), e);
113         }
114     }
115 
116     @Override
117     protected void doStart() throws MuleException
118     {
119         // nothing to do
120     }
121 
122     @Override
123     protected void doStop() throws MuleException
124     {
125         // nothing to do
126     }
127 
128     /**
129      * Obtain the serverSocket
130      * @return the server socket for this server
131      */
132     public ServerSocket getServerSocket()
133     {
134         return serverSocket;
135     }
136 
137     public void run()
138     {
139         while (!disposing.get())
140         {
141             if (connector.isStarted() && !disposing.get())
142             {
143                 try
144                 {
145                     retryTemplate.execute(new RetryCallback()
146                     {
147                         public void doWork(RetryContext context) throws Exception
148                         {
149                             Socket socket = null;
150                             try
151                             {
152                                 socket = serverSocket.accept();
153                             }
154                             catch (Exception e)
155                             {
156                                 if (!connector.isDisposed() && !disposing.get())
157                                 {
158                                     throw new ConnectException(e, null);
159                                 }
160                             }
161 
162                             if (socket != null)
163                             {
164                                 Work work = createWork(socket);
165                                 getWorkManager().scheduleWork(work, WorkManager.INDEFINITE, null, connector);
166                             }
167                         }
168 
169                         public String getWorkDescription()
170                         {
171                             return getConnectionDescription();
172                         }
173                     }, getWorkManager());
174                 }
175                 catch (Exception e)
176                 {
177                     getConnector().getMuleContext().getExceptionListener().handleException(e);
178                 }
179             }
180         }
181     }
182 
183     public void release()
184     {
185         // template method
186     }
187 
188     @Override
189     protected void doDispose()
190     {
191         try
192         {
193             if (serverSocket != null && !serverSocket.isClosed())
194             {
195                 if (logger.isDebugEnabled())
196                 {
197                     logger.debug("Closing: " + serverSocket);
198                 }
199                 serverSocket.close();
200             }
201             serverSocket = null;
202         }
203         catch (Exception e)
204         {
205             logger.error(new DisposeException(TcpMessages.failedToCloseSocket(), e, this));
206         }
207         logger.info("Closed Tcp port");
208     }
209 
210     protected Work createWork(Socket socket) throws IOException
211     {
212         return new TcpWorker(socket, this);
213     }
214 
215     protected class TcpWorker extends AbstractReceiverResourceWorker implements Disposable, Expirable
216     {
217         protected Socket socket = null;
218         protected TcpInputStream dataIn;
219         protected InputStream underlyingIn;
220         protected OutputStream dataOut;
221         protected TcpProtocol protocol;
222         protected boolean dataInWorkFinished = false;
223         protected Object notify = new Object();
224         private boolean moreMessages = true;
225         
226         public TcpWorker(Socket socket, AbstractMessageReceiver receiver) throws IOException
227         {
228             super(socket, receiver, ((TcpConnector) connector).getTcpProtocol().createResponse(socket));
229             this.socket = socket;
230 
231             final TcpConnector tcpConnector = ((TcpConnector) connector);
232             protocol = tcpConnector.getTcpProtocol();
233 
234             tcpConnector.configureSocket(TcpConnector.SERVER, socket);
235 
236             underlyingIn = new BufferedInputStream(socket.getInputStream());
237             dataIn = new TcpInputStream(underlyingIn)
238             {
239                 @Override
240                 public void close() throws IOException
241                 {
242                     // Don't actually close the stream, we just want to know if the
243                     // we want to stop receiving messages on this sockete.
244                     // The Protocol is responsible for closing this.
245                     dataInWorkFinished = true;
246                     moreMessages = false;
247                     
248                     synchronized (notify)
249                     {
250                         notify.notifyAll();
251                     }
252                 }
253             };
254             dataOut = new BufferedOutputStream(socket.getOutputStream());
255         }
256 
257         public void expired()
258         {
259             dispose();
260         }
261         
262         public void dispose()
263         {
264             releaseSocket();
265         }
266 
267         @Override
268         public void release()
269         {
270             waitForStreams();
271             releaseSocket();
272         }
273 
274         private void waitForStreams()
275         {
276             // The Message with the InputStream as a payload can be dispatched
277             // into a different thread, in which case we need to wait for it to 
278             // finish streaming 
279             if (!dataInWorkFinished)
280             {
281                 synchronized (notify)
282                 {
283                     if (!dataInWorkFinished)
284                     {
285                         try
286                         {
287                             notify.wait();
288                         }
289                         catch (InterruptedException e)
290                         {
291                         }
292                     }
293                 }
294             }
295         }
296 
297         /**
298          * Releases the socket when the input stream is closed.
299          */
300         private void releaseSocket()
301         {
302             if (socket != null && !socket.isClosed())
303             {
304                 if (logger.isDebugEnabled())
305                 {
306                     // some dirty workaround for IBM JSSE's SSL implementation,
307                     // which closes sockets asynchronously by that point.
308                     final SocketAddress socketAddress = socket.getLocalSocketAddress();
309                     if (socketAddress == null)
310                     {
311                         logger.debug("Listener has already been closed by other process.");
312                     }
313                     else
314                     {
315                         logger.debug("Closing listener: " + socketAddress);
316                     }
317                 }
318                     
319                 try
320                 {
321                     shutdownSocket();
322                     socket.close();
323                 }
324                 catch (IOException e)
325                 {
326                     logger.warn("Socket close failed with: " + e);
327                 }
328             }
329         }
330 
331         protected void shutdownSocket() throws IOException
332         {
333             try
334             {
335                 socket.shutdownOutput();
336             }
337             catch (UnsupportedOperationException e)
338             {
339                 //Ignore, not supported by ssl sockets
340             }
341         }
342 
343         @Override
344         protected void bindTransaction(Transaction tx) throws TransactionException
345         {
346             //nothing to do
347         }
348 
349         @Override
350         protected Object getNextMessage(Object resource) throws Exception
351         {
352             long keepAliveTimeout = ((TcpConnector)connector).getKeepAliveTimeout();
353             
354             Object readMsg = null;
355             try
356             {
357                 // Create a monitor if expiry was set
358                 if(keepAliveTimeout > 0)
359                 {
360                     ((TcpConnector) connector).getKeepAliveMonitor().addExpirable(keepAliveTimeout, 
361                         TimeUnit.MILLISECONDS, this);
362                 }
363                 
364                 readMsg = protocol.read(dataIn);
365                 
366                 // There was some action so we can clear the monitor
367                 ((TcpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
368                 
369                 if (dataIn.isStreaming())
370                 {
371                     moreMessages = false;
372                 } 
373                 
374                 return readMsg;
375             }
376             catch (SocketTimeoutException e)
377             {
378                 ((TcpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
379             }
380             finally
381             {
382                 if (readMsg == null)
383                 {
384                     // Protocols can return a null object, which means we're done
385                     // reading messages for now and can mark the stream for closing later.
386                     // Also, exceptions can be thrown, in which case we're done reading.
387                     dataIn.close();
388                 }
389             }
390             
391             return null;
392         }
393         
394         @Override
395         protected boolean hasMoreMessages(Object message)
396         {
397             return !socket.isClosed() && !dataInWorkFinished 
398                 && !disposing.get() && moreMessages;
399         }
400 
401         @Override
402         protected void handleResults(List messages) throws Exception
403         {            
404             //should send back only if remote synch is set or no outbound endpoints
405             if (endpoint.getExchangePattern().hasResponse())
406             {
407                 for (Iterator iterator = messages.iterator(); iterator.hasNext();)
408                 {
409                     Object o = iterator.next();
410                     protocol.write(dataOut, o);
411                     dataOut.flush();
412                 }
413             }
414         }
415 
416         @Override
417         protected void preRouteMuleMessage(final MuleMessage message) throws Exception
418         {
419             super.preRouteMuleMessage(message);
420 
421             final SocketAddress clientAddress = socket.getRemoteSocketAddress();
422             if (clientAddress != null)
423             {
424                 message.setOutboundProperty(MuleProperties.MULE_REMOTE_CLIENT_ADDRESS, clientAddress.toString());
425             }
426         }
427     }
428 
429 }