1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 package uk.ac.rdg.resc.jstyx.server;
30
31 import org.apache.mina.protocol.ProtocolSession;
32 import org.apache.mina.common.ByteBuffer;
33
34 import java.util.Date;
35 import java.util.Vector;
36 import java.util.Enumeration;
37
38 import uk.ac.rdg.resc.jstyx.messages.RreadMessage;
39 import uk.ac.rdg.resc.jstyx.messages.RwriteMessage;
40
41 import uk.ac.rdg.resc.jstyx.StyxException;
42 import uk.ac.rdg.resc.jstyx.StyxUtils;
43
44 import uk.ac.rdg.resc.jstyx.types.DirEntry;
45 import uk.ac.rdg.resc.jstyx.types.Qid;
46 import uk.ac.rdg.resc.jstyx.types.ULong;
47
48 /***
49 * Class representing a file (or directory) on a Styx server. There may
50 * be different types of file; a file might map directly to a file on disk, or
51 * it may be a synthetic file representing a program interface. This class
52 * creates a StyxFile that does nothing useful, returning errors when reading
53 * from or writing to it. Subclasses should override the read(), write()
54 * and getLength() methods to implement the desired behaviour.
55 *
56 * Currently each StyxFile has exactly one parent. Therefore symbolic links
57 * on the host filesystem cannot currently be handled.
58 *
59 * @author Jon Blower
60 * $Revision: 602 $
61 * $Date: 2006-03-21 09:06:15 +0000 (Tue, 21 Mar 2006) $
62 * $Log$
63 * Revision 1.25 2006/03/21 09:06:15 jonblower
64 * Still implementing authentication
65 *
66 * Revision 1.24 2006/03/20 17:51:50 jonblower
67 * Adding authentication to base JStyx system
68 *
69 * Revision 1.23 2006/02/17 09:22:32 jonblower
70 * Added rename() method
71 *
72 * Revision 1.22 2006/01/04 16:47:29 jonblower
73 * Reworked getName() and getFullPath()
74 *
75 * Revision 1.21 2005/12/01 08:21:56 jonblower
76 * Fixed javadoc comments
77 *
78 * Revision 1.20 2005/11/04 19:33:41 jonblower
79 * Changed contentsChanged() to fileContentsChanged() in StyxFileChangeListener
80 *
81 * Revision 1.19 2005/11/03 21:50:04 jonblower
82 * Added clarification to comments
83 *
84 * Revision 1.18 2005/09/08 07:08:59 jonblower
85 * Removed "String user" from list of parameters to StyxFile.write()
86 *
87 * Revision 1.17 2005/08/30 16:29:00 jonblower
88 * Added processAndReplyRead() helper functions to StyxFile
89 *
90 * Revision 1.16 2005/07/06 17:42:47 jonblower
91 * Changed getUniqueID() to be based on creation time in addition to file name
92 *
93 * Revision 1.15 2005/05/10 08:02:18 jonblower
94 * Changes related to implementing MonitoredFileOnDisk
95 *
96 * Revision 1.14 2005/05/09 07:12:52 jonblower
97 * Clarified some comments
98 *
99 * Revision 1.13 2005/04/28 08:11:15 jonblower
100 * Modified permissions handling in documentation directory of SGS
101 *
102 * Revision 1.12 2005/04/27 16:11:43 jonblower
103 * Added capability to add documentation files to SGS namespace
104 *
105 * Revision 1.11 2005/03/24 14:47:47 jonblower
106 * Provided default read() and write() methods for StyxFile so it is no longer abstract
107 *
108 * Revision 1.10 2005/03/24 09:48:32 jonblower
109 * Changed 'count' from long to int throughout for reading and writing
110 *
111 * Revision 1.9 2005/03/24 07:57:41 jonblower
112 * Improved code for reading SSL info from SGSconfig file and included parameter
113 * information for the Grid Services in the config file
114 *
115 * Revision 1.8 2005/03/19 21:47:02 jonblower
116 * Further fixes relating to releasing ByteBuffers
117 *
118 * Revision 1.7 2005/03/18 16:45:18 jonblower
119 * Released ByteBuffers after use
120 *
121 * Revision 1.6 2005/03/18 13:56:00 jonblower
122 * Improved freeing of ByteBuffers, and bug fixes
123 *
124 * Revision 1.5 2005/03/16 22:16:43 jonblower
125 * Added Styx Grid Service classes to core module
126 *
127 * Revision 1.4 2005/03/16 17:56:24 jonblower
128 * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
129 *
130 * Revision 1.3 2005/03/11 14:02:16 jonblower
131 * Merged MINA-Test_20059309 into main line of development
132 *
133 * Revision 1.2.2.2 2005/03/10 11:53:54 jonblower
134 * Modified for MINA framework
135 *
136 * Revision 1.2.2.1 2005/03/09 19:44:18 jonblower
137 * Changes concerned with migration to MINA
138 *
139 * Revision 1.2 2005/03/01 13:47:43 jonblower
140 * Changed default user and group to 'user' and 'group'
141 *
142 * Revision 1.1.1.1 2005/02/16 18:58:32 jonblower
143 * Initial import
144 *
145 */
146 public class StyxFile
147 {
148
149 protected String name;
150 protected StyxDirectory parent;
151
152
153 protected boolean directory;
154 private boolean appendOnly;
155 private boolean exclusive;
156 protected boolean auth;
157
158 private int permissions;
159
160 private long version;
161
162
163
164 private long creationTime;
165
166 private long lastAccessTime;
167 protected long lastModifiedTime;
168 private String owner;
169 private String group;
170 private String lastModifiedBy;
171
172 private Vector clients;
173
174 private Vector changeListeners;
175
176
177 /***
178 * @todo check that the name is valid (no trailing or leading slashes unless
179 * it is the root directory, no spaces)
180 * @todo according to the Manual, the parent of the root of the tree is itself
181 * @throws StyxException if an attempt is made to create a file with the name
182 * "", "." or ".."
183 */
184 public StyxFile(String name, String owner, String group, int permissions,
185 boolean isAppendOnly, boolean isExclusive)
186 throws StyxException
187 {
188 name = name.trim();
189 if (name.equals("") || name.equals(".") || name.equals(".."))
190 {
191 throw new StyxException("illegal file name");
192 }
193 this.parent = null;
194 this.name = name;
195 this.directory = false;
196 this.auth = false;
197 this.permissions = permissions;
198 this.appendOnly = isAppendOnly;
199 this.exclusive = isExclusive;
200 this.version = 0;
201 this.creationTime = System.currentTimeMillis();
202 this.lastAccessTime = StyxUtils.now();
203 this.lastModifiedTime = StyxUtils.now();
204 this.owner = owner.trim();
205 this.group = group.trim();
206 this.lastModifiedBy = "";
207 this.clients = new Vector();
208 this.changeListeners = new Vector();
209 }
210
211 /***
212 * Creates a StyxFile with the default username and group
213 */
214 public StyxFile(String name, int permissions, boolean isAppendOnly,
215 boolean isExclusive) throws StyxException
216 {
217
218 this(name, "user", "group", permissions, isAppendOnly, isExclusive);
219 }
220
221 public StyxFile(String name, int permissions)
222 throws StyxException
223 {
224 this(name, permissions, false, false);
225 }
226
227 /***
228 * Creates a StyxFile with the default permissions (0666, rw-rw-rw-)
229 */
230 public StyxFile(String name) throws StyxException
231 {
232 this(name, 0666);
233 }
234
235 /***
236 * @return the name of the file, or the empty string if this is the root
237 * directory
238 */
239 public String getName()
240 {
241 return this.name;
242 }
243
244 /***
245 * @throws StyxException if the name of this file cannot be changed
246 */
247 public void checkSetName(String newName) throws StyxException
248 {
249 return;
250 }
251
252 /***
253 * Changes the name of the file. Must have checked for correct permissions
254 * before doing this. Also must call checkSetName() to check if this method
255 * will succeed.
256 */
257 public void setName(String name)
258 {
259 this.name = name.trim();
260 }
261
262 /***
263 * @return true if this StyxFile is a directory (should always return the
264 * same result as "instanceof StyxDirectory")
265 */
266 public boolean isDirectory()
267 {
268 return this.directory;
269 }
270
271 public boolean isAppendOnly()
272 {
273 return this.appendOnly;
274 }
275
276 /***
277 * @return true if this file is marked for exclusive use
278 */
279 public boolean isExclusive()
280 {
281 return this.exclusive;
282 }
283
284 public boolean isAuth()
285 {
286 return this.auth;
287 }
288
289 /***
290 * Gets the full path relative to the root of this file system.
291 */
292 public String getFullPath()
293 {
294 if (this.auth)
295 {
296
297 return this.getName();
298 }
299 else
300 {
301 return this.parent.getFullPath() + this.getName();
302 }
303 }
304
305 /***
306 * @return the length of the file. This default implementation returns
307 * zero; subclasses should override this method
308 */
309 public ULong getLength()
310 {
311 return ULong.ZERO;
312 }
313
314 /***
315 * Returns the parent of this file. The parent of the root directory is
316 * the root itself (according to the Inferno manual)
317 */
318 public StyxDirectory getParent()
319 {
320 return this.parent;
321 }
322
323 public String getOwner()
324 {
325 return this.owner;
326 }
327
328 public String getGroup()
329 {
330 return this.group;
331 }
332
333 public synchronized DirEntry getDirEntry()
334 {
335 return new DirEntry(this.getQid(), this.getMode(), this.lastAccessTime,
336 this.lastModifiedTime, this.getLength(), this.name, this.owner,
337 this.group, this.lastModifiedBy);
338 }
339
340 public synchronized Qid getQid()
341 {
342
343 int type = (int)(this.getType() >> 24);
344 return new Qid(type, this.version, this.getUniqueID());
345 }
346
347 /***
348 * Gets the type of the file as a number representing the OR of DMDIR,
349 * DMAPPEND, DMEXCL and DMAUTH as appropriate, used to create the Qid
350 */
351 private long getType()
352 {
353 long type = 0;
354 if (this.directory)
355 {
356 type |= StyxUtils.DMDIR;
357 }
358 if (this.appendOnly)
359 {
360 type |= StyxUtils.DMAPPEND;
361 }
362 if (this.exclusive)
363 {
364 type |= StyxUtils.DMEXCL;
365 }
366 if (this.auth)
367 {
368 type |= StyxUtils.DMAUTH;
369 }
370 return type;
371 }
372
373 /***
374 * @return the permissions of the file as an integer (e.g. 0755). Does not
375 * include the DMDIR, DMAPPEND, DMEXCL, DMAUTH flags (check these with the
376 * accessor methods isDirectory(), isAppendOnly(), isExclusive(), isAuth()
377 */
378 public int getPermissions()
379 {
380 return this.permissions;
381 }
382
383 /***
384 * Sets the permissions of the file
385 * @param permissions the permissions of the file as an integer (e.g. 0755).
386 */
387 public void setPermissions(int permissions)
388 {
389 this.permissions = permissions;
390 }
391
392 /***
393 * Makes the file read-only (e.g. rwxrwxr-x gets turned to r-xrr-xr-x).
394 */
395 public void setReadOnly()
396 {
397 this.permissions |= 0555;
398 }
399
400 /***
401 * Checks to see if this file allows the mode (permissions plus flags) of
402 * the file to be changed. This is called when the server receives a Twstat
403 * message. This default implementation does nothing.
404 * @param newMode the new mode of the file (permissions plus any other flags
405 * such as DMDIR, DMAPPEND, DMEXCL, DMAUTH)
406 * @throws StyxException if the mode of this file cannot be changed
407 */
408 public void checkSetMode(long newMode) throws StyxException
409 {
410 return;
411 }
412
413 /***
414 * Sets the mode of the file (permissions plus other flags). Must check all
415 * relevant permissions and call checkSetMode() before calling this method
416 * as the system will assume that this method will always succeed.
417 */
418 public void setMode(long newMode)
419 {
420 this.appendOnly = ((newMode & StyxUtils.DMAPPEND) == StyxUtils.DMAPPEND);
421 this.exclusive = ((newMode & StyxUtils.DMEXCL) == StyxUtils.DMEXCL);
422 this.auth = ((newMode & StyxUtils.DMAUTH) == StyxUtils.DMAUTH);
423 this.permissions = (int)(newMode & 1023);
424 }
425
426 /***
427 * Check to see if the length of this file can be changed to the given
428 * value. If this does not throw an exception then the setLength() method
429 * will be assumed to be guaranteed to succeed. Subclasses should make sure
430 * that this method throws an exception if this StyxFile is a directory.
431 * This default implementation always throws an exception; subclasses should
432 * override this method if they wish to allow the length of this file to
433 * be changed.
434 */
435 public void checkSetLength(ULong newLength) throws StyxException
436 {
437 throw new StyxException("cannot change the length of this file directly");
438 }
439
440 /***
441 * Sets the length of the file. Should check for all relevant
442 * permissions and call checkSetLength() before calling this. The
443 * system will assume that this method is guaranteed to succeed (it throws
444 * no exceptions and returns no value). This default implementation does
445 * nothing; subclasses should override it.
446 */
447 public void setLength(ULong newLength)
448 {
449 return;
450 }
451
452 /***
453 * @throws StyxException if the last modified time cannot be changed directly
454 * (i.e. with a Twstat message)
455 */
456 public void checkSetLastModifiedTime(long lastModifiedTime) throws StyxException
457 {
458 return;
459 }
460
461 /***
462 * Sets the last modified time of the file. Should check for all relevant
463 * permissions and, for a Twstat message, call checkSetLastModifiedTime()
464 * before calling this. The system will assume that this method is guaranteed
465 * to succeed (it throws no exceptions and returns no value).
466 * This method also always sets the last access time to the same value.
467 * @param lastModifiedTime time as represented in a stat entry (e.g. in a
468 * TwstatMessage), i.e. the number of seconds (not milliseconds) since the
469 * epoch (Jan 1 00:00 1970 GMT)
470 * @param user The user who is modifying the file
471 */
472 public void setLastModified(long lastModifiedTime, User user)
473 {
474 this.lastModifiedTime = lastModifiedTime;
475 this.setLastAccessTime(lastModifiedTime);
476 this.lastModifiedBy = user.getUsername();
477 }
478
479 public void setLastAccessTime(long lastAccessTime)
480 {
481 this.lastAccessTime = lastAccessTime;
482 }
483
484 /***
485 * Renames this file to the given name. If a file with the given name already
486 * exists in the parent directory this method will throw a StyxException.
487 * Also throws a StyxException if this is the root directory.
488 */
489 public void rename(String newName) throws StyxException
490 {
491 if (this.getParent() == null && this.getParent() == this)
492 {
493 throw new StyxException("Cannot change the name of the root directory");
494 }
495 else
496 {
497
498
499 if (this.getParent().childExists(newName))
500 {
501 throw new StyxException("A file with name " + newName +
502 " already exists in this directory");
503 }
504 else
505 {
506 this.name = newName;
507 }
508 }
509 }
510
511 /***
512 * Gets the unique numeric ID for the path of this file (generated from the
513 * low-order bytes of the creation time and the hashcode of the full path).
514 * If the file is deleted and re-created the unique ID will change (except
515 * for the extremely unlikely case in which the low-order bytes of the creation
516 * time happen to be the same in the new file and the old file).
517 */
518 private long getUniqueID()
519 {
520
521 long timeBytes = this.creationTime & 0xffffffffL;
522
523
524 return (this.getFullPath().hashCode() << 32) | timeBytes;
525 }
526
527 /***
528 * Gets the mode of this file (permissions and flags)
529 */
530 private long getMode()
531 {
532 return this.getType() | this.permissions;
533 }
534
535 /***
536 * Reads data from this file. This method could be synchronized in subclasses,
537 * but watch out for blocks if the read is expected to take some time to
538 * complete. Subclasses must make sure they reply to the read request by
539 * creating a java.nio.ByteBuffer or byte array of data, then calling the
540 * appropriate readReply() (this can be done at any time; it does not have
541 * to be done within the read() method).
542 *
543 * This default implementation simply throws a StyxException, which will
544 * result in an Rerror message being sent back to the client. Subclasses
545 * should override this to provide the desired behaviour when the file is
546 * read.
547 *
548 * @param client The client that is performing the read
549 * @param offset The point in the file at which to start reading
550 * @param count The maximum number of bytes to read
551 * @param tag The tag of the incoming Tread message (this is needed when
552 * calling readReply())
553 */
554 public void read(StyxFileClient client, long offset, int count, int tag)
555 throws StyxException
556 {
557 throw new StyxException("Cannot read from this file");
558 }
559
560 /***
561 * Writes data to this file. Must check that the file is open for writing
562 * before this. We have already dealt with the possibility that this is an
563 * append-only file before calling this method so subclasses do not need to
564 * check this. Subclasses must make sure they reply to the write request by
565 * calling writeReply() (although this can be done at any time; it does not
566 * have to be done within the write() method).
567 *
568 * After this method is called, ByteBuffer containing the data will be
569 * returned to the pool. If subclasses wish to keep the ByteBuffer after
570 * this method is complete, they should call data.acquire() to increase
571 * the reference count to the buffer.
572 *
573 * This default implementation simply throws a StyxException, which will
574 * result in an Rerror message being sent back to the client. Subclasses
575 * should override this to provide the desired behaviour when the file is
576 * written to.
577 *
578 * @param client The client that is performing the write operation
579 * @param offset The place in the file where the new data will be added
580 * @param count The number of bytes to write
581 * @param data The data to write. The position and limit of this ByteBuffer
582 * will be set correctly, but subclasses should note that the position might
583 * not be zero.
584 * @param truncate If this is true the file will be truncated at the end of
585 * the new data
586 * @param tag The tag of the incoming Twrite message (this is needed when
587 * calling writeReply())
588 */
589 public void write(StyxFileClient client, long offset, int count,
590 ByteBuffer data, boolean truncate, int tag)
591 throws StyxException
592 {
593 throw new StyxException("Cannot write to this file");
594 }
595
596 /***
597 * Refreshes this file (if it represents another entity, such as a file on disk,
598 * this method is used to make sure that the file metadata (length, access
599 * time etc) are up to date. This default implementation does nothing;
600 * subclasses must override this to provide the correct functionality
601 */
602 public synchronized void refresh()
603 {
604 return;
605 }
606
607 /***
608 * Removes this file from the Styx server
609 */
610 public synchronized void remove() throws StyxException
611 {
612
613 this.delete();
614
615 this.getParent().removeChild(this);
616 return;
617 }
618
619 /***
620 * Called when the file is removed from the server. Aborts all outstanding
621 * i/o and frees any resources associated with the file. This default
622 * implementation does nothing; subclasses should implement appropriate
623 * methods if necessary.
624 */
625 protected synchronized void delete()
626 {
627 return;
628 }
629
630 /***
631 * Adds the given client to the file's list of connected clients.
632 * Records the mode with which the client has the file open. Fires the
633 * clientConnected() event
634 */
635 public void addClient(StyxFileClient client)
636 {
637 this.clients.add(client);
638 this.clientConnected(client);
639 }
640
641 /***
642 * Called after a client connects to this file (i.e. opens it).
643 * This default implementation does nothing, but subclasses might want to
644 * catch this event and do something, e.g. open file handles when the first
645 * client has connected (i.e. when getNumClients() == 0)
646 */
647 protected void clientConnected(StyxFileClient client)
648 {
649 return;
650 }
651
652 /***
653 * Gets the StyxFileClient associated with the given Session and fid, or null
654 * if client does not exist
655 */
656 public StyxFileClient getClient(ProtocolSession session, long fid)
657 {
658 synchronized (this.clients)
659 {
660 for (int i = 0; i < this.clients.size(); i++)
661 {
662 StyxFileClient client = (StyxFileClient)this.clients.get(i);
663 if ( (client.getSession() == session) && (client.getFid() == fid) )
664 {
665 return client;
666 }
667 }
668 }
669 return null;
670 }
671
672 /***
673 * @return The number of clients that have this file open (rememember that
674 * several open handles to this file might exist on the same connection.
675 * This essentially counts the number of unique client/fid pairs)
676 */
677 public int getNumClients()
678 {
679 synchronized(this.clients)
680 {
681 this.removeDeadClients();
682 return this.clients.size();
683 }
684 }
685
686 /***
687 * Checks for clients that have disconnected or been lost and removes them.
688 * This may not be necessary if we can trust the architecture to reliably
689 * determine when a client has disconnected, in which case this method
690 * just adds unnecessary overhead.
691 * @todo do logging to find out whether this method is ever necessary?
692 */
693 private void removeDeadClients()
694 {
695 synchronized(this.clients)
696 {
697
698 for (int i = 0; i < this.clients.size(); i++)
699 {
700 StyxFileClient client = (StyxFileClient)this.clients.get(i);
701 ProtocolSession session = client.getSession();
702 if (session == null || !session.isConnected())
703 {
704 this.removeClient(client);
705 }
706 }
707 }
708 }
709
710 /***
711 * Removes the client that is connected on the given session. Fires the
712 * clientDisconnected event. If client is null, this will do nothing
713 */
714 public void removeClient(StyxFileClient client)
715 {
716 if (client != null)
717 {
718 this.clients.remove(client);
719 this.clientDisconnected(client);
720 }
721 }
722
723 /***
724 * Called after a client disconnects from this file (i.e. clunks the fid).
725 * This default implementation does nothing, but subclasses might want to
726 * catch this event and do something, e.g. close file handles when the last
727 * client has disconnected (i.e. when getNumClients() == 0).
728 * @param client The client that has just disconnected from the file. This
729 * will not be null.
730 */
731 protected void clientDisconnected(StyxFileClient client)
732 {
733 return;
734 }
735
736 /***
737 * Method for processing a read request and replying appropriately to the
738 * client, based on the contents of the file. This method is used
739 * for StyxFiles whose contents can be represented as a String (i.e. fairly
740 * short files).
741 * @param fileContents String representing the <b>entire contents</b> of
742 * the file.
743 * @param client the StyxFileClient making the request
744 * @param offset the index of the first byte in the file to return to the client
745 * @param count the maximum number of bytes to return to the client
746 * @param tag the tag of the incoming read message
747 */
748 public void processAndReplyRead(String fileContents, StyxFileClient client,
749 long offset, int count, int tag)
750 {
751
752 byte[] bytes = StyxUtils.strToUTF8(fileContents);
753 this.processAndReplyRead(bytes, client, offset, count, tag);
754 }
755
756 /***
757 * Method for processing a read request and replying appropriately to the
758 * client, based on the contents of the file. This method is used
759 * for StyxFiles whose contents can be represented as a byte array.
760 * @param fileContents Byte array representing the <b>entire contents</b> of
761 * the file.
762 * @param client the StyxFileClient making the request
763 * @param offset the index of the first byte in the file to return to the client
764 * @param count the maximum number of bytes to return to the client
765 * @param tag the tag of the incoming read message
766 */
767 public void processAndReplyRead(byte[] fileContents, StyxFileClient client,
768 long offset, int count, int tag)
769 {
770
771 if (offset >= fileContents.length)
772 {
773
774 this.replyRead(client, new byte[0], tag);
775 }
776 else
777 {
778
779 int numBytesToReturn = Math.min(fileContents.length - (int)offset, count);
780
781 this.replyRead(client, fileContents, (int)offset, numBytesToReturn, tag);
782 }
783 }
784
785 /***
786 * Method for processing a read request and replying appropriately to the
787 * client, based on the contents of the file. This method is used
788 * for StyxFiles whose contents can be represented as a ByteBuffer.
789 * @param fileContents ByteBuffer representing the <b>entire contents</b> of
790 * the file. The position and limit of this buffer will be unchanged by this
791 * method. This can be null: in this case all read requests will return zero bytes.
792 * @param client the StyxFileClient making the request
793 * @param offset the index of the first byte in the file to return to the client
794 * @param count the maximum number of bytes to return to the client
795 * @param tag the tag of the incoming read message
796 */
797 public void processAndReplyRead(ByteBuffer fileContents, StyxFileClient client,
798 long offset, int count, int tag)
799 {
800 if (fileContents == null || offset >= fileContents.limit())
801 {
802
803
804 this.replyRead(client, new byte[0], tag);
805 }
806 else
807 {
808 int numBytesToReturn = Math.min(fileContents.limit() - (int)offset, count);
809
810 int oldPos = fileContents.position();
811 int oldLimit = fileContents.limit();
812
813
814 fileContents.position((int)offset);
815 fileContents.limit((int)offset + numBytesToReturn);
816 this.replyRead(client, fileContents, tag);
817
818 fileContents.position(oldPos);
819 fileContents.limit(oldLimit);
820 }
821 }
822
823 /***
824 * Method to reply to a Read message. One of the replyRead() methods
825 * must be called by all subclasses when sending data back to the client in
826 * response to a read request.
827 * @param client The connection on which the reply will be sent
828 * @param bytes The data to include in the message. All the data in this
829 * array will be written
830 * @param tag The tag to be attached to the message
831 */
832 protected void replyRead(StyxFileClient client, byte[] bytes, int tag)
833 {
834 this.replyRead(client, bytes, 0, bytes.length, tag);
835 }
836
837 /***
838 * Method to reply to a Read message. One of the replyRead() methods
839 * must be called by all subclasses when sending data back to the client in
840 * response to a read request. Leaves the position of the input ByteBuffer
841 * unchanged.
842 * @param client The connection on which the reply will be sent
843 * @param buf a java.nio.ByteBuffer containing the data to write to the file.
844 * All the remaining data in the buffer will be sent back to the client.
845 * @param tag The tag to be attached to the message
846 */
847 protected void replyRead(StyxFileClient client, java.nio.ByteBuffer buf, int tag)
848 {
849 byte[] bytes;
850 if (buf.hasArray())
851 {
852
853 bytes = buf.array();
854
855 this.replyRead(client, bytes, buf.position(), buf.remaining(), tag);
856 }
857 else
858 {
859
860 int oldPos = buf.position();
861 bytes = new byte[buf.remaining()];
862 buf.get(bytes);
863 buf.position(oldPos);
864 this.replyRead(client, bytes, tag);
865 }
866 }
867
868 /***
869 * Method to reply to a Read message. One of the replyRead() methods
870 * must be called by all subclasses when sending data back to the client in
871 * response to a read request. Leaves the position of the input ByteBuffer
872 * unchanged. The buffer that is provided to this method will be released
873 * automatically so users of this method should not release the buffer
874 * themselves.
875 * @param client The connection on which the reply will be sent
876 * @param buf a org.apache.mina.common.ByteBuffer containing the data to
877 * write to the file. All the remaining data in the buffer will be sent
878 * back to the client.
879 * @param tag The tag to be attached to the message
880 */
881 protected void replyRead(StyxFileClient client, ByteBuffer buf, int tag)
882 {
883 RreadMessage rReadMsg = new RreadMessage(buf);
884 rReadMsg.setTag(tag);
885 this.replyRead(client, rReadMsg);
886 }
887
888 /***
889 * Method to reply to a Read message. One of the replyRead() methods
890 * must be called by all subclasses when sending data back to the client in
891 * response to a read request.
892 * @param client The connection on which the reply will be sent
893 * @param bytes The data to include in the message.
894 * @param pos The index of the first byte in the array to be written
895 * @param count The number of bytes in the array to write
896 * @param tag The tag to be attached to the message
897 */
898 protected void replyRead(StyxFileClient client, byte[] bytes, int pos,
899 int count, int tag)
900 {
901 RreadMessage rReadMsg = new RreadMessage(bytes, pos, count);
902 rReadMsg.setTag(tag);
903 this.replyRead(client, rReadMsg);
904 }
905
906 /***
907 * Method to reply to a Read message. One of the replyRead() methods
908 * must be called by all subclasses when sending data back to the client in
909 * response to a read request.
910 * @param client The connection on which the reply will be sent
911 * @param rReadMsg The RreadMessage to send back to the client. The tag of
912 * this message must be set correctly.
913 */
914 protected void replyRead(StyxFileClient client, RreadMessage rReadMsg)
915 {
916 ProtocolSession session = client.getSession();
917 StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
918 synchronized (sessionState)
919 {
920 int tag = rReadMsg.getTag();
921
922 if (sessionState.tagInUse(tag))
923 {
924 this.setLastAccessTime(StyxUtils.now());
925 session.write(rReadMsg);
926 sessionState.releaseTag(tag);
927 }
928 }
929 }
930
931 /***
932 * Method to reply to a Write message. This must be called by all subclasses
933 * when sending data back to the client in response to a write request.
934 * @param client The connection on which the reply will be sent
935 * @param count The number of bytes actually written to the file in question.
936 * @param tag The tag to be attached to the message
937 */
938 protected void replyWrite(StyxFileClient client, int count, int tag)
939 {
940 ProtocolSession session = client.getSession();
941 StyxSessionState sessionState = (StyxSessionState)session.getAttachment();
942 synchronized (sessionState)
943 {
944
945 if (sessionState.tagInUse(tag))
946 {
947 this.setLastModified(StyxUtils.now(), sessionState.getUser());
948 this.contentsChanged();
949 RwriteMessage rWriteMsg = new RwriteMessage(count);
950 rWriteMsg.setTag(tag);
951 session.write(rWriteMsg);
952 sessionState.releaseTag(tag);
953 }
954 }
955 }
956
957 public long getVersion()
958 {
959 return this.version;
960 }
961
962 /***
963 * Call this to indicate that the file's data have changed. This is called
964 * automatically when the file is written to with a TwriteMessage. This
965 * default implementation simply increments the version number with a call
966 * to this.incrementVersion(). Subclasses may override this method, for
967 * example to notify waiting clients that the data have changed. This will
968 * fire the contentsChanged() event on any registered StyxFileChangeListeners.
969 */
970 public void contentsChanged()
971 {
972 this.incrementVersion();
973 this.fireContentsChanged();
974 }
975
976 /***
977 * Increments the file's version number and ensures that the version number
978 * never exceeds the maximum value of an unsigned 4-byte integer (it wraps
979 * back to zero if the version number gets this large)
980 */
981 protected final void incrementVersion()
982 {
983 this.version++;
984 if (this.version > StyxUtils.MAXUINT)
985 {
986 this.version = 0;
987 }
988 }
989
990 /***
991 * Registers a StyxFileChangeListener. If it is already registered, this
992 * method does nothing.
993 */
994 public void addChangeListener(StyxFileChangeListener listener)
995 {
996 synchronized(this.changeListeners)
997 {
998 if (!this.changeListeners.contains(listener))
999 {
1000 this.changeListeners.add(listener);
1001 }
1002 }
1003 }
1004
1005 /***
1006 * Removes a change listener. If the given change listener is not registered,
1007 * this method does nothing.
1008 */
1009 public void removeChangeListener(StyxFileChangeListener listener)
1010 {
1011 synchronized(this.changeListeners)
1012 {
1013 this.changeListeners.remove(listener);
1014 }
1015 }
1016
1017 /***
1018 * This method is called when the contents of the file change. This can be
1019 * due to two things: (1) When a client writes to the file using a
1020 * TwriteMessage. In this case this method is called just before the Rwrite
1021 * message is sent to the client. (2) It is also called when
1022 * this.contentsChanged() is called.
1023 */
1024 protected void fireContentsChanged()
1025 {
1026 synchronized(this.changeListeners)
1027 {
1028 StyxFileChangeListener listener;
1029 for (int i = 0; i < this.changeListeners.size(); i++)
1030 {
1031 listener = (StyxFileChangeListener)this.changeListeners.get(i);
1032 listener.fileContentsChanged();
1033 }
1034 }
1035 }
1036 }