View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx;
30  
31  import java.io.UnsupportedEncodingException;
32  import java.io.IOException;
33  
34  import java.nio.charset.Charset;
35  
36  import java.text.SimpleDateFormat;
37  import java.text.ParseException;
38  
39  import java.util.Date;
40  import java.util.TimeZone;
41  
42  import org.apache.mina.common.ByteBuffer;
43  
44  /***
45   * Set of constants and useful static methods for the Styx protocol
46   * @author Jon Blower
47   * $Revision: 604 $
48   * $Date: 2006-03-21 14:58:42 +0000 (Tue, 21 Mar 2006) $
49   * $Log$
50   * Revision 1.12  2006/03/21 14:58:40  jonblower
51   * Implemented clear-text password-based authentication and did some simple tests
52   *
53   * Revision 1.11  2006/01/04 16:45:29  jonblower
54   * Implemented automatic termination of SGS instances using Quartz scheduler
55   *
56   * Revision 1.10  2006/01/04 11:24:56  jonblower
57   * Implemented time directory in the SGS instance namespace
58   *
59   * Revision 1.8  2005/03/18 13:55:55  jonblower
60   * Improved freeing of ByteBuffers, and bug fixes
61   *
62   * Revision 1.7  2005/03/16 22:16:41  jonblower
63   * Added Styx Grid Service classes to core module
64   *
65   * Revision 1.6  2005/03/16 17:55:46  jonblower
66   * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
67   *
68   * Revision 1.5  2005/03/15 15:52:17  jonblower
69   * Added constant for maximum allowable message size
70   *
71   * Revision 1.4  2005/03/11 13:58:24  jonblower
72   * Merged MINA-Test_20059309 into main line of development
73   *
74   * Revision 1.3.2.1  2005/03/10 20:54:55  jonblower
75   * Removed references to Netty
76   *
77   * Revision 1.3  2005/03/09 16:59:51  jonblower
78   * Added HEADER_LENGTH
79   *
80   * Revision 1.2  2005/02/24 07:39:39  jonblower
81   * Added getDataSummary()
82   *
83   * Revision 1.1.1.1  2005/02/16 18:58:16  jonblower
84   * Initial import
85   *
86   */
87  public class StyxUtils
88  {
89      /***
90       * The header length of a StyxMessage
91       */
92      public static final int HEADER_LENGTH = 7;
93      
94      /***
95       * The maximum length of a single Styx message.  This is actually an
96       * arbitrary figure; there is no reason why messages can't be larger than this
97       */
98      public static final int MAX_MESSAGE_SIZE = 65536;
99      
100     // Constants relating to max values of unsigned quantities
101     public static final int  MAXUBYTE  = 0xff;
102     public static final int  MAXUSHORT = 0xffff;
103     public static final long MAXUINT   = 0xffffffffL;
104     public static final long MAXULONG  = -1;
105     
106     public static final int NOTAG = MAXUSHORT; // Used by TversionMessages, which don't need a proper tag
107     public static final long NOFID = MAXUINT;  // Used by TattachMessages for an unauthenticated connection
108     
109     public static final int MAXPATHELEMENTS = 16;  // The maximum number of path elements in a Twalk message
110     
111     // Constants relating to file mode (see TopenMessage)
112     // TODO: should OREAD, OWRITE, ORDWR, OEXEC be a type-safe enumeration?
113     public static final int OREAD   = 0;    // Open file for reading
114     public static final int OWRITE  = 1;    // Open file for writing
115     public static final int ORDWR   = 2;    // Open file for reading and writing
116     public static final int OEXEC   = 3;    // Open file for execution
117     public static final int OTRUNC  = 0x10; // If this bit is set, the file will be truncated
118     public static final int ORCLOSE = 0x40; // If this bit is set, the file will be removed 
119                                             // when the fid is clunked (requires permission 
120                                             // to remove the file)
121     
122     // Constants relating to file type
123     public static final long DMDIR    = 0x80000000L; // A directory
124     public static final long DMAPPEND = 0x40000000L; // An append-only file
125     public static final long DMEXCL   = 0x20000000L; // File that can only be opened
126                                                      // by one client at a time
127     public static final long DMAUTH   =  0x8000000L; // File that is used during authentication    
128     
129     // We know that UTF-8 must be supported on all Java platforms
130     private static final String charsetName = "UTF-8";
131     public static final Charset UTF8 = Charset.forName(charsetName);
132     
133     public static final String NEWLINE = "\n"; // Newline is character 10 on Inferno
134     public static final String SYSTEM_NEWLINE = System.getProperty("line.separator"); // The newline character on the host OS
135     public static final String SYSTEM_FILE_SEPARATOR = System.getProperty("file.separator");
136     
137     /***
138      * The username for an anonymous user
139      */
140     public static final String ANONYMOUS_USER = "nobody";
141     /***
142      * When a client creates a file on the Styx server, this is the default
143      * group for the file. All users belong to this group.
144      */
145     public static final String DEFAULT_GROUP = "users";
146     
147     // Formatter for xsd:dateTime format: [-]CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
148     // We don't allow fractions of seconds
149     private static SimpleDateFormat XSD_DATE_TIME_FORMAT
150         = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
151     
152     /***
153      * Converts a string to a byte array in UTF-8
154      */
155     public static byte[] strToUTF8(String str)
156     {
157         try
158         {
159             // TODO: use the Charset object to do the encoding?
160             return str.getBytes(charsetName);
161         }
162         catch (UnsupportedEncodingException uee)
163         {
164             // can't happen: UTF-8 should always be supported
165             throw new InternalError("Fatal error: " + charsetName + 
166                 " is not supported on this platform!");
167         }
168     }
169     
170     /***
171      * Converts an array of bytes in UTF-8 to a String
172      */
173     public static String utf8ToString(byte[] bytes)
174     {
175         return utf8ToString(bytes, 0, bytes.length);
176     }
177     
178     /***
179      * Converts an array of bytes in UTF-8 to a String
180      * @param bytes The array of bytes to convert
181      * @param offset The index of the first byte in the array to convert
182      * @param length The number of bytes to convert
183      */
184     public static String utf8ToString(byte[] bytes, int offset, int length)
185     {
186         try
187         {
188             return new String(bytes, offset, length, charsetName);
189         }
190         catch (UnsupportedEncodingException uee)
191         {
192             // can't happen: UTF-8 should always be supported
193             throw new InternalError("Fatal error: " + charsetName + 
194                 " is not supported on this platform!");
195         }
196     }
197     
198     /***
199      * Gets the remaining contents of the given ByteBuffer (i.e. the bytes
200      * between its position and limit) as a String.  Leaves the position of the
201      * ByteBuffer unchanged.
202      */
203     public static String dataToString(ByteBuffer buf)
204     {
205         // MINA's ByteBuffers do not have a backing array (they are all created
206         // with allocateDirect), so we have to extract the bytes ourselves
207         byte[] bytes;
208         synchronized (buf)
209         {
210             // Remember the original position of the buffer
211             int pos = buf.position();
212             bytes = new byte[buf.remaining()];
213             buf.get(bytes);
214             // Reset the original position of the buffer
215             buf.position(pos);
216         }
217         return utf8ToString(bytes);
218     }
219     
220     /***
221      * Gets the remaining contents of the given java.nio.ByteBuffer (i.e. the bytes
222      * between its position and limit) as a String.  Leaves the position of the
223      * ByteBuffer unchanged.
224      */
225     public static String dataToString(java.nio.ByteBuffer buf)
226     {
227         // First check to see if the input buffer has a backing array; if so,
228         // we can just use it, to save making a copy of the data
229         byte[] bytes;
230         if (buf.hasArray())
231         {
232             bytes = buf.array();
233             return utf8ToString(bytes, buf.position(), buf.remaining());
234         }
235         else
236         {
237             synchronized (buf)
238             {
239                 // Remember the original position of the buffer
240                 int pos = buf.position();
241                 bytes = new byte[buf.remaining()];
242                 buf.get(bytes);
243                 // Reset the original position of the buffer
244                 buf.position(pos);
245             }
246             return utf8ToString(bytes);
247         }
248     }
249     
250     /***
251      * @return the first n bytes of the data in the given buffer as a String
252      * (in quotes), then the number of bytes remaining. Leaves the position of
253      * the ByteBuffer unchanged.
254      */
255     public static String getDataSummary(int n, ByteBuffer data)
256     {
257         byte[] bytes;
258         synchronized(data)
259         {
260             int numBytes = data.remaining() < n ? data.remaining() : n;
261             bytes = new byte[numBytes];
262             int pos = data.position();
263             data.get(bytes);
264             // Reset the position of the data buffer
265             data.position(pos);
266         }
267         return getDataSummary(bytes.length, bytes);
268     }
269     
270     /***
271      * @return the first n bytes of the data in the given byte array as a String
272      * (in quotes), then the number of bytes remaining.
273      */
274     public static String getDataSummary(int n, byte[] bytes)
275     {
276         StringBuffer s = new StringBuffer("\"");
277         int bytesToWrite = bytes.length < n ? bytes.length : n;
278         s.append(StyxUtils.utf8ToString(bytes, 0, bytesToWrite));
279         s.append("\"");
280         int moreBytes = bytes.length - bytesToWrite;
281         if (moreBytes > 0)
282         {
283             s.append(" (plus " + moreBytes + " more bytes)");
284         }
285         return s.toString();
286     }
287     
288     /***
289      * @return the current time in seconds since the epoch (Jan 1 00:00 1970 GMT),
290      * suitable for use in stat messages
291      */
292     public static long now()
293     {
294         // TODO: use rounding instead of integer truncation? Does it really matter?
295         return System.currentTimeMillis() / 1000;
296     }
297     
298     /***
299      * Returns a date formatted according to the xsd:dateTime data type
300      * @param date the date to format.
301      * @return the formatted date.
302      */
303     public static String formatAsXsdDateTime(Date date)
304     {
305         // Set time zone on formatter
306         XSD_DATE_TIME_FORMAT.setTimeZone(TimeZone.getDefault());
307         // Format the date
308         StringBuffer buffer = new StringBuffer(XSD_DATE_TIME_FORMAT.format(date));
309         // Add the colon into the time offset
310         buffer.insert(buffer.length() - 2, ':');
311 
312         return buffer.toString();
313     }
314     
315     /***
316      * Parses a String that is formatted according to the xsd:dateTime data type
317      * and returns it as a Date
318      * @param date the String to format.
319      * @return the parse date.
320      */
321     public static Date parseXsdDateTime(String date) throws ParseException
322     {
323         // Trim any whitespace (e.g. a newline at the end of the string)
324         String newDate = date.trim();
325         if (newDate.endsWith("Z"))
326         {
327             // Remove the Z and replace with "+0000"
328             newDate = newDate.substring(0, newDate.length() - 1) + "+0000";
329         }
330         else
331         {
332             // Remove the last colon from the string (i.e. the time offset)
333             int colonPos = newDate.lastIndexOf(":");
334             newDate = newDate.substring(0, colonPos) +
335                 newDate.substring(colonPos + 1, newDate.length());
336         }
337         return XSD_DATE_TIME_FORMAT.parse(newDate);
338     }
339     
340     /***
341      * Simple test routine for the date parsing routines
342      */
343     public static void main(String[] args) throws Exception
344     {
345         Date now = java.util.Calendar.getInstance().getTime();
346         System.out.println(now.toString());
347         String xsdStr = formatAsXsdDateTime(now);
348         System.out.println(xsdStr);
349         System.out.println(parseXsdDateTime(xsdStr));
350     }
351     
352 }