View Javadoc

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