View Javadoc

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