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.server;
30  
31  import org.apache.mina.common.ByteBuffer;
32  
33  import java.util.Vector;
34  import java.util.Iterator;
35  
36  import uk.ac.rdg.resc.jstyx.types.ULong;
37  import uk.ac.rdg.resc.jstyx.types.DirEntry;
38  import uk.ac.rdg.resc.jstyx.StyxException;
39  import uk.ac.rdg.resc.jstyx.messages.StyxBuffer;
40  
41  /***
42   * Class representing a directory on a Styx server. One would only rarely need 
43   * to create subclasses of this; an example would be creating a StyxDirectory that
44   * represents a directory on the host filesystem (see DirectoryOnDisk).
45   *
46   * @author Jon Blower
47   * $Revision: 601 $
48   * $Date: 2006-03-20 17:51:50 +0000 (Mon, 20 Mar 2006) $
49   * $Log$
50   * Revision 1.13  2006/03/20 17:51:50  jonblower
51   * Adding authentication to base JStyx system
52   *
53   * Revision 1.12  2006/01/04 16:47:29  jonblower
54   * Reworked getName() and getFullPath()
55   *
56   * Revision 1.11  2005/09/08 07:08:59  jonblower
57   * Removed "String user" from list of parameters to StyxFile.write()
58   *
59   * Revision 1.10  2005/05/19 14:46:51  jonblower
60   * Changed behaviour of StyxDirectory.createChild(): no longer adds file to namespace in this method
61   *
62   * Revision 1.9  2005/05/11 10:34:31  jonblower
63   * Changed so that addChild() returns this StyxDirectory object to allow chaining
64   *
65   * Revision 1.8  2005/04/28 08:11:15  jonblower
66   * Modified permissions handling in documentation directory of SGS
67   *
68   * Revision 1.7  2005/03/24 09:48:31  jonblower
69   * Changed 'count' from long to int throughout for reading and writing
70   *
71   * Revision 1.6  2005/03/22 17:48:27  jonblower
72   * Removed debug code that tracked ByteBuffer allocation
73   *
74   * Revision 1.5  2005/03/21 17:57:11  jonblower
75   * Trying to fix ByteBuffer leak in SGS server
76   *
77   * Revision 1.4  2005/03/16 17:56:24  jonblower
78   * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
79   *
80   * Revision 1.3  2005/03/11 14:02:16  jonblower
81   * Merged MINA-Test_20059309 into main line of development
82   *
83   * Revision 1.2.2.1  2005/03/10 11:53:54  jonblower
84   * Modified for MINA framework
85   *
86   * Revision 1.2  2005/03/01 13:47:43  jonblower
87   * Changed default user and group to 'user' and 'group'
88   *
89   * Revision 1.1.1.1  2005/02/16 18:58:32  jonblower
90   * Initial import
91   *
92   */
93  public class StyxDirectory extends StyxFile
94  {   
95      
96      private Vector children; // All the children of this directory
97      
98      /*** Creates a new instance of StyxDirectory */
99      public StyxDirectory(String name, String owner, String group, int permissions)
100         throws StyxException
101     {
102         // Directories cannot be append-only, exclusive or auth files
103         super(name, owner, group, permissions, false, false);
104         this.directory = true;
105         this.children = new Vector();
106     }
107     
108     /***
109      * Creates a directory with default permissions (0777, rwxrwxrwx)
110      */
111     public StyxDirectory(String name) throws StyxException
112     {
113         this(name, "user", "group", 0777);
114     }
115     
116     /***
117      * Creates a directory with the given permissions
118      */
119     public StyxDirectory(String name, int permissions) throws StyxException
120     {
121         this(name, "user", "group", permissions);
122     }
123     
124     /***
125      * This method is overridden to return a more meaningful error message.
126      */
127     public void checkSetLength(ULong newLength) throws StyxException
128     {
129         throw new StyxException("cannot change the length of a directory");
130     }
131     
132     /***
133      * @return true if this is the root directory (i.e. if it has no parent)
134      */
135     public boolean isRoot()
136     {
137         return (this.parent == null);
138     }
139     
140     /***
141      * Gets the full path relative to the root of this file system, or
142      * a single slash if this is the root directory.  Since this is a directory,
143      * the full path will end in a slash
144      */
145     public String getFullPath()
146     {
147         if (this.parent == null)
148         {
149             return "/";
150         }
151         return this.parent.getFullPath() + this.getName() + "/";
152     }
153     
154     /***
155      * @return the name of this file
156      */
157     public String getName()
158     {
159         return this.name;
160     }
161     
162     /***
163      * Returns the directory contents. This method cannot be overridden.
164      */
165     public final void read(StyxFileClient client, long offset, int count, int tag)
166         throws StyxException
167     {
168         if (client == null)
169         {
170             throw new StyxException("Internal error: client is null");
171         }
172 
173         // Check that the offset is valid; zero offsets are always valid, but
174         // non-zero offsets are only valid if this client has read part of
175         // the contents of this directory before
176         if (offset != 0 && offset != client.getOffset())
177         {
178             throw new StyxException("Invalid offset when reading directory");
179         }
180 
181         // We create the bytes to return in a ByteBuffer for convenience
182         ByteBuffer buf = ByteBuffer.allocate((int)count);
183         StyxBuffer styxBuf = new StyxBuffer(buf);
184         StyxFile sf;
185         int nextFile = (offset == 0) ? 0 : client.getNextFileToRead();
186 
187         while (nextFile < this.children.size())
188         {
189             sf = (StyxFile)this.children.get(nextFile);
190             // check for overflows, or if data written > count
191             DirEntry dirEntry = sf.getDirEntry();
192             if (dirEntry.getSize() <= buf.remaining())
193             {
194                 styxBuf.putDirEntry(dirEntry);
195             }
196             else
197             {
198                 break;
199             }
200             nextFile++;
201         }
202 
203         buf.flip();
204         // remember the number of bytes returned and the index of the 
205         // next child file to include in the next message
206         client.setOffset(offset + buf.limit());
207         client.setNextFileToRead(nextFile);
208         
209         // Get the bytes from the buffer
210         byte[] bytes = new byte[buf.remaining()];
211         buf.get(bytes);
212         // Free the buffer so that it can be reused
213         buf.release();
214         
215         this.replyRead(client, bytes, tag);
216     }
217     
218     /***
219      * This always throws a StyxException as it is illegal to write to a
220      * directory
221      */
222     public void write(StyxFileClient client, long offset, int count,
223         ByteBuffer data, boolean truncate, int tag)
224         throws StyxException
225     {
226         throw new StyxException("Cannot write to a directory");
227     }
228     
229     /***
230      * @return the length of the file. This is always zero: subclasses cannot
231      * override this.
232      */
233     public final ULong getLength()
234     {
235         return ULong.ZERO;
236     }
237     
238     /***
239      * Refreshes this file (if it represents another entity, such as a file on disk,
240      * this method is used to make sure that the file metadata (length, access
241      * time etc) are up to date.
242      */
243     public synchronized void refresh()
244     {
245         this.refresh(true);
246     }
247     
248     /***
249      * @param updateChildren If this is set true, this method will also refresh
250      * all the immediate children of this directory. This default method does
251      * nothing; subclasses must override this.
252      */
253     protected synchronized void refresh(boolean updateChildren)
254     {
255         return; // it is up to subclasses to implement this if necessary
256     }
257     
258     /***
259      * Gets all the direct descendants of this directory
260      * @return the children as an array of StyxFiles or null if this StyxFile
261      * is not a directory
262      */
263     public synchronized StyxFile[] getChildren()
264     {
265         if (this.children == null)
266         {
267             return null;
268         }
269         // TODO: toArray() should work without the dummy 
270         // argument - why doesn't it?
271         return (StyxFile[])this.children.toArray(new StyxFile[0]);
272     }
273     
274     /***
275      * Gets the number of direct descendants of this directory
276      */
277     public int getNumChildren()
278     {
279         if (this.children == null)
280         {
281             return 0;
282         }
283         return this.children.size();
284     }
285     
286     /***
287      * Adds a file to this directory.  If a file with the same name already
288      * exists, throws a FileExistsException
289      * @return this StyxDirectory object, so that calls can be chained:
290      * <code>StyxDirectory root = new StyxDirectory().addChild(file1).addChile(file2)</code>
291      */
292     public synchronized StyxDirectory addChild(StyxFile sf) throws FileExistsException
293     {
294         // Check that the 
295         // check that a file with this name does not already exist
296         synchronized (this.children)
297         {
298             if (childExists(sf.getName()))
299             {            
300                 throw new FileExistsException(sf.getName() + " already exists");
301             }
302             sf.parent = this;
303             this.children.add(sf);
304         }
305         // Notify all interested parties that the contents of this directory
306         // have changed
307         this.fireContentsChanged();
308         return this;
309     }
310     
311     /***
312      * @return true if a child with the given name exists in this directory
313      */
314     public boolean childExists(String name)
315     {
316         name = name.trim();
317         // check that a file with this name does not already exist
318         synchronized (this.children)
319         {
320             for (int i = 0; i < this.children.size(); i++)
321             {
322                 StyxFile f = (StyxFile)this.children.get(i);
323                 if (f.getName().equals(name))
324                 {
325                     return true;
326                 }
327             }
328         }
329         return false;
330     }
331     
332     /***
333      * Creates a new file to be added to this directory. This default
334      * implementation throws an exception; subclasses should override this method
335      * to allow files to be created.
336      * This method should <b>not</b> add the new file to the directory. This will
337      * be done in StyxServerProtocolHandler.replyCreate().
338      * @return The newly-created file
339      */
340     public StyxFile createChild(String name, int perm, boolean isDir,
341         boolean isAppOnly, boolean isExclusive) throws StyxException
342     {
343         throw new StyxException("cannot create a new file in this type of directory");
344     }
345     
346     /***
347      * Removes the given file from this directory.
348      */
349     public synchronized void removeChild(StyxFile child)
350     {
351         this.children.remove(child);
352         // Notify all interested parties that the contents of this directory
353         // have changed
354         this.fireContentsChanged();
355     }
356     
357     /***
358      * Removes this directory from the server. This directory must be empty
359      * (see this.removeAllChildren())
360      * @throws StyxException if this is the root directory, or if it is not empty
361      */
362     public synchronized void remove() throws StyxException
363     {
364         if (this.isRoot())
365         {
366             throw new StyxException("Cannot remove the root directory of the server");
367         }
368         if (this.getNumChildren() != 0)
369         {
370             throw new StyxException("Cannot remove a directory unless it is empty");
371         }
372         super.remove();
373     }
374     
375     /***
376      * Recursively removes all children from this directory.
377      */
378     public synchronized void removeAllChildren()
379     {
380         synchronized(this.children)
381         {
382             StyxFile sf;
383             while(this.children.size() > 0)
384             {
385                 // Always get the first child: the children are progressively removed
386                 sf = (StyxFile)this.children.get(0);
387                 if (sf instanceof StyxDirectory)
388                 {
389                     // If this is a directory, remove all its children
390                     StyxDirectory sd = (StyxDirectory)sf;
391                     sd.removeAllChildren();
392                 }
393                 // Now remove this file or directory
394                 try
395                 {
396                     // This will also remove the StyxFile from the Vector of children
397                     sf.remove();
398                 }
399                 catch(StyxException se)
400                 {
401                     // TODO: is this the best thing to do here? We probably
402                     // shouldn't abort the whole operation so it's probably best
403                     // not to throw the exception
404                     se.printStackTrace();
405                     // Just to be sure, let's remove the StyxFile from the Vector
406                     // of children (otherwise this loop will never end)
407                     this.children.remove(sf);
408                 }
409             }
410         }
411     }
412     
413     /***
414      * Gets the child with the given name or null if it does not exist
415      */
416     public StyxFile getChild(String name)
417     {
418         if (name.equals(".."))
419         {
420             return this.getParent();
421         }
422         if (name.equals("."))
423         {
424             return this; // Should not happen: TwalkMessage should filter out
425                          // path elements representing the current directory
426         }
427         for (int i = 0; i < this.children.size(); i++)
428         {
429             StyxFile sf = (StyxFile)this.children.get(i);
430             String sfName = sf.getName();
431             if (name.equals(sfName))
432             {
433                 return sf;
434             }
435         }
436         return null;
437     }
438     
439     /***
440      * Returns the parent of this directory. The parent of the root directory is
441      * the root itself (according to the Inferno manual)
442      */
443     public final StyxDirectory getParent()
444     {
445         return this.parent == null ? this : this.parent;
446     }
447     
448 }