View Javadoc

1   package org.astrogrid.store.delegate.myspaceItn05;
2   
3   import java.io.FileNotFoundException;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.OutputStream;
7   import java.lang.reflect.Array;
8   import java.net.InetAddress;
9   import java.net.MalformedURLException;
10  import java.net.URL;
11  import java.net.UnknownHostException;
12  import java.util.ArrayList;
13  import java.util.Date;
14  import java.util.Hashtable;
15  import java.util.StringTokenizer;
16  import javax.xml.rpc.ServiceException;
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.astrogrid.community.User;
20  import org.astrogrid.store.Agsl;
21  import org.astrogrid.store.Msrl;
22  import org.astrogrid.store.delegate.StoreAdminClient;
23  import org.astrogrid.store.delegate.StoreClient;
24  import org.astrogrid.store.delegate.StoreDelegateFactory;
25  import org.astrogrid.store.delegate.StoreException;
26  import org.astrogrid.store.delegate.StoreFile;
27  
28  
29  /***
30   * <code>MySpaceIt05Delegate</code> is the delegate class which
31   * applications invoke in order to access a MySpace service.
32   *
33   * @author A C Davenhall (Edinburgh)
34   * @author C L Qin (Leicester)
35   * @since Iteration 5.  There has been a MySpace delegate since
36   *   Iteration 2.  The present delegate dates from Iteration 5.
37   * @version Iteration 5.
38   */
39  
40  //
41  // Note: Throughout this class the acronym `MSS' denotes a MySpace
42  // Service and AGSL denotes an AstroGrid Store-point Locator.
43  
44  public class MySpaceIt05Delegate implements StoreClient, StoreAdminClient {
45     
46      /***
47       * Commons logger
48       */
49      private static final Log logger = LogFactory.getLog(MySpaceIt05Delegate.class);
50  
51     /*** Axis-generated binding = inner delegate */
52     private Manager     innerDelegate = null; // Inner delegate.
53     /*** Location of Manager */
54     private Msrl managerMsrl = null; // Location of the Manager.
55     /*** Account that is operating this delegate - eg user that is browsing myspace */
56     private User operator = null;    // User of the delegate [TODO] Account?.
57  
58     private boolean isTest = false;
59     private boolean throwExceptions = true;
60     private boolean allowOverWrite = true;
61  
62     private ArrayList statusList = new ArrayList(); // List of Status messages.
63  
64  //
65  // ======================================================================
66  
67  //
68  // Constructors.
69  
70  /***
71   * Constructs delegate that is operated by the given User, to connect to the
72   * MySpace Manager at the given endPoint.
73   */
74  
75     public MySpaceIt05Delegate(User givenOperator, String givenEndPoint) throws IOException
76     {
77        this.operator = givenOperator;
78  //      System.out.println("entered MyspaceIt05Delegate: operator = "
79  //        + operator.toString() + " endpoint = " + endPoint);
80  
81        if (!givenEndPoint.startsWith(Msrl.SCHEME)) {
82           givenEndPoint = Msrl.SCHEME+":"+givenEndPoint;
83        }
84        
85        managerMsrl = new Msrl(givenEndPoint);
86        
87        logger.debug("the endpoint in myspaceitn05delegate = " + managerMsrl.getDelegateEndpoint());
88        try
89        {  ManagerService service = new ManagerServiceLocator();
90           innerDelegate = service.getAstrogridMyspace(managerMsrl.getDelegateEndpoint());
91        }
92        catch (ServiceException e)
93        {  throw new IOException
94             ("Failed to connect to: " + givenEndPoint );
95        }
96     }
97     
98  
99  //
100 // ======================================================================
101 //
102 // Get, set and related methods.
103 //
104 // These methods are mostly concerned with configuring the internal
105 // state of the delegate and with retrieving information from it.
106 
107 
108 /***
109  * Specify whether the Manager is being used in test or genuine mode.
110  *
111  * In test mode the Manager will return standard responses, irrespective
112  * of whether it contains any entries.  In genuine mode the Manager
113  * genuinely attempts to manipulate its data holdings.
114  *
115  * @param isTest Flag indicating whether in test or genuine mode: true for
116  *    test mode and false otherwise.
117  */
118 
119    public void setTest(boolean isTest)
120    {  this.isTest = isTest;
121    }
122 
123 
124 /***
125  * Specify whether a bad status being returned by the Manager will
126  * cause the delegate to throw an exception.
127  *
128  * @param thowExceptions Flag indicating whether or not bad status
129  *    returns cause an exception to be throw: true to throw exceptions
130  *    and false otherwise.
131  */
132 
133    public void setThrow(boolean throwExceptions)
134    {  this.throwExceptions = throwExceptions;
135    }
136 
137 
138 /***
139  * Specify whether the Manager is allowed to overwrite an existing
140  * file when importing a new one.
141  *
142  * param allowOverWrite Flag indicating whether or not overwriting is
143  *   allowed.  True indicates that it is.
144  */
145 
146    public void setOverWrite(boolean allowOverWrite)
147    {  this.allowOverWrite = allowOverWrite;
148    }
149 
150 
151 /***
152  * Return a list of error codes accummulated by successive
153  * invocations of action methods.
154  *
155  * @return List of error codes, each of type <code>StatusMessage</code>.
156  */
157 
158    public ArrayList getStatusList()
159    {  return statusList;
160    }
161 
162 /***
163  * Convenience method to list the error codes.  The codes accummulated
164  * by successive invocations of action methods to standard output.  Note
165  * that this method does not reset the list of error codes.
166  */
167 
168    public void outputStatusList()
169    {  int numMessages = statusList.size();
170 
171       if (numMessages > 0)
172       {  for(int loop=0; loop<numMessages; loop++)
173          {  StatusMessage message =
174              (StatusMessage)statusList.get(loop);
175             logger.debug(message.toString() );
176          }
177       }
178       else
179       {  logger.debug("No messages returned.");
180       }
181    }
182 
183 /***
184  * Reset the list of error codes to contain zero entries.
185  */
186 
187    public void resetStatusList()
188    {  statusList.clear();
189    }
190 
191 
192 //
193 // ======================================================================
194 //
195 // StoreClient methods.
196 //
197 // The following Action methods implement the StoreClient interface.
198 
199 /***
200  * Return the User of this delegate - ie the account it is being used by
201  *
202  * @return The User of this delegate.
203  */
204    public User getOperator()
205    {  return operator;
206    }
207    
208 // ----------------------------------------------------------------------
209 
210 /***
211  * Return the Agsl of the service to which this client is connected.
212  *
213  * @return The Agsl of the service to which this client is connected.
214  */
215    public Agsl getEndpoint()
216    {
217       Agsl agsl = new Agsl( managerMsrl );
218       return agsl;
219    }
220 
221    
222 // ----------------------------------------------------------------------
223 
224    /***
225     * Helper function for converting EntryResults to MySpaceFile/Folder
226     * returns the name (ie last path token) of an EntryResults
227     */
228    public String getNameOf(EntryResults entryResult) {
229       String path = entryResult.getEntryName();
230 
231       //remove trailing slash if any - this just tells us that it's a directory,
232       //but not hainvg one doens't mean it isn't a directory, so ignore
233       if (path.endsWith("/")) {
234          path = path.substring(0,path.length());
235       }
236 
237       if (path.lastIndexOf("/") == -1) {
238          return path;
239       }
240       
241       return path.substring(path.lastIndexOf("/")+1);
242    }
243    
244    /***
245     * Helper function for converting EntryResults to MySpaceFile/Folder
246     * returns the path only (ie not including name) of an EntryResults
247     */
248    public String getParentPathOf(EntryResults entryResult) {
249       String fullPath = entryResult.getEntryName();
250 
251       //remove trailing slash if any - this just tells us that it's a directory,
252       //but not hainvg one doens't mean it isn't a directory, so ignore
253       if (fullPath.endsWith("/")) {
254          fullPath = fullPath.substring(0,fullPath.length());
255       }
256 
257       if (fullPath.lastIndexOf("/") == -1) {
258          return "";
259       }
260       
261       return fullPath.substring(0,fullPath.lastIndexOf("/"));
262    }
263    /***
264     * Helper function for converting EntryResults to MySpaceFile/Folder
265     * Creates a MySpaceFile/Folder from the given entry result
266     */
267    public StoreFile makeStoreFile(MySpaceFolder parent, EntryResults result) {
268 logger
269         .info("makeStoreFile(MySpaceFolder, EntryResults) - FROG : makeStoreFile()");
270 logger.info("makeStoreFile(MySpaceFolder, EntryResults) -   Name"
271         + result.getEntryName());
272 logger.info("makeStoreFile(MySpaceFolder, EntryResults) -   Size"
273         + result.getSize());
274 
275       StoreFile file;
276       if (result.getType() == EntryCodes.CON) {
277          if (parent == null) {
278             file = new MySpaceFolder();
279          }
280          else {
281             file = new MySpaceFolder(parent, result);
282          }
283       }
284       else {
285          if (parent == null) {
286             //make up a folder that has the right path
287             parent = new MySpaceFolder(getParentPathOf(result));
288             
289             file = new MySpaceFile(parent, result);
290          }
291          else {
292             file = new MySpaceFile(parent, result);
293          }
294       }
295 logger.info("makeStoreFile(MySpaceFolder, EntryResults) -   Size"
296         + file.getSize());
297       return file;
298    }
299 
300    /***
301  * Return a tree representation of the files that match the expression
302  */
303 
304    public StoreFile getFiles(String filter) throws IOException
305    {
306       //returns a list of the files that match the expression
307       KernelResults results = innerDelegate.getEntriesList(filter, isTest);
308 
309       this.appendAndCheckStatusMessages(results);
310 
311       if (results == null) {
312          return null;
313       }
314       
315       //turn list into a tree of StoreFiles
316       
317       Object[] fileList =  results.getEntries();
318 
319       if (fileList == null) {
320          return null;
321       }
322       
323       MySpaceFolder root = new MySpaceFolder();
324       
325       //represent entries
326       for (int r=0;r<fileList.length;r++) {
327          
328          EntryResults result = (EntryResults) fileList[r];
329          
330          //Work out parent. Assumes parent folders appear in list before the children do.
331          MySpaceFolder parentFolder;
332          //Start with finding parent part of path
333          String path = result.getEntryName();
334          //remove trailing slash if any - this just tells us that it's a directory,
335          //but not hainvg one doens't mean it isn't a directory, so ignore
336          if (path.endsWith("/")) {
337             path = path.substring(0,path.length());
338          }
339          //remove starting slash as we don't care if it has one of those or not
340          if (path.startsWith("/")) {
341             path = path.substring(1);
342          }
343          if (path.indexOf("/")==-1) {
344             //no other slashes - must be a root directory file
345             parentFolder = root;
346          }
347          else {
348             String parentPath = path.substring(0, path.lastIndexOf("/"));
349             parentFolder = (MySpaceFolder) root.findFile(parentPath);
350          }
351 
352 
353          //work out type & create right instance
354          StoreFile file = makeStoreFile(parentFolder, result);
355          parentFolder.add(file);
356       }
357       
358       return root;
359    }
360 
361 
362 // ----------------------------------------------------------------------
363 
364 /*
365  * Return a list of all the files that match the expression
366  *
367  * @param filter Filter (or query) which the files must match.  Queries
368  *   take the form of entry names which may optionally include a
369  *   wild-card character.  This wild-card character is an asterisk and
370  *   it must occur at the end of the name.
371  * @return Array of <code>EntryRecord</code>s which satisfy the query.
372  *   If no entries satisfy the query a null value is returned.
373  *
374 
375    public StoreFile[] listFiles(String filter) throws IOException
376    {
377       KernelResults results = innerDelegate.getEntriesList(filter, isTest);
378 
379       this.appendAndCheckStatusMessages(results);
380 
381       if (results == null) {
382          return null;
383       }
384 //
385 //   Assemble an array of any files which matched the query.
386 //   Each file is returned as an EntryResults object, which is
387 //   converted to the corresponding EntryRecord.
388 
389       //copy list into a list of StoreFiles
390       Object[] resultList = results.getEntries();
391       StoreFile[] fileList = new StoreFile[resultList.length];
392       
393       for(int loop=0; loop<resultList.length; loop++)
394       {
395          fileList[loop] = makeStoreFile(null, (EntryResults) resultList[loop]);
396       }
397 
398       return fileList;
399    }
400 */
401 
402 // ----------------------------------------------------------------------
403 
404 /***
405  * Returns the StoreFile representation of the file at the given AGSL
406  */
407 
408    public StoreFile getFile(String path) throws IOException
409    {
410 logger.info("getFile(String) - FROG : getFile()");
411 logger.info("getFile(String) -   Path" + path);
412 
413       if (!path.startsWith("/")) path = "/"+path;
414       
415       KernelResults results = innerDelegate.getEntriesList(path, isTest);
416 
417 //
418 //   Append and check any status messages.
419 
420       this.appendAndCheckStatusMessages(results);
421 
422 //
423 //   Obtain the file which matched the query.  This file is taken
424 //   to be the first entry in the return array.
425 
426       if (results.getEntries() == null) {
427          return null;
428       }
429       else  {
430          return makeStoreFile(null, (EntryResults) results.getEntries()[0]);
431       }
432    }
433 
434 
435    /***
436     * Returns the Agsl for the given source path.  NB = no such thing for
437     * folders at the moment
438     */
439    public Agsl getAgsl(String sourcePath) throws IOException {
440       assert (!sourcePath.endsWith("/") && !sourcePath.endsWith("//")) : "Cannot create AGSLs for folders (yet)";
441       
442       return new Agsl(getEndpoint(), sourcePath);
443    }
444   
445    
446    
447 // ----------------------------------------------------------------------
448 
449 /***
450  * Put the given byte buffer from offset of length bytes, to the given target
451  */
452    public void putBytes(byte[] bytes, int offset, int length,
453      String targetPath, boolean append) throws IOException
454    {
455 
456       if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
457 //
458 //   Determine how a pre-existing file with the specified name is
459 //   to be dispatched.
460 
461       int dispatchExisting = ManagerCodes.LEAVE;
462       if (append)
463       {  dispatchExisting = ManagerCodes.APPEND;
464       }
465       else
466       {  if (this.allowOverWrite)
467          {  dispatchExisting = ManagerCodes.OVERWRITE;
468          }
469          else
470          {  dispatchExisting = ManagerCodes.LEAVE;
471          }
472       }
473 
474 //
475 //   Extract the subset of the array which is to be sent.
476 
477       int numBytes = length - offset;
478       byte[] subsetToSend = new byte[length];
479 
480       for (int loop=0; loop<numBytes; loop++)
481       {  subsetToSend[loop] = bytes[loop + offset];
482       }
483 
484 //
485 //   Attempt to save the array of bytes as a file.
486 //m
487 //   [TODO] The current StoreClient interface has no mechanism for
488 //   passing the category of file (VOTable etc.), so here it is set
489 //   to UNKNOWN.
490 
491       KernelResults results = innerDelegate.putBytes(targetPath,
492         subsetToSend, EntryCodes.UNKNOWN, dispatchExisting, isTest);
493 
494 //
495 //   Append and check any status messages.
496 
497       this.appendAndCheckStatusMessages(results);
498 
499    }
500 
501 
502 // ----------------------------------------------------------------------
503 
504 /***
505  * Put the given string into the given location
506  */
507    public void putString(String contents, String targetPath, boolean append)
508       throws IOException
509    {
510 
511       if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
512 //
513 //   Determine how a pre-existing file with the specified name is
514 //   to be dispatched.
515 
516       int dispatchExisting = ManagerCodes.LEAVE;
517       if (append)
518       {  dispatchExisting = ManagerCodes.APPEND;
519       }
520       else
521       {  if (this.allowOverWrite)
522          {  dispatchExisting = ManagerCodes.OVERWRITE;
523          }
524          else
525          {  dispatchExisting = ManagerCodes.LEAVE;
526          }
527       }
528 
529 //
530 //   Attempt to save the String as a file.
531 //
532 //   [TODO] The current StoreClient interface has no mechanism for
533 //    passing the category of file (VOTable etc.), so here it is set
534 //    to UNKNOWN.
535 
536       KernelResults results = innerDelegate.putString(targetPath,
537         contents, EntryCodes.UNKNOWN, dispatchExisting, isTest);
538 
539 //
540 //   Append and check any status messages.
541 
542       this.appendAndCheckStatusMessages(results);
543 
544    }
545 
546 
547 // ----------------------------------------------------------------------
548 
549 /***
550  * Copy the contents of the file at the given source url to the given
551  * location.
552  */
553    public void putUrl(URL source, String targetPath, boolean append)
554      throws IOException
555    {
556 
557       if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
558 //
559 //   Determine how a pre-existing file with the specified name is
560 //   to be dispatched.
561 
562       int dispatchExisting = ManagerCodes.LEAVE;
563       if (append)
564       {  dispatchExisting = ManagerCodes.APPEND;
565       }
566       else
567       {  if (this.allowOverWrite)
568          {  dispatchExisting = ManagerCodes.OVERWRITE;
569          }
570          else
571          {  dispatchExisting = ManagerCodes.LEAVE;
572          }
573       }
574 
575 //
576 //   Attempt to save the URL as a file.
577 //
578 //   [TODO] The current StoreClient interface has no mechanism for
579 //    passing the category of file (VOTable etc.), so here it is set
580 //    to UNKNOWN.
581 
582       String uri = source.toString();
583       KernelResults results = innerDelegate.putUri(targetPath,
584         uri, EntryCodes.UNKNOWN, dispatchExisting, isTest);
585 
586 //
587 //   Append and check any status messages.
588 
589       this.appendAndCheckStatusMessages(results);
590 
591    }
592 
593 
594 // ----------------------------------------------------------------------
595 
596 /***
597  * Streaming output - returns a stream that can be used to output to the
598  * given location.
599  */
600 
601    public OutputStream putStream(String targetPath, boolean append)
602      throws IOException
603    {
604       if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
605       
606       return new MySpaceOutputStream(targetPath, append);
607    }
608 
609 
610 // ----------------------------------------------------------------------
611 
612 /***
613  * Get a file's contents as a stream
614  */
615    public InputStream getStream(String sourcePath) throws IOException
616    {
617 
618      if (!sourcePath.startsWith("/")) sourcePath = "/"+sourcePath;
619 
620       /*
621 //
622 //  [TODO]: This implementation is really a placeholder.  The
623 //   entire file contents are returned from the Manager in one call
624 //   and then merely streamed from a String in the delegate.
625 //   Returning the contents from the Manager to the delegate should
626 //   be streamed.
627 
628       byte[] contents = this.getBytes(sourcePath);
629 
630       ByteArrayInputStream inStream =
631         new ByteArrayInputStream(contents);
632 
633       return inStream;
634        */
635 
636       //streamed implementation, but getUrl is deprecated...  Need some
637       //sort of getBytes() a bit at a time.
638       URL url = getUrl(sourcePath);
639 
640       if (url == null)
641       {  throw new FileNotFoundException(
642            "Failed to find URL for path: " + sourcePath+" on "+managerMsrl);
643       }
644 
645       return url.openStream();
646    }
647 
648 
649 // ----------------------------------------------------------------------
650    
651 /***
652  * Get the URL to the given file.  This is a short term method; urls are not
653     * appropriate to access private files
654  */
655 
656    public URL getUrl(String path) throws IOException
657    {
658 logger.info("getUrl(String)");
659 logger.info("getUrl(String) - MySPaceDeletage.getUrl");
660 logger.info("getUrl(String) -   Path" + path);
661 
662       if (!path.startsWith("/")) path = "/"+path;
663       
664       KernelResults results = innerDelegate.getEntriesList(path, isTest);
665 
666       this.appendAndCheckStatusMessages(results);
667 
668       if ((results == null)  || (results.getEntries() == null)) {
669          return null;
670       }
671 
672       Object[] entries = results.getEntries();
673       EntryResults entry = (EntryResults) entries[0];
674       
675       //this seems to return a localhost
676       URL url = new URL(entry.getEntryUri());
677 logger.info("getUrl(String) -   URL " + url.toString());
678       
679       // >>> HACK ALERT!
680       // >>> @author peter.shillan
681       // This "hack" gets around the problem of being returned localhost URLs.
682       // The preferred way is to configure MySpace with the full server name.
683       // The problem only arises if the servers are co-located and the solution works
684       // in these circumstances.
685       String host = url.getHost();
686 logger.info("getUrl(String) -   Host" + host);
687 
688 
689       // If localhost is returned, try to work out the real host name.
690       if(host.equalsIgnoreCase("localhost")) {
691         String protocol = url.getProtocol();
692         int port = url.getPort();
693         String urlPath = url.getPath();
694         
695         try {
696           InetAddress addr = InetAddress.getLocalHost();
697           String fullHostName = addr.getCanonicalHostName();
698           url = new URL(protocol, fullHostName, port, urlPath);
699         }
700         catch(UnknownHostException e) {
701           // Default url will be used.
702         }
703       }
704       // <<< HACK ALERT!
705 logger.info("getUrl(String) -   URL " + url.toString());
706       
707       return url;
708    }
709 
710 
711 // ----------------------------------------------------------------------
712 
713 /***
714  * Delete a file.
715  */
716 
717    public void delete(String deletePath) throws IOException
718    {
719       if (!deletePath.startsWith("/")) deletePath = "/"+deletePath;
720       
721       KernelResults results = innerDelegate.deleteFile(deletePath, isTest);
722 
723 //
724 //   Append and check any status messages.
725 
726       this.appendAndCheckStatusMessages(results);
727 
728    }
729 
730 
731 // ----------------------------------------------------------------------
732 
733    /***
734     * Copy a file to a target Agsl.
735     * Because myspace can 'pull' better than push, this calls the target Agsl
736     * store (if different from this one) with a putUrl to the sourcepath
737     */
738 
739    public void copy(String sourcePath, Agsl target) throws IOException
740    {
741 /*
742  *
743  *
744       if (target.getEndpoint().equals(getEndpoint())) {
745          String targetPath = target.getPath();
746          if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
747          if (!sourcePath.startsWith("/")) sourcePath = "/"+sourcePath;
748       
749          KernelResults results = innerDelegate.copyFile(sourcePath,
750                targetPath, isTest);
751 //
752 //   Append and check any status messages.
753 
754          this.appendAndCheckStatusMessages(results);
755 
756       }
757       else {
758          StoreClient targetStore = StoreDelegateFactory.createDelegate(operator, target);
759          targetStore.putUrl(getUrl(sourcePath), target.getPath(), false);
760 
761       }
762  *
763  *
764  */
765 logger.info("copy(String, Agsl) - ----");
766 logger.info("copy(String, Agsl) - MySpaceIt05Delegate.copy");
767 logger.info("copy(String, Agsl) -   Source path : '" + sourcePath + "'");
768 logger.info("copy(String, Agsl) -   Target agsl : '" + target.toString() + "'");
769 
770       URL url = getUrl(sourcePath) ;
771 // BANG !!
772 logger.info("copy(String, Agsl) -   Source URL  : '" + url.toString() + "'");
773 
774 logger.info("copy(String, Agsl)");
775 logger.info("copy(String, Agsl) - Creating target delegate ....");
776       StoreClient targetStore =
777          StoreDelegateFactory.createDelegate(
778             operator,
779             target
780             );
781 logger.info("copy(String, Agsl) - Done");
782 
783 logger.info("copy(String, Agsl)");
784 logger.info("copy(String, Agsl) - Calling target delegate ....");
785       targetStore.putUrl(
786          url,
787          target.getPath(),
788          false
789          );
790 logger.info("copy(String, Agsl) - Done");
791 
792    }
793 
794 
795 // ----------------------------------------------------------------------
796 
797 /***
798  * Copy a file from a source Agsl.
799  */
800 
801    public void copy(Agsl source, String targetPath) throws IOException
802    {
803       if (source.getEndpoint().equals(getEndpoint())) {
804          String sourcePath = source.getPath();
805          if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
806          if (!sourcePath.startsWith("/")) sourcePath = "/"+sourcePath;
807       
808          KernelResults results = innerDelegate.copyFile(sourcePath, targetPath, isTest);
809 
810 //
811 //   Append and check any status messages.
812 
813          this.appendAndCheckStatusMessages(results);
814       }
815       else
816       {
817          StoreClient sourceStore = StoreDelegateFactory.createDelegate(operator, source);
818          
819          putUrl(sourceStore.getUrl(source.getPath()), targetPath, false);
820       }
821    }
822 
823 // ----------------------------------------------------------------------
824    
825 /***
826  * Move (or rename) a file to a target Agsl.
827  */
828 
829    public void move(String sourcePath, Agsl target) throws IOException
830    {
831       //could call innerdelegate move if target/source stores are the same
832       
833       copy(sourcePath, target);
834       delete(sourcePath);
835    }
836 
837 
838 // ----------------------------------------------------------------------
839 
840 /***
841  * Moves (or rename) a file from a source Agsl.
842  */
843 
844    public void move(Agsl source, String targetPath) throws IOException
845    {
846       //could call innerdelegate move if target/source stores are the same
847       
848       copy(source, targetPath);
849       StoreDelegateFactory.createDelegate(operator, source);
850    }
851 
852 
853 // ----------------------------------------------------------------------
854    
855 /***
856  * Create a container.
857  */
858 
859    public void newFolder(String targetPath) throws IOException
860    {
861       if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
862 
863       KernelResults results = innerDelegate.createContainer(targetPath, isTest);
864 
865 //
866 //   Append and check any status messages.
867 
868       this.appendAndCheckStatusMessages(results);
869    }
870 
871 
872 //
873 // ======================================================================
874 //
875 // StoreAdminClient methods.
876 //
877 // The following Action methods implement the StoreAdminClient interface.
878 
879 /***
880  * Create a new account on an MSS.
881  *
882  * <p>
883  * `Create a new account' is something of a misnomer.  This method is
884  * really just creating the base set of containers which every account
885  * needs.
886  * </p>
887  *
888  * @param newAccount Account of the user to be created.
889  */
890 
891    public void createUser(User newAccount) throws IOException
892    {  String account = newAccount.getUserId();
893       KernelResults results = innerDelegate.createAccount(
894         account, isTest);
895 
896 //
897 //   Append and check any status messages.
898 
899       this.appendAndCheckStatusMessages(results);
900 
901    }
902 
903 
904 // ----------------------------------------------------------------------
905 
906 /***
907  * Delete an account from an MSS.
908  *
909  * <p>
910  * `Delete an account' is something of a misnomer.  The method merely
911  * removes all extant containers belonging to an account.  Any files
912  * belonging to the account must have been removed previously.  However,
913  * the method will remove any remaining arbitrarily complex tree of
914  * containers.  If any files are found the account will be left
915  * untouched.
916  * </p>
917  *
918  * @param deadAccount Details of the account to be deleted.
919  */
920 
921    public void deleteUser(User deadAccount) throws IOException
922    {  String account = deadAccount.getUserId();
923       KernelResults results = innerDelegate.deleteAccount(
924         account, isTest);
925 
926 //
927 //   Append and check any status messages.
928 
929       this.appendAndCheckStatusMessages(results);
930 
931    }
932 
933 
934 // ======================================================================
935 
936 //
937 // Additional Action methods not in the StoreClient or StoreAdminClient
938 // interfaces.
939 
940 /***
941  * `Heart-beat' method to check whether the Manager is up and running.
942  *
943  * @return Return true if the Manager is responding; otherwise return
944  *  false.
945  */
946 
947    public boolean heartBeat() throws IOException
948    {  boolean response = false;
949 
950       try
951       {  String result = innerDelegate.heartBeat();
952          if (result.equals("Adsum.") )
953          {  response = true;
954          }
955       }
956       catch (Exception all)
957       {  response = false;
958       }
959 
960       return response;
961    }
962 
963 
964 // ----------------------------------------------------------------------
965 
966 /***
967  * Return the contents of a file as a String.
968  *
969  * @param targetPath Path to the file whose contents are to be
970  *    retrieved.
971  * @return The contents of the file.
972  * @deprecated (extremely) - will break on large files.  Use getStream() instead.
973  */
974  public String getString(String targetPath) throws IOException
975    {  String contents = "";
976 
977      if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
978 //
979 //   Attempt to retrieve the contents of the file as a String.
980 
981       KernelResults results = innerDelegate.getString(targetPath, isTest);
982 
983 //
984 //   Obtain the retrieved contents from the results object.
985 
986       if (results.getContentsString() != null)
987       {  contents = results.getContentsString();
988       }
989 
990 //
991 //   Append and check any status messages.
992 
993       this.appendAndCheckStatusMessages(results);
994 
995       return contents;
996    }
997 
998 
999 // ----------------------------------------------------------------------
1000 
1001 /***
1002  * Return the contents of a file as an array of bytes.
1003  *
1004  * @param targetPath Path to the file whose contents are to be
1005  *    retrieved.
1006  * @return The contents of the file.
1007  * @deprecated (extremely) - will break on large files.  Use getStream() instead.
1008  */
1009    public byte[] getBytes(String targetPath) throws IOException
1010    {  byte[] contents = null;
1011 
1012      if (!targetPath.startsWith("/")) targetPath = "/"+targetPath;
1013 //
1014 //   Attempt to retrieve the contents of the file as a String.
1015 
1016       KernelResults results = innerDelegate.getBytes(targetPath,
1017         isTest);
1018 
1019 //
1020 //   Obtain the retrieved contents from the results object.
1021 
1022       if (results.getContentsBytes() != null)
1023       {  contents = results.getContentsBytes();
1024       }
1025 
1026 //
1027 //   Append and check any status messages.
1028 
1029       this.appendAndCheckStatusMessages(results);
1030 
1031       return contents;
1032    }
1033 
1034 
1035 // ----------------------------------------------------------------------
1036 
1037 /***
1038  * Extend the lifetime of a file in an MSS.
1039  *
1040  * <p>
1041  * This method operates on multiple AGSLs by permitting wild-cards in
1042  * the AGSL path.
1043  * </p>
1044  *
1045  * @param fileName File name of the entry to be modified.
1046  * @param newExpiryDate New expiry date for the file.
1047  */
1048 
1049    public void extendLifetime(String fileName, Date newExpiryDate)
1050      throws IOException
1051    {  long expiry = newExpiryDate.getTime();
1052       KernelResults results = innerDelegate.extendLifetime(
1053         fileName, expiry, isTest);
1054 
1055 //
1056 //   Append and check any status messages.
1057 
1058       this.appendAndCheckStatusMessages(results);
1059 
1060    }
1061 
1062 
1063 // ----------------------------------------------------------------------
1064 
1065 /***
1066  * Change the owner of a MySpace file.
1067  *
1068  * <p>
1069  * This method operates on multiple AGSLs by permitting wild-cards in
1070  * the AGSL path.
1071  * </p>
1072  *
1073  * @param path Path (incl wild cards) of fiels to be modified.
1074  * @param newOwner Account of the new owner.
1075  */
1076 
1077    public void changeOwner(String path, User newOwner)
1078      throws IOException
1079    {  String owner = newOwner.getAccount();
1080       KernelResults results = innerDelegate.changeOwner(
1081         path, owner, isTest);
1082 
1083 //
1084 //   Append and check any status messages.
1085 
1086       this.appendAndCheckStatusMessages(results);
1087 
1088    }
1089 
1090 
1091 // ======================================================================
1092 
1093 //
1094 // Additional internal methods.
1095 
1096 /***
1097  * Given a results object returned by the inner delegate, the array
1098  * of status results are extracted, appended to the delegate's internal
1099  * list and checked to see whether any correspond to an error.
1100  *
1101  * @param results Results object returned by the inner delegate.
1102  */
1103 
1104    private void appendAndCheckStatusMessages(KernelResults results)
1105      throws StoreException
1106    {  boolean errorRaised = false;
1107       String messageFromManager = "none.";
1108 
1109       Object[] statusResults = results.getStatusList();
1110       int numStatus = Array.getLength(statusResults);
1111 
1112       if (numStatus > 0)
1113       {  StatusMessage status = new StatusMessage();
1114 
1115          for(int loop=0; loop<numStatus; loop++)
1116          {
1117 //
1118 //         Convert each statusResults object to a StatusMessage.
1119 
1120             status = new StatusMessage( (StatusResults)statusResults[loop] );
1121 
1122 //
1123 //         Add the StatusMessage to the accummulating list.
1124 
1125             this.statusList.add(status);
1126 
1127 //
1128 //         Check whether any errors have been raised, and if so preserve
1129 //         the text associated with the first.
1130 
1131             if (status.getSeverity() == StatusCodes.ERROR)
1132             {  if (!errorRaised)
1133                {  messageFromManager = status.getMessage();
1134                }
1135                errorRaised = true;
1136             }
1137          }
1138       }
1139 
1140       if (errorRaised && throwExceptions)
1141       {  throw new StoreException(messageFromManager);
1142       }
1143    }
1144 
1145 
1146 // ----------------------------------------------------------------------
1147 
1148 /***
1149  * Special OutputStream which writes to a String, then sends it when the
1150  * stream is closed.
1151  */
1152 
1153    private class MySpaceOutputStream extends OutputStream
1154    {
1155     /***
1156      * Commons Logger for this class
1157      */
1158     private final Log logger = LogFactory.getLog(MySpaceOutputStream.class);
1159   private String targetPath = null;
1160 
1161       private byte[] buffer = new byte[32000];
1162       private int cursor = 0;  //insert point
1163 
1164 
1165       public MySpaceOutputStream(String aTargetPath, boolean append)
1166         throws IOException
1167       {  this.targetPath = aTargetPath;
1168 
1169 //
1170 //      If the new stream is not being appended to an existing file
1171 //      then attempt to overwrite any existing file.
1172 
1173          if (!append)
1174          {  putString("", targetPath, false);
1175          }
1176       }
1177 
1178       /*** OutputStream method implementation.  All writes go through this method -
1179        * it batches up the low byte of each character 'i' into a buffer, and
1180        * when the buffer is full 'flushes' it to the server using writeString()
1181        * with append on.
1182        */
1183       public void write(int i) throws IOException
1184       {
1185 //
1186 //      Get low byte - streams do not do words.
1187 
1188          buffer[cursor] = (byte) (i & 0xFF);
1189          
1190          cursor++;
1191          if (cursor >= buffer.length)
1192          {  flush();
1193          }
1194       }
1195 
1196 
1197       public void flush() throws IOException
1198       {
1199 //
1200 //      Append string to file - cursor = length
1201 
1202          putBytes(buffer, 0, cursor, targetPath, true);
1203          
1204          cursor=0;
1205       }
1206 
1207       /*** For human readable debugging */
1208       public String toString() {
1209          return "MySpaceOutputStream ["+managerMsrl+"#"+targetPath+"]";
1210       }
1211 
1212       public void close() throws IOException
1213       {  flush();
1214          super.close();
1215       }
1216    }
1217 
1218    
1219    /***
1220     * Inner class that implements StoreFile.  It's useful to have it as an
1221     * inner class so that it can access private methods of the delegate to
1222     * getParent, children, etc
1223     */
1224    private class MySpaceFile implements StoreFile {
1225     /***
1226      * Commons Logger for this class
1227      */
1228     private final Log logger = LogFactory.getLog(MySpaceFile.class);
1229       
1230       String name = null; //just file or folder name, not path
1231       String owner = null;
1232       Date created = null;
1233       Date expires = null;
1234       long size = -1;
1235       String permissions = null;
1236       URL url = null;
1237 
1238       String mime = null ;
1239       MySpaceFileType type = null;
1240       MySpaceFolder parentFolder = null;
1241 
1242       /*** Constructs an empty file - suitable eg for root */
1243       private MySpaceFile() {
1244       }
1245       
1246       /***
1247        * Constructs a file from the given parent folder and the axis-generated
1248        * class returned by the binding
1249        */
1250       public MySpaceFile(MySpaceFolder parent, EntryResults bindingEntry)  {
1251    
1252          this.parentFolder = parent;
1253          this.name = getNameOf(bindingEntry);
1254          
1255          this.owner = bindingEntry.getOwnerId();
1256          this.created = new Date(bindingEntry.getCreationDate());
1257          this.expires = new Date(bindingEntry.getExpiryDate());
1258          this.size = bindingEntry.getSize();
1259          this.permissions = bindingEntry.getPermissionsMask();
1260          this.mime = bindingEntry.getMime() ;
1261          this.type = MySpaceFileType.getForManagerRef(bindingEntry.getType());
1262          try {
1263             this.url = new URL(bindingEntry.getEntryUri());
1264          }
1265          catch (MalformedURLException mue) {
1266             //log but don't crash
1267             logger.error("Server returned invalid URL "+bindingEntry.getEntryUri()+" for entry "+bindingEntry.getEntryName());
1268          }
1269       }
1270       
1271       public String getType() {        return type.toString();   }
1272       
1273       public String toString() {       return getName();   }
1274       
1275       public String getOwner() {       return owner; }
1276       
1277       /*** Returns the date the file was created */
1278       public Date getCreated() {       return created; }
1279          
1280       public Date getExpires() {       return expires; }
1281    
1282       public long getSize() {          return size; }
1283    
1284       public String getPermissions() { return permissions; }
1285    
1286       /*** Returns the mime type (null if unknown) */
1287       public String getMimeType() {    return mime ; }
1288       
1289       /*** Returns the date the file was last modified (null if unknown) */
1290       public Date getModified() {      return null;  }
1291       
1292       /*** Returns URL to the file */
1293       public URL getUrl() {            return url; }
1294       
1295       /*** Returns the path to this file on the server including filename */
1296       public String getPath() {
1297          return getParent().getPath()+name;
1298       }
1299       
1300       /*** Returns true if this is a container that can hold other files/folders */
1301       public boolean isFolder()     {     return false;   }
1302       
1303       /*** Returns true if this is a self-contained file.  For example, a database
1304        * table might be represented as a StoreFile but it is not a file */
1305       public boolean isFile() {           return true;   }
1306       
1307       /*** Returns file or folder name */
1308       public String getName() {
1309          return name;
1310       }
1311       
1312       /*** Returns the parent folder */
1313       public StoreFile getParent() {
1314          return parentFolder;
1315       }
1316    
1317       
1318       /*** Lists children files if this is a container - returns null otherwise */
1319       public StoreFile[] listFiles() {    return null;   }
1320       
1321       /*** Returns true if this represents the same file as the given one */
1322       public boolean equals(StoreFile anotherFile) {
1323          if (anotherFile instanceof MySpaceFile) {
1324             return name.equals( ((MySpaceFile) anotherFile).name) &&
1325                   parentFolder.equals(((MySpaceFile) anotherFile).parentFolder);
1326          }
1327          return false;
1328       }
1329    }
1330 
1331    /***
1332     * Represents a folder in myspace.
1333     * See also comments on @link MySpaceFile
1334     *
1335     * @author mch
1336     */
1337    private class MySpaceFolder extends MySpaceFile  {
1338     /***
1339      * Commons Logger for this class
1340      */
1341     private final Log logger = LogFactory.getLog(MySpaceFolder.class);
1342       
1343       Hashtable children = new Hashtable();
1344    
1345       boolean isRoot = false;
1346       
1347       /*** Creates a folder that is a child of another one. */
1348       public MySpaceFolder(MySpaceFolder parent, EntryResults bindingEntry)  {
1349          super(parent, bindingEntry);
1350       }
1351    
1352       /*** Creates the root folder */
1353       public MySpaceFolder() {
1354          super();
1355          isRoot = true;
1356       }
1357    
1358       /*** Creates a folder from a path */
1359       public MySpaceFolder(String givenPath) {
1360          super();
1361          if (givenPath.startsWith("/")) {
1362             givenPath = givenPath.substring(1); //chop off leading slash
1363          }
1364          this.name = givenPath;
1365       }
1366 
1367       /*** Adds the given StoreFile as a child that exists in this folder */
1368       public void add(StoreFile child) {
1369          children.put(child.getName(), child);
1370       }
1371    
1372       /*** Returns the StoreFile representation of the child with the given filename */
1373       public StoreFile getChild(String filename) {
1374          return (StoreFile) children.get(filename);
1375       }
1376       
1377       /*** Returns an array of the files in this container */
1378       public StoreFile[] listFiles() {
1379          return (StoreFile[]) (children.values().toArray(new StoreFile[] {}));
1380       }
1381    
1382       /*** Returns path on server */
1383       public String getPath() {
1384          if (isRoot) {
1385             return "";
1386          }
1387          else {
1388             if (getParent() == null) {
1389                //temporary allowance for folders that describe a path
1390                return getName()+"/";
1391             }
1392             else {
1393                return getParent().getPath()+getName()+"/";
1394             }
1395          }
1396       }
1397       
1398       /*** Returns true - this is a container */
1399       public boolean isFolder() {      return true;   }
1400       
1401       /*** Returns false - this is a container */
1402       public boolean isFile() {        return false;  }
1403       
1404       /***
1405        * Returns the folder or file matching the given path in the *children* of
1406        * this folder.  So if the path is '/famous/stuff/main/' the returned StoreFile
1407        * will be the MySpaceFolder instance representing the 'main' directory
1408        */
1409       public StoreFile findFile(String path) throws FileNotFoundException {
1410          
1411          //locate file
1412          StringTokenizer dirTokens = new StringTokenizer(path, "/");
1413          MySpaceFolder folder = this;
1414          StoreFile child = null;
1415          while (dirTokens.hasMoreTokens())
1416          {
1417             String token = dirTokens.nextToken();
1418             child = folder.getChild(token);
1419             if (child == null) {
1420                throw new FileNotFoundException("No such token '"+token+"' in path '"+path+"' from "+this);
1421             }
1422             else {
1423                if (child.isFolder()) {
1424                   folder = (MySpaceFolder) child;
1425                }
1426             }
1427          }
1428          
1429          if (dirTokens.hasMoreTokens()) {
1430             throw new FileNotFoundException("path "+path+" only partly found from "+this);
1431          }
1432          
1433          return child;
1434          
1435       }
1436       
1437    }
1438 }
1439 
1440 /*
1441 $Log: MySpaceIt05Delegate.java,v $
1442 Revision 1.12  2005/01/13 11:27:39  jdt
1443 Merges from myspace-nww-890
1444 
1445 Revision 1.11.18.1  2005/01/12 15:43:34  nw
1446 replaced System.out with logging.
1447 
1448 Revision 1.11  2004/11/07 22:19:57  mch
1449 added endpoint to exception
1450 
1451 Revision 1.10  2004/11/05 17:39:59  mch
1452 Added outputstream.toString with useful info
1453 
1454 Revision 1.9  2004/09/02 10:25:41  dave
1455 Updated FileStore and MySpace to handle mime type and file size.
1456 Updated Community deployment script.
1457 
1458 Revision 1.8.2.1  2004/09/02 00:01:45  dave
1459 Extended EntryResults and delegate MySpaceFile to handle mime type.
1460 
1461 Revision 1.8  2004/08/27 22:43:15  dave
1462 Updated filestore and myspace to report file size correctly.
1463 
1464 Revision 1.7.12.2  2004/08/27 16:17:48  dave
1465 ....
1466 
1467 Revision 1.7.12.1  2004/08/27 16:16:15  dave
1468 Added temp debug ...
1469 
1470 Revision 1.7  2004/08/18 19:00:01  dave
1471 Myspace manager modified to use remote filestore.
1472 Tested before checkin - integration tests at 91%.
1473 
1474 Revision 1.6  2004/08/05 15:37:25  mch
1475 Fix for empty myspaces
1476 
1477 Revision 1.5.26.2  2004/08/09 12:37:58  dave
1478 Added debug to delegate ...
1479 
1480 Revision 1.5.26.1  2004/08/06 22:25:03  dave
1481 Refactored bits and broke a few tests ...
1482 
1483 Revision 1.5  2004/06/29 14:20:01  gps
1484 - making clear note of the "localhost hack"
1485 
1486 Revision 1.4  2004/06/24 11:45:53  gps
1487 - removed extraneous code from getUrl()
1488 
1489 Revision 1.3  2004/06/24 11:43:08  gps
1490 - fixed 'localhost' return problem in getUrl()
1491 
1492 Revision 1.2  2004/06/14 23:08:52  jdt
1493 Merge from branches
1494 
1495 ClientServerSplit_JDT
1496 
1497 and
1498 
1499 MySpaceClientServerSplit_JDT
1500 
1501 
1502 
1503 MySpace now split into a client/delegate jar
1504 
1505 astrogrid-myspace-<version>.jar
1506 
1507 and a server/manager war
1508 
1509 astrogrid-myspace-server-<version>.war
1510 
1511 Revision 1.1.2.1  2004/06/14 22:33:20  jdt
1512 Split into delegate jar and server war.
1513 Delegate: astrogrid-myspace-SNAPSHOT.jar
1514 Server/Manager: astrogrid-myspace-server-SNAPSHOT.war
1515 
1516 Package names unchanged.
1517 If you regenerate the axis java/wsdd/wsdl files etc you'll need
1518 to move some files around to ensure they end up in the client
1519 or the server as appropriate.
1520 As of this check-in the tests/errors/failures is 162/1/22 which
1521 matches that before the split.
1522 
1523 Revision 1.29  2004/05/19 16:24:33  mch
1524 Properly typed Agsl creation, some fixes to tests
1525 
1526 Revision 1.28  2004/05/14 12:53:06  mch
1527 Added assertion to getAgsl() for folders
1528 
1529 Revision 1.27  2004/05/14 09:39:55  mch
1530 Fixed prepended myspace to endpoint msrl
1531 
1532 Revision 1.26  2004/05/12 14:48:01  mch
1533 Fixed endpoint AGSL error
1534 
1535 Revision 1.25  2004/05/12 09:01:02  mch
1536 Prepended myspace: to endpoints on constructor
1537 
1538 Revision 1.24  2004/05/04 14:36:29  jdt
1539 Javadoc comments were interfering with each other.
1540 
1541 Revision 1.23  2004/05/03 14:46:22  mch
1542 Fixes for int tests
1543 
1544 Revision 1.22  2004/05/03 13:39:40  mch
1545 Removed dependencies on EntryRecord and EntryNode
1546 
1547 Revision 1.21  2004/05/03 08:55:53  mch
1548 Fixes to getFiles(), introduced getSize(), getOwner() etc to StoreFile
1549 
1550  Added inner classes to represent files.
1551  Bug fixes: firstChunk not threadsafe
1552             constructor setting wrong endpoint
1553  */
1554