View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.transport.tcp;
8   
9   import org.mule.api.MuleException;
10  import org.mule.api.MuleMessage;
11  import org.mule.api.config.MuleProperties;
12  import org.mule.api.construct.FlowConstruct;
13  import org.mule.api.endpoint.InboundEndpoint;
14  import org.mule.api.lifecycle.CreateException;
15  import org.mule.api.lifecycle.Disposable;
16  import org.mule.api.lifecycle.DisposeException;
17  import org.mule.api.retry.RetryCallback;
18  import org.mule.api.retry.RetryContext;
19  import org.mule.api.transaction.Transaction;
20  import org.mule.api.transaction.TransactionException;
21  import org.mule.api.transport.Connector;
22  import org.mule.config.i18n.CoreMessages;
23  import org.mule.transport.AbstractMessageReceiver;
24  import org.mule.transport.AbstractReceiverResourceWorker;
25  import org.mule.transport.ConnectException;
26  import org.mule.transport.tcp.i18n.TcpMessages;
27  import org.mule.util.monitor.Expirable;
28  
29  import java.io.BufferedInputStream;
30  import java.io.BufferedOutputStream;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.io.OutputStream;
34  import java.net.ServerSocket;
35  import java.net.Socket;
36  import java.net.SocketAddress;
37  import java.net.SocketTimeoutException;
38  import java.net.URI;
39  import java.util.Iterator;
40  import java.util.List;
41  
42  import javax.resource.spi.work.Work;
43  import javax.resource.spi.work.WorkException;
44  import javax.resource.spi.work.WorkManager;
45  
46  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
47  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
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      protected final AtomicBoolean disposing = new AtomicBoolean(false);
58      
59      public TcpMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
60              throws CreateException
61      {
62          super(connector, flowConstruct, endpoint);
63      }
64  
65      @Override
66      protected void doConnect() throws ConnectException
67      {
68          disposing.set(false);
69  
70          URI uri = endpoint.getEndpointURI().getUri();
71  
72          try
73          {
74              serverSocket = ((TcpConnector) connector).getServerSocket(uri);
75          }
76          catch (Exception e)
77          {
78              throw new ConnectException(TcpMessages.failedToBindToUri(uri), e, this);
79          }
80  
81          try
82          {
83              getWorkManager().scheduleWork(this, WorkManager.INDEFINITE, null, connector);
84          }
85          catch (WorkException e)
86          {
87              throw new ConnectException(CoreMessages.failedToScheduleWork(), e, this);
88          }
89      }
90  
91      @Override
92      protected void doDisconnect() throws ConnectException
93      {
94          // this will cause the server thread to quit
95          disposing.set(true);
96  
97          try
98          {
99              if (serverSocket != null)
100             {
101                 if (logger.isDebugEnabled())
102                 {
103                     logger.debug("Closing: " + serverSocket);
104                 }
105                 serverSocket.close();
106             }
107         }
108         catch (IOException e)
109         {
110             logger.warn("Failed to close server socket: " + e.getMessage(), e);
111         }
112     }
113 
114     @Override
115     protected void doStart() throws MuleException
116     {
117         // nothing to do
118     }
119 
120     @Override
121     protected void doStop() throws MuleException
122     {
123         // nothing to do
124     }
125 
126     /**
127      * Obtain the serverSocket
128      * @return the server socket for this server
129      */
130     public ServerSocket getServerSocket()
131     {
132         return serverSocket;
133     }
134 
135     public void run()
136     {
137         while (!disposing.get())
138         {
139             if (connector.isStarted() && !disposing.get())
140             {
141                 try
142                 {
143                     retryTemplate.execute(new RetryCallback()
144                     {
145                         public void doWork(RetryContext context) throws Exception
146                         {
147                             Socket socket = null;
148                             try
149                             {
150                                 socket = serverSocket.accept();
151                             }
152                             catch (Exception e)
153                             {
154                                 if (!connector.isDisposed() && !disposing.get())
155                                 {
156                                     throw new ConnectException(e, null);
157                                 }
158                             }
159 
160                             if (socket != null)
161                             {
162                                 Work work = createWork(socket);
163                                 getWorkManager().scheduleWork(work, WorkManager.INDEFINITE, null, connector);
164                             }
165                         }
166 
167                         public String getWorkDescription()
168                         {
169                             return getConnectionDescription();
170                         }
171                     }, getWorkManager());
172                 }
173                 catch (Exception e)
174                 {
175                     getConnector().getMuleContext().getExceptionListener().handleException(e);
176                 }
177             }
178         }
179     }
180 
181     public void release()
182     {
183         // template method
184     }
185 
186     @Override
187     protected void doDispose()
188     {
189         try
190         {
191             if (serverSocket != null && !serverSocket.isClosed())
192             {
193                 if (logger.isDebugEnabled())
194                 {
195                     logger.debug("Closing: " + serverSocket);
196                 }
197                 serverSocket.close();
198             }
199             serverSocket = null;
200         }
201         catch (Exception e)
202         {
203             logger.error(new DisposeException(TcpMessages.failedToCloseSocket(), e, this));
204         }
205         logger.info("Closed Tcp port");
206     }
207 
208     protected Work createWork(Socket socket) throws IOException
209     {
210         return new TcpWorker(socket, this);
211     }
212 
213     protected class TcpWorker extends AbstractReceiverResourceWorker implements Disposable, Expirable
214     {
215         protected Socket socket = null;
216         protected TcpInputStream dataIn;
217         protected InputStream underlyingIn;
218         protected OutputStream dataOut;
219         protected TcpProtocol protocol;
220         protected boolean dataInWorkFinished = false;
221         protected Object notify = new Object();
222         private boolean moreMessages = true;
223         
224         public TcpWorker(Socket socket, AbstractMessageReceiver receiver) throws IOException
225         {
226             super(socket, receiver, ((TcpConnector) connector).getTcpProtocol().createResponse(socket));
227             this.socket = socket;
228 
229             final TcpConnector tcpConnector = ((TcpConnector) connector);
230             protocol = tcpConnector.getTcpProtocol();
231 
232             try
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             catch (IOException e)
257             {
258                 logger.error("Failed to set Socket properties: " + e.getMessage(), e);
259             }
260         }
261 
262         public void expired()
263         {
264             dispose();
265         }
266         
267         public void dispose()
268         {
269             releaseSocket();
270         }
271 
272         @Override
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         @Override
349         protected void bindTransaction(Transaction tx) throws TransactionException
350         {
351             //nothing to do
352         }
353 
354         @Override
355         protected Object getNextMessage(Object resource) throws Exception
356         {
357             long keepAliveTimeout = ((TcpConnector)connector).getKeepAliveTimeout();
358             
359             Object readMsg = null;
360             try
361             {
362                 // Create a monitor if expiry was set
363                 if(keepAliveTimeout > 0)
364                 {
365                     ((TcpConnector) connector).getKeepAliveMonitor().addExpirable(keepAliveTimeout, 
366                         TimeUnit.MILLISECONDS, this);
367                 }
368                 
369                 readMsg = protocol.read(dataIn);
370                 
371                 // There was some action so we can clear the monitor
372                 ((TcpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
373                 
374                 if (dataIn.isStreaming())
375                 {
376                     moreMessages = false;
377                 } 
378                 
379                 return readMsg;
380             }
381             catch (SocketTimeoutException e)
382             {
383                 ((TcpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
384             }
385             finally
386             {
387                 if (readMsg == null)
388                 {
389                     // Protocols can return a null object, which means we're done
390                     // reading messages for now and can mark the stream for closing later.
391                     // Also, exceptions can be thrown, in which case we're done reading.
392                     dataIn.close();
393                 }
394             }
395             
396             return null;
397         }
398         
399         @Override
400         protected boolean hasMoreMessages(Object message)
401         {
402             return !socket.isClosed() && !dataInWorkFinished 
403                 && !disposing.get() && moreMessages;
404         }
405 
406         @Override
407         protected void handleResults(List messages) throws Exception
408         {            
409             //should send back only if remote synch is set or no outbound endpoints
410             if (endpoint.getExchangePattern().hasResponse())
411             {
412                 for (Iterator iterator = messages.iterator(); iterator.hasNext();)
413                 {
414                     Object o = iterator.next();
415                     protocol.write(dataOut, o);
416                     dataOut.flush();
417                 }
418             }
419         }
420 
421         @Override
422         protected void preRouteMuleMessage(final MuleMessage message) throws Exception
423         {
424             super.preRouteMuleMessage(message);
425 
426             final SocketAddress clientAddress = socket.getRemoteSocketAddress();
427             if (clientAddress != null)
428             {
429                 message.setOutboundProperty(MuleProperties.MULE_REMOTE_CLIENT_ADDRESS, clientAddress.toString());
430             }
431         }
432     }
433 
434 }