View Javadoc

1   /*
2   * $Id: AbstractByteProtocol.java 21584 2011-03-18 12:17:31Z tcarlson $
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  
11  package org.mule.transport.tcp.protocols;
12  
13  import org.mule.ResponseOutputStream;
14  import org.mule.api.MuleMessage;
15  import org.mule.transport.tcp.TcpProtocol;
16  import org.mule.util.ClassUtils;
17  import org.mule.util.IOUtils;
18  import org.mule.util.SerializationUtils;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.Serializable;
24  import java.net.Socket;
25  import java.net.SocketException;
26  import java.net.SocketTimeoutException;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  /**
32   * This Abstract class has been introduced so as to have the byte protocols (i.e. the
33   * protocols that had only a single write method taking just an array of bytes as a
34   * parameter) to inherit from since they will all behave the same, i.e. if the object
35   * is serializable, serialize it into an array of bytes and send it.
36   * <p/>
37   * <p>Note that the raw write method has changed name from <code>write</code> to
38   * <code>writeByteArray</code>.  This is to remove ambiguity from the code.  In almost
39   * all cases it is possible to call {@link #write(java.io.OutputStream, Object)} which
40   * will, via {@link #write(java.io.OutputStream, Object)}, dispatch to
41   * {@link #writeByteArray(java.io.OutputStream, byte[])}.</p>.
42   */
43  public abstract class AbstractByteProtocol implements TcpProtocol
44  {
45      private static final Log logger = LogFactory.getLog(DirectProtocol.class);
46      private static final long PAUSE_PERIOD = 100;
47      public static final int EOF = -1;
48  
49      // make this really clear in subclasses, because otherwise people will forget
50      public static final boolean STREAM_OK = true;
51      public static final boolean NO_STREAM = false;
52      private boolean streamOk;
53      private boolean rethrowExceptionOnRead = false;
54  
55      public AbstractByteProtocol(boolean streamOk)
56      {
57          this.streamOk = streamOk;
58      }
59  
60      public void write(OutputStream os, Object data) throws IOException
61      {
62          if (data instanceof InputStream)
63          {
64              if (streamOk)
65              {
66                  InputStream is = (InputStream) data;
67                  IOUtils.copyLarge(is, os);
68                  os.flush();
69                  os.close();
70                  is.close();
71              }
72              else
73              {
74                  throw new IOException("TCP protocol " + ClassUtils.getSimpleName(getClass())
75                          + " cannot handle streaming");
76              }
77          }
78          else if (data instanceof MuleMessage)
79          {
80              write(os, ((MuleMessage) data).getPayload());
81          }
82          else if (data instanceof byte[])
83          {
84              writeByteArray(os, (byte[]) data);
85          }
86          else if (data instanceof String)
87          {
88              // TODO SF: encoding is lost/ignored; it is probably a good idea to have
89              // a separate "stringEncoding" property on the protocol
90              writeByteArray(os, ((String) data).getBytes());
91          }
92          else if (data instanceof Serializable)
93          {
94              writeByteArray(os, SerializationUtils.serialize((Serializable) data));
95          }
96          else
97          {
98              throw new IllegalArgumentException("Cannot serialize data: " + data);
99          }
100     }
101 
102     protected void writeByteArray(OutputStream os, byte[] data) throws IOException
103     {
104         os.write(data);
105     }
106 
107     /**
108      * Manage non-blocking reads and handle errors
109      *
110      * @param is     The input stream to read from
111      * @param buffer The buffer to read into
112      * @return The amount of data read (always non-zero, -1 on EOF or socket exception)
113      * @throws IOException other than socket exceptions
114      */
115     protected int safeRead(InputStream is, byte[] buffer) throws IOException
116     {
117         return safeRead(is, buffer, buffer.length);
118     }
119 
120     /**
121      * Manage non-blocking reads and handle errors
122      *
123      * @param is     The input stream to read from
124      * @param buffer The buffer to read into
125      * @param size   The amount of data (upper bound) to read
126      * @return The amount of data read (always non-zero, -1 on EOF or socket exception)
127      * @throws IOException other than socket exceptions
128      */
129     protected int safeRead(InputStream is, byte[] buffer, int size) throws IOException
130     {
131         int len;
132         try
133         {
134             do
135             {
136                 len = is.read(buffer, 0, size);
137                 if (0 == len)
138                 {
139                     // wait for non-blocking input stream
140                     // use new lock since not expecting notification
141                     try
142                     {
143                         Thread.sleep(PAUSE_PERIOD);
144                     }
145                     catch (InterruptedException e)
146                     {
147                         // no-op
148                     }
149                 }
150             }
151             while (0 == len);
152             return len;
153         }
154         catch (SocketException e)
155         {
156             // do not pollute the log with a stacktrace, log only the message
157             logger.info("Socket exception occured: " + e.getMessage());
158             if (this.rethrowExceptionOnRead)
159             {
160                 throw e;
161             }
162             else
163             {
164                 return EOF;
165             }
166         }
167         catch (SocketTimeoutException e)
168         {
169             logger.debug("Socket timeout.");
170             if (this.rethrowExceptionOnRead)
171             {
172                 throw e;
173             }
174             else
175             {
176                 return EOF;
177             }
178         }
179     }
180 
181     /**
182      * Make a single transfer from source to dest via a byte array buffer
183      *
184      * @param source Source of data
185      * @param buffer Buffer array for transfer
186      * @param dest   Destination of data
187      * @return Amount of data transferred, or -1 on eof or socket error
188      * @throws IOException On non-socket error
189      */
190     protected int copy(InputStream source, byte[] buffer, OutputStream dest) throws IOException
191     {
192         return copy(source, buffer, dest, buffer.length);
193     }
194 
195     /**
196      * Make a single transfer from source to dest via a byte array buffer
197      *
198      * @param source Source of data
199      * @param buffer Buffer array for transfer
200      * @param dest   Destination of data
201      * @param size   The amount of data (upper bound) to read
202      * @return Amount of data transferred, or -1 on eof or socket error
203      * @throws IOException On non-socket error
204      */
205     protected int copy(InputStream source, byte[] buffer, OutputStream dest, int size) throws IOException
206     {
207         int len = safeRead(source, buffer, size);
208         if (len > 0)
209         {
210             dest.write(buffer, 0, len);
211         }
212         return len;
213     }
214 
215     protected byte[] nullEmptyArray(byte[] data)
216     {
217         if (0 == data.length)
218         {
219             return null;
220         }
221         else
222         {
223             return data;
224         }
225     }
226 
227     public ResponseOutputStream createResponse(Socket socket) throws IOException
228     {
229         return new ResponseOutputStream(socket, new ProtocolStream(this, streamOk, socket.getOutputStream()));
230     }
231 
232     public boolean isRethrowExceptionOnRead()
233     {
234         return rethrowExceptionOnRead;
235     }
236 
237     public void setRethrowExceptionOnRead(boolean rethrowExceptionOnRead)
238     {
239         this.rethrowExceptionOnRead = rethrowExceptionOnRead;
240     }
241 
242 }