View Javadoc

1   /*$Id: IterationSixTreeClient.java,v 1.3 2005/01/13 11:27:39 jdt Exp $
2    * Created on 05-Nov-2004
3    *
4    * Copyright (C) AstroGrid. All rights reserved.
5    *
6    * This software is published under the terms of the AstroGrid 
7    * Software License version 1.2, a copy of which has been included 
8    * with this distribution in the LICENSE.txt file.  
9    *
10  **/
11  package org.astrogrid.store.tree;
12  
13  import org.astrogrid.community.User;
14  import org.astrogrid.community.common.exception.CommunitySecurityException;
15  import org.astrogrid.community.common.exception.CommunityServiceException;
16  import org.astrogrid.community.common.ivorn.CommunityIvornParser;
17  import org.astrogrid.community.common.security.data.SecurityToken;
18  import org.astrogrid.community.resolver.CommunityAccountSpaceResolver;
19  import org.astrogrid.community.resolver.CommunityPasswordResolver;
20  import org.astrogrid.store.Ivorn;
21  import org.astrogrid.store.VoSpaceClient;
22  import org.astrogrid.store.delegate.StoreClient;
23  import org.astrogrid.store.delegate.StoreFile;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.Map;
35  import java.util.StringTokenizer;
36  
37  /*** Implementation of the TreeClient interfaces against the Iteration6 VoSpace client.
38   * 
39   * bit fiddly in places - as the underlying storeClient and StoreFile are broken, fragile and inconsistent. Which means that we need to do a lot of fiddling with path expressions
40   * to get it to do what we want.
41   * 
42   * The call to {@link #getRoot()} gets the entire file tree for the user's myspace. We then cache this, and don't requery the server again - opoerations to create files, etc, are mirrored in our file tree.
43   * the view can be synchronized from the server by calling getRoot again.
44   *  
45   * 
46   * NB: noticed that there's no way to delete a file / folder - odd.
47   * @author Noel Winstanley nw@jb.man.ac.uk 05-Nov-2004
48   */
49  public class IterationSixTreeClient implements TreeClient {
50      /***
51       * Commons Logger for this class
52       */
53      private static final Log logger = LogFactory
54              .getLog(IterationSixTreeClient.class);
55  
56      /*** Construct a new tree client
57       * 
58       */
59      public IterationSixTreeClient() {
60  
61      }
62      
63      protected SecurityToken token;
64      protected final CommunityPasswordResolver security = new CommunityPasswordResolver();
65      protected final CommunityAccountSpaceResolver spaceResolver = new CommunityAccountSpaceResolver();
66      protected StoreClient client;
67      protected String username;
68  
69    
70      public void login(Ivorn communityIvorn, String password)
71              throws TreeClientLoginException, TreeClientServiceException {
72          if (communityIvorn == null) {
73              throw new IllegalArgumentException("Null account");
74          }
75          if (password == null) {
76              throw new IllegalArgumentException("Null password");
77          }
78          logger.info("Logging in " + communityIvorn );
79          try {           
80              logger.info("Checking password");            
81              this.token = security.checkPassword(communityIvorn.toString(),password);
82              logger.debug(token);
83              CommunityIvornParser ivornParser = new CommunityIvornParser(this.token.getAccount());
84  
85              logger.info("user object");
86              username =  ivornParser.getAccountName() ;
87              User u = new User();            
88              u.setAccount(username+ "@" + ivornParser.getCommunityName());
89              u.setToken(this.token.getToken());            
90              logger.debug(u);
91              
92              logger.info("Creating vospace client");            
93              VoSpaceClient voSpace  = new VoSpaceClient(u);
94              
95              logger.info("Creating Store Client");    
96              Ivorn vospaceIvorn = spaceResolver.resolve(communityIvorn);
97              logger.debug(vospaceIvorn);              
98              //@todo temporary work-around for bug. - if calculated ivorn ends with null, remove 'null'
99              if (vospaceIvorn.toString().endsWith("null")) {
100                 String s = vospaceIvorn.toString();
101                 vospaceIvorn = new Ivorn(s.substring(0,s.lastIndexOf("null")));
102                 logger.warn("mangled vospaceIvorn to " + vospaceIvorn);
103             }
104             client = voSpace.getDelegate(vospaceIvorn); 
105             logger.debug(client);
106             
107         } catch (CommunitySecurityException e) {
108             logger.info("login failed",e);
109             throw new TreeClientLoginException("Login failed",e);
110         } catch (CommunityServiceException e ) { //NB security client seems to throw one of these.
111                 logger.info("login failed",e);
112                 throw new TreeClientLoginException("Login failed",e);
113         } catch (Throwable e) {
114             logger.error("Failed to perform request",e);
115             throw new TreeClientServiceException("Failed to perform request",e);
116         }
117     }
118 
119     public StoreClient getStoreClient() {
120         return client;
121     }
122     
123     /***
124      * @todo need to call any kind of logout method?
125      */
126     public void logout() throws TreeClientServiceException {
127         logger.info("logging out");
128         this.token = null; 
129         this.client = null;
130         
131     }    
132 
133  
134     public SecurityToken getToken() {
135         return this.token;
136     }
137 
138     /*** queries server, rebuilds tree.
139      */
140     public Container getRoot()
141             throws TreeClientSecurityException,
142             TreeClientServiceException {
143         if (this.token == null) {
144             throw new TreeClientSecurityException("Not logged in");
145         }
146         try {
147             String rootPath = "/" + username + "*"; //NB - note no '/' between username and *, as this fails.
148             logger.info("Getting root via query " + rootPath);            
149             StoreFile container = client.getFiles(rootPath);
150             assert container != null: "Null container returned";
151             StoreFile[] children = container.listFiles();
152             assert children.length == 1 : "Found multiple roots";
153             StoreFile root = children[0];
154             assert root != null : "Root is null";
155             return new IterationSixContainer(root);            
156         } catch (IOException e) {
157             logger.fatal("could not get root",e);
158               throw new TreeClientServiceException("Could not get root",e);
159         }
160     }
161 
162     
163     /*** helper function for string mangling */
164     protected String dropTrailingSlash(String s) {
165         if (s.endsWith("/")) {
166             return s.substring(0,s.length()-1);
167         } else {
168             return s;
169         }
170     }
171 
172     /***
173      * Iteration-6 version of a  node.
174      * @author Noel Winstanley nw@jb.man.ac.uk 05-Nov-2004
175      *
176      *@modified noel #890 made this public, so AladinAdapter interface can cast and get to the path.
177          * once we drop aladinAdapter, can make this protected again.
178 
179      */
180     public class IterationSixNode implements Node {
181         
182         public IterationSixNode(StoreFile wrapped, String path) {
183             this.wrapped = wrapped;
184             this.path = path;
185             logger.debug("Creating iteration six node for:" + path);            
186         }
187         
188         /*** compute path from wrapped StoreFile, mangling the result */
189         public IterationSixNode(StoreFile wrapped) {
190             this(wrapped,"/" + dropTrailingSlash(wrapped.getPath()).trim());
191         }
192 
193         protected final StoreFile wrapped;
194         protected final String path;
195 
196         public String getName() {
197             return wrapped.getName().trim();
198         }
199 
200         public boolean isFile() {
201             return wrapped.isFile();
202         }
203 
204         public boolean isContainer() {
205             return wrapped.isFolder();
206         }
207         /***
208          *@modified noel #890 made this public, so AladinAdapter interface can cast and get to this.
209              * once we drop aladinAdapter, can make this protected again.
210 
211           */        
212         public String getPath() {
213             return path;
214         }
215         
216         public boolean equals(Object obj) {
217             IterationSixNode casted = (IterationSixNode)obj;            
218             return this.getPath().equals(casted.getPath());
219         }
220         public String toString() {
221             return "Node (IterationSix) for " + (isFile() ? "file " : "folder ") + getPath();
222         }
223     }
224     /*** 
225      * Iteration 6 version of a container
226      * @author Noel Winstanley nw@jb.man.ac.uk 05-Nov-2004
227      *
228      */
229     protected class IterationSixContainer extends IterationSixNode implements Container {
230 
231         /*** Construct a new IterationSixContainer
232          * @param wrapped
233          */
234         public IterationSixContainer(StoreFile wrapped) {
235             super(wrapped);
236             buildChildren();
237         }
238 
239         private Map childMap;
240         /*** populate the children of the map - will recurse */
241         private final void buildChildren() {
242             StoreFile[] children = wrapped.listFiles();
243             childMap = new HashMap(children.length);
244             for (int i = 0; i < children.length; i++) {
245                 StoreFile f = children[i];
246                 logger.debug("found child:" + f.getName());
247                 IterationSixNode n = f.isFile() ?  (IterationSixNode)new IterationSixFile(f) : (IterationSixNode)new IterationSixContainer(f);
248                 childMap.put(n.getName(),n);
249                 }                
250             }            
251 
252         public Collection getChildNodes()  {
253             return Collections.unmodifiableCollection(childMap.values());
254         }
255 
256         public Container addContainer(String name) throws TreeClientServiceException, TreeClientDuplicateException {
257             if (name == null || name.trim().length() == 0) {
258                 throw new IllegalArgumentException("Cannot create unnamed container");
259             }
260             String targetPath = getPath() + "/" + name.trim();
261             logger.info("Will add container " + targetPath);
262             try {
263                 if (client.getFile(targetPath) != null){ //although this returns no useful info, it does tell us if the file exists.
264                     logger.info("duplicate of " + targetPath + " found");
265                     throw new TreeClientDuplicateException(targetPath);
266                 }
267                 client.newFolder(targetPath);
268                 IterationSixNode newFolder = findNewNode(targetPath);
269                 childMap.put(newFolder.getName(),newFolder);
270                 return (Container)newFolder;
271             } catch (IOException e) {
272                 logger.error("addContainer(" + targetPath+")",e);
273                 throw new TreeClientServiceException("addContainer(" + targetPath+")",e);
274             }
275         }
276 
277         public File addFile(String name) throws TreeClientServiceException, TreeClientDuplicateException {
278             if (name == null || name.trim().length() == 0) {
279                 throw new IllegalArgumentException("Cannot create unnamed file");
280             }
281             String targetPath = getPath() + "/" + name.trim();
282             logger.info("Will add file " + targetPath);
283             OutputStream os = null;
284             try {
285             if (client.getFile(targetPath) != null) {//although this returns no useful info, it does tell us if the file exists.
286                 logger.info("duplicate of " + targetPath + " found");
287                 throw new TreeClientDuplicateException(targetPath);
288             }
289             //  there isn't a method to create an empty file. - so we just open an output stream to it, and close again.
290             os = client.putStream(targetPath,false);
291             IterationSixNode newFile = findNewNode(targetPath);
292             childMap.put(newFile.getName(),newFile);
293             return (File)newFile;
294             } catch (IOException e) {
295                 logger.error("addFile(" + targetPath +")",e);
296                 throw new TreeClientServiceException("addFile(" + targetPath + ")",e);
297             } finally {
298                 if (os != null) {
299                     try {
300                         os.close();
301                     }catch (IOException e) {
302                         logger.warn("failed to close output stream",e);
303                     }
304                 }
305             }
306                 
307         }
308         
309         /*** We've just created a new node. Now want to access the store file for that node, so it can be spliced into our tree.
310          * 
311          * lots of problems here - see  {@link org.astrogrid.store.adapter.aladin.integration.TestOddBehaviourOfStoreClient} for detals. 
312          * <br>StoreClient.getFile(targetPath) won't return any information in the file object.
313          * <br>StoreClient.getFiles(targetPath) throws an exception if targeetPath contains a '/' 
314          * only option is to re-query root, traverse returned tree using target path, and splice target node into existing tree. CRAP!
315          * @param targetPath the path to the newly created node
316          * @return the new storeFile.
317          * @throws TreeClientServiceException
318          * @todo fix StoreClient so that this can be done efficiently.
319          */
320         private IterationSixNode findNewNode(String targetPath) throws  TreeClientServiceException {
321             /* throws
322             StoreFile container = client.getFiles(targetPath);
323             assert container != null:"null search result returned";
324             StoreFile[] contents = container.listFiles();
325             assert contents != null && contents.length == 1: "expected a single search result";
326             return contents[0];
327             */
328             try {
329                 StringTokenizer tok = new StringTokenizer(targetPath,"/");
330                 tok.nextToken(); // discard root - e.g. 'frog'
331                 
332                 IterationSixNode current = (IterationSixNode)getRoot(); // start from the root
333                 while (tok.hasMoreTokens()) {
334                     String name = tok.nextToken();
335                     current = (IterationSixNode)  ((IterationSixContainer)current).childMap.get(name); 
336                 }
337                 return current;
338             } catch (TreeClientSecurityException e) {
339                 // can't possibly happen, but must catch - as return type of this method's clients won't allow us to propagate it. 
340                 logger.fatal("findNewNode - not logged in",e);
341                 throw new IllegalStateException("find new Node - not logged in");
342             }
343         }
344     }
345     
346 
347     
348     /*** 
349      * Iteration 6 implementation of  a file.
350      * @author Noel Winstanley nw@jb.man.ac.uk 05-Nov-2004
351      *
352      */
353     protected class IterationSixFile extends IterationSixNode implements File {
354 
355         /*** Construct a new IterationSixFile
356          * @param wrapped
357          */
358         public IterationSixFile(StoreFile wrapped) {
359             super(wrapped);
360         }
361 
362         public String getMimeType() {
363             return wrapped.getMimeType();
364         }
365 
366         public OutputStream getOutputStream() throws TreeClientServiceException {
367             try {
368                 return client.putStream(this.getPath(),false);
369             } catch (IOException e) {
370                 logger.error("Could not get output stream for " + this.getPath(),e);
371                 throw new TreeClientServiceException("Could not get output stream for " + this.getPath(),e);
372             }
373         }
374 
375 
376         public InputStream getInputStream() throws TreeClientServiceException {
377             try {
378                 return client.getStream(this.getPath());
379             } catch (IOException e) {
380                 logger.error("Could not get input stream for " + this.getPath(),e);
381                 throw new TreeClientServiceException("Could not get input stream for " + this.getPath(),e);
382             }
383             
384         }
385     }
386     
387 
388 }
389 
390 
391 /* 
392 $Log: IterationSixTreeClient.java,v $
393 Revision 1.3  2005/01/13 11:27:39  jdt
394 Merges from myspace-nww-890
395 
396 Revision 1.2.8.1  2005/01/12 17:08:02  nw
397 had to relax permissions to get at some internals required for getURL
398 
399 Revision 1.2  2004/11/17 16:22:53  clq2
400 nww-itn07-704
401 
402 Revision 1.1.2.2  2004/11/16 17:27:58  nw
403 tidied imports
404 
405 Revision 1.1.2.1  2004/11/16 16:47:28  nw
406 copied aladinAdapter interfaces into a neutrally-named package.
407 deprecated original interfaces.
408 javadoc
409 
410 Revision 1.2  2004/11/11 17:50:42  clq2
411 Noel's aladin stuff
412 
413 Revision 1.1.2.2  2004/11/11 13:21:14  nw
414 working implementation. wasn't easy.
415 
416 Revision 1.1.2.1  2004/11/09 13:56:38  nw
417 first stab at an iteration6 implementation
418  
419 */