View Javadoc

1   /*
2   * $Id: AbstractByteProtocol.java 10489 2008-01-23 17:53:38Z dfeist $
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  
11  package org.mule.transport.tcp.protocols;
12  
13  import org.mule.ResponseOutputStream;
14  import org.mule.api.transport.MessageAdapter;
15  import org.mule.transport.tcp.TcpProtocol;
16  import org.mule.util.ClassUtils;
17  import org.mule.util.IOUtils;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.io.Serializable;
23  import java.net.Socket;
24  import java.net.SocketException;
25  import java.net.SocketTimeoutException;
26  
27  import org.apache.commons.lang.SerializationUtils;
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  
54      public AbstractByteProtocol(boolean streamOk)
55      {
56          this.streamOk = streamOk;
57      }
58  
59      public void write(OutputStream os, Object data) throws IOException
60      {
61          if (data instanceof InputStream)
62          {
63              if (streamOk)
64              {
65                  IOUtils.copyLarge((InputStream) data, os);
66                  os.flush();
67                  os.close();
68              }
69              else
70              {
71                  throw new IOException("TCP protocol " + ClassUtils.getSimpleName(getClass())
72                          + " cannot handle streaming");
73              }
74          }
75          else if (data instanceof MessageAdapter)
76          {
77              write(os, ((MessageAdapter) data).getPayload());
78          }
79          else if (data instanceof byte[])
80          {
81              writeByteArray(os, (byte[]) data);
82          }
83          else if (data instanceof String)
84          {
85              // TODO SF: encoding is lost/ignored; it is probably a good idea to have
86              // a separate "stringEncoding" property on the protocol
87              writeByteArray(os, ((String) data).getBytes());
88          }
89          else if (data instanceof Serializable)
90          {
91              writeByteArray(os, SerializationUtils.serialize((Serializable) data));
92          }
93          else
94          {
95              throw new IllegalArgumentException("Cannot serialize data: " + data);
96          }
97      }
98  
99      protected void writeByteArray(OutputStream os, byte[] data) throws IOException
100     {
101         os.write(data);
102     }
103 
104     /**
105      * Manage non-blocking reads and handle errors
106      *
107      * @param is     The input stream to read from
108      * @param buffer The buffer to read into
109      * @return The amount of data read (always non-zero, -1 on EOF or socket exception)
110      * @throws IOException other than socket exceptions
111      */
112     protected int safeRead(InputStream is, byte[] buffer) throws IOException
113     {
114         return safeRead(is, buffer, buffer.length);
115     }
116 
117     /**
118      * Manage non-blocking reads and handle errors
119      *
120      * @param is     The input stream to read from
121      * @param buffer The buffer to read into
122      * @param size   The amount of data (upper bound) to read
123      * @return The amount of data read (always non-zero, -1 on EOF or socket exception)
124      * @throws IOException other than socket exceptions
125      */
126     protected int safeRead(InputStream is, byte[] buffer, int size) throws IOException
127     {
128         int len;
129         try
130         {
131             do
132             {
133                 len = is.read(buffer, 0, size);
134                 if (0 == len)
135                 {
136                     // wait for non-blocking input stream
137                     // use new lock since not expecting notification
138                     try
139                     {
140                         Thread.sleep(PAUSE_PERIOD);
141                     }
142                     catch (InterruptedException e)
143                     {
144                         // no-op
145                     }
146                 }
147             }
148             while (0 == len);
149             return len;
150         }
151         catch (SocketException e)
152         {
153             // do not pollute the log with a stacktrace, log only the message
154             logger.info("Socket exception occured: " + e.getMessage());
155             return EOF;
156         }
157         catch (SocketTimeoutException e)
158         {
159             logger.debug("Socket timeout.");
160             return EOF;
161         }
162     }
163 
164     /**
165      * Make a single transfer from source to dest via a byte array buffer
166      *
167      * @param source Source of data
168      * @param buffer Buffer array for transfer
169      * @param dest   Destination of data
170      * @return Amount of data transferred, or -1 on eof or socket error
171      * @throws IOException On non-socket error
172      */
173     protected int copy(InputStream source, byte[] buffer, OutputStream dest) throws IOException
174     {
175         return copy(source, buffer, dest, buffer.length);
176     }
177 
178     /**
179      * Make a single transfer from source to dest via a byte array buffer
180      *
181      * @param source Source of data
182      * @param buffer Buffer array for transfer
183      * @param dest   Destination of data
184      * @param size   The amount of data (upper bound) to read
185      * @return Amount of data transferred, or -1 on eof or socket error
186      * @throws IOException On non-socket error
187      */
188     protected int copy(InputStream source, byte[] buffer, OutputStream dest, int size) throws IOException
189     {
190         int len = safeRead(source, buffer, size);
191         if (len > 0)
192         {
193             dest.write(buffer, 0, len);
194         }
195         return len;
196     }
197 
198     protected byte[] nullEmptyArray(byte[] data)
199     {
200         if (0 == data.length)
201         {
202             return null;
203         }
204         else
205         {
206             return data;
207         }
208     }
209 
210     public ResponseOutputStream createResponse(Socket socket) throws IOException
211     {
212         return new ResponseOutputStream(socket, new ProtocolStream(this, streamOk, socket.getOutputStream()));
213     }
214 
215 }