View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.page.document.psml;
18  
19  import java.io.File;
20  import java.io.FileNotFoundException;
21  import java.io.FilenameFilter;
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.jetspeed.cache.file.FileCache;
29  import org.apache.jetspeed.cache.file.FileCacheEntry;
30  import org.apache.jetspeed.cache.file.FileCacheEventListener;
31  import org.apache.jetspeed.om.folder.Folder;
32  import org.apache.jetspeed.om.folder.FolderNotFoundException;
33  import org.apache.jetspeed.om.folder.InvalidFolderException;
34  import org.apache.jetspeed.om.folder.Reset;
35  import org.apache.jetspeed.om.folder.psml.FolderImpl;
36  import org.apache.jetspeed.om.folder.psml.FolderMetaDataImpl;
37  import org.apache.jetspeed.om.page.Document;
38  
39  import org.apache.jetspeed.page.document.DocumentHandler;
40  import org.apache.jetspeed.page.document.DocumentHandlerFactory;
41  import org.apache.jetspeed.page.document.DocumentNotFoundException;
42  import org.apache.jetspeed.page.document.FailedToDeleteFolderException;
43  import org.apache.jetspeed.page.document.FailedToUpdateFolderException;
44  import org.apache.jetspeed.page.document.FolderHandler;
45  import org.apache.jetspeed.page.document.Node;
46  import org.apache.jetspeed.page.document.NodeException;
47  import org.apache.jetspeed.page.document.NodeSet;
48  import org.apache.jetspeed.page.document.UnsupportedDocumentTypeException;
49  
50  /***
51   * <p>
52   * FileSystemFolderHandler
53   * </p>
54   * <p>
55   * Implementation of <code>FolderHanlder</code> that is based off of the file
56   * system.
57   * </p>
58   * 
59   * @author <a href="mailto:weaver@apache.org">Scott T. Weaver </a>
60   * @version $Id: FileSystemFolderHandler.java 553584 2007-07-05 18:09:45Z taylor $
61   *  
62   */
63  public class FileSystemFolderHandler implements FolderHandler, FileCacheEventListener
64  {
65  
66      private File documentRootDir;
67      private DocumentHandler metadataDocHandler;
68      private DocumentHandlerFactory handlerFactory;
69  
70      private final static Log log = LogFactory.getLog(FileSystemFolderHandler.class);
71  
72      protected static final FilenameFilter FOLDER_FILTER = new FilenameFilter()
73      {
74  
75          public boolean accept( File pathname, String fileName )
76          {
77              return new File(pathname, fileName).isDirectory();
78          }
79  
80      };
81      private FileCache fileCache;
82  
83      /***
84       * 
85       * @param documentRoot
86       *            directory on file system to use as the root when locating
87       *            folders
88       * @param handlerFactory
89       *            A <code>DocumentHandlerFactory</code>
90       * @param fileCache
91       *            For caching folder instances
92       * @throws FileNotFoundException
93       *             if the <code>documentRoot</code> does not exist
94       * @throws UnsupportedDocumentTypeException
95       *             if no <code>DocumentHnadler</code> could be found that
96       *             supports folder metadata (folder.metadata) in the
97       *             <code>handlerFactory</code>.
98       */
99      public FileSystemFolderHandler( String documentRoot, DocumentHandlerFactory handlerFactory, FileCache fileCache )
100             throws FileNotFoundException, UnsupportedDocumentTypeException
101     {
102         super();
103         this.documentRootDir = new File(documentRoot);
104         verifyPath(documentRootDir);
105         this.handlerFactory = handlerFactory;
106         this.metadataDocHandler = handlerFactory.getDocumentHandler(FolderMetaDataImpl.DOCUMENT_TYPE);
107         this.fileCache = fileCache;
108         this.fileCache.addListener(this);
109     }
110 
111     /***
112      * <p>
113      * getFolder
114      * </p>
115      * 
116      * @see org.apache.jetspeed.page.document.FolderHandler#getFolder(java.lang.String)
117      * @param path
118      * @return @throws
119      *         FolderNotFoundException
120      * @throws FolderNotFoundException
121      * @throws InvalidFolderException
122      * @throws NodeException
123      * @throws DocumentNotFoundException
124      */
125     public Folder getFolder( String path ) throws FolderNotFoundException, InvalidFolderException, NodeException
126     {
127 
128         return getFolder(path, true);
129     }
130 
131     protected void verifyPath( File path ) throws FileNotFoundException
132     {
133         if (path == null)
134         {
135             throw new IllegalArgumentException("Page root cannot be null");
136         }
137 
138         if (!path.exists())
139         {
140             throw new FileNotFoundException("Could not locate root pages path " + path.getAbsolutePath());
141         }
142     }
143 
144     /***
145      * <p>
146      * getFolder
147      * </p>
148      * 
149      * @see org.apache.jetspeed.page.document.FolderHandler#getFolder(java.lang.String,
150      *      boolean)
151      * @param path
152      * @param fromCache
153      * @return @throws
154      *         DocumentException, FolderNotFoundException
155      * @throws InvalidFolderException
156      * @throws DocumentNotFoundException
157      */
158     public Folder getFolder( String path, boolean fromCache ) throws NodeException, FolderNotFoundException, InvalidFolderException
159     {
160         Folder folder = null;
161         File folderFile = new File(documentRootDir, path);
162         if(!folderFile.exists())
163         {
164             throw new FolderNotFoundException(folderFile.getAbsolutePath()+" does not exist.");
165         }
166         
167         if(!folderFile.isDirectory())
168         {
169             throw new InvalidFolderException(folderFile.getAbsolutePath()+" is not a valid directory.");
170         }
171         
172         // cleanup trailing separators
173         if (!path.equals(Folder.PATH_SEPARATOR) && path.endsWith(Folder.PATH_SEPARATOR))
174         {
175             path = path.substring(0, path.length()-1);
176         }
177 
178         // check cache
179         if (fromCache)
180         {
181             folder = (Folder) fileCache.getDocument(path);
182         }
183 
184         // get new folder
185         if (folder == null)
186         {
187             try
188             {
189                 // look for metadata
190                 FolderMetaDataImpl metadata = (FolderMetaDataImpl) metadataDocHandler.getDocument(path + Folder.PATH_SEPARATOR + FolderMetaDataImpl.DOCUMENT_TYPE);
191                 folder = new FolderImpl(path, metadata, handlerFactory, this);
192             }
193             catch (DocumentNotFoundException e)
194             {
195                 // no metadata
196                 folder = new FolderImpl(path, handlerFactory, this);
197             }
198 
199             // recursively set parent
200             if (!path.equals(Folder.PATH_SEPARATOR))
201             {
202                 String parentPath = path;
203                 int parentSeparatorIndex = parentPath.lastIndexOf(Folder.PATH_SEPARATOR_CHAR);
204                 if (parentSeparatorIndex > 0)
205                 {
206                     parentPath = parentPath.substring(0, parentSeparatorIndex);
207                 }
208                 else
209                 {
210                     parentPath = Folder.PATH_SEPARATOR;
211                 }
212                 folder.setParent(getFolder(parentPath));
213             }
214 
215             // folder unmarshalled
216             ((FolderImpl) folder).unmarshalled();
217 
218             // add to cache
219             if (fromCache)
220             {
221                 addToCache(path, folder);
222             }
223         }
224 
225         return folder;
226     }
227 
228     /***
229      * <p>
230      * updateFolder
231      * </p>
232      * 
233      * @see org.apache.jetspeed.page.document.FolderHandler#updateFolder(org.apache.jetspeed.om.folder.Folder)
234      * @param folder
235      * @throws FailedToUpdateFolderException
236      */
237     public void updateFolder(Folder folder) throws FailedToUpdateFolderException
238     {
239         // sanity checks
240         if (folder == null)
241         {
242             log.warn("Recieved null Folder to update");
243             return;
244         }
245         String path = folder.getPath();
246         if (path == null)
247         {
248             path = folder.getId();
249             if (path == null)
250             {
251                 log.warn("Recieved Folder with null path/id to update");
252                 return;
253             }
254             folder.setPath(path);
255         }
256 
257         // setup folder implementation
258         FolderImpl folderImpl = (FolderImpl)folder;
259         folderImpl.setFolderHandler(this);
260         folderImpl.setHandlerFactory(handlerFactory);
261         folderImpl.setPermissionsEnabled(handlerFactory.getPermissionsEnabled());
262         folderImpl.setConstraintsEnabled(handlerFactory.getConstraintsEnabled());
263         folderImpl.marshalling();
264 
265         // create underlying folder if it does not exist
266         File folderFile = new File(documentRootDir, path);
267         if ((folderFile.exists() && !folderFile.isDirectory()) || (!folderFile.exists() && !folderFile.mkdir()))
268         {
269             throw new FailedToUpdateFolderException(folderFile.getAbsolutePath()+" does not exist and cannot be created.");
270         }
271 
272         // update metadata
273         try
274         {
275             FolderMetaDataImpl metadata = folderImpl.getFolderMetaData();
276             metadata.setPath(path + Folder.PATH_SEPARATOR + FolderMetaDataImpl.DOCUMENT_TYPE);
277             metadataDocHandler.updateDocument(metadata);
278         }
279         catch (Exception e)
280         {
281             throw new FailedToUpdateFolderException(folderFile.getAbsolutePath()+" failed to update folder.metadata", e);
282         }
283 
284         // add to cache
285         addToCache(path, folder);
286     }
287 
288     /***
289      * <p>
290      * removeFolder
291      * </p>
292      * 
293      * @see org.apache.jetspeed.page.document.FolderHandler#removeFolder(org.apache.jetspeed.om.folder.Folder)
294      * @param folder
295      * @throws FailedToDeleteFolderException
296      */
297     public void removeFolder(Folder folder) throws FailedToDeleteFolderException
298     {
299         // sanity checks
300         if (folder == null)
301         {
302             log.warn("Recieved null Folder to remove");
303             return;
304         }
305         String path = folder.getPath();
306         if (path == null)
307         {
308             path = folder.getId();
309             if (path == null)
310             {
311                 log.warn("Recieved Folder with null path/id to remove");
312                 return;
313             }
314             folder.setPath(path);
315         }
316 
317         // remove folder nodes
318         FolderImpl folderImpl = (FolderImpl)folder;
319         try
320         {
321             // copy all folder nodes to remove
322             List removeNodes = new ArrayList();
323             Iterator copyIter = folderImpl.getAllNodes().iterator();
324             while (copyIter.hasNext())
325             {
326                 removeNodes.add(copyIter.next());
327             }
328             
329             // remove folder nodes
330             Iterator removeIter = removeNodes.iterator();
331             while (removeIter.hasNext())
332             {
333                 Node node = (Node)removeIter.next();
334                 if (node instanceof Folder)
335                 {
336                     // recursively remove folder
337                     removeFolder((Folder)node);
338                 }
339                 else if (node instanceof Document)
340                 {
341                     // remove folder document
342                     try
343                     {
344                         handlerFactory.getDocumentHandler(node.getType()).removeDocument((Document)node);
345                     }
346                     catch (Exception e)
347                     {
348                         File documentFile = new File(this.documentRootDir, node.getPath());
349                         throw new FailedToDeleteFolderException(documentFile.getAbsolutePath()+" document cannot be deleted.");
350                     }
351                 }
352                 ((NodeSetImpl)folderImpl.getAllNodes()).remove(node);
353             }
354         }
355         catch (FailedToDeleteFolderException fdfe)
356         {
357             throw fdfe;
358         }
359         catch (Exception e)
360         {
361             throw new FailedToDeleteFolderException(e.getMessage());
362         }
363 
364         // remove underlying folder and unknown files
365         File folderFile = new File(this.documentRootDir, path);
366         File metadataFile = null;
367         if ((folderImpl.getFolderMetaData() != null) && (folderImpl.getFolderMetaData().getPath() != null))
368         {
369             metadataFile = new File(this.documentRootDir, folderImpl.getFolderMetaData().getPath());
370         }
371         if (folderFile.exists() && folderFile.isDirectory())
372         {
373             // attempt to clean folder for delete
374             String[] contents = folderFile.list();
375             for (int i = 0; (i < contents.length); i++)
376             {
377                 File contentFile = new File(folderFile, contents[i]);
378                 if ((metadataFile == null) || !contentFile.equals(metadataFile))
379                 {
380                     if (!deleteFile(contentFile))
381                     {
382                         throw new FailedToDeleteFolderException(folderFile.getAbsolutePath()+" unrecognized folder contents cannot be deleted.");
383                     }
384                 }
385             }
386             // delete folder and metadata
387             if ((metadataFile != null) && metadataFile.exists() && !metadataFile.delete())
388             {
389                 throw new FailedToDeleteFolderException(folderFile.getAbsolutePath()+" folder metadata cannot be deleted.");
390             }
391             // delete folder and all remaining folder contents
392             // unless folder is root folder which should be
393             // preserved as PSML "mount point"
394             if (!path.equals(Folder.PATH_SEPARATOR) && !folderFile.delete())
395             {
396                 throw new FailedToDeleteFolderException(folderFile.getAbsolutePath()+" folder cannot be deleted.");
397             }
398         }
399         else
400         {
401             throw new FailedToDeleteFolderException(folderFile.getAbsolutePath()+" not found.");
402         }
403 
404         // remove from cache
405         fileCache.remove(path);
406 
407         // reset folder
408         if (folderImpl.getFolderMetaData() != null)
409         {
410             folderImpl.getFolderMetaData().setParent(null);
411         }
412         folderImpl.setParent(null);
413         folderImpl.reset();
414     }
415 
416     private static final boolean deleteFile(File file)
417     {
418         if (file.isDirectory())
419         {
420             String[] children = file.list();
421             for (int i = 0; (i < children.length); i++)
422             {
423                 if (!deleteFile(new File(file, children[i])))
424                 {
425                     return false;
426                 }
427             }
428         }
429         return file.delete();
430     }
431 
432     /***
433      * <p>
434      * getFolders
435      * </p>
436      * 
437      * @see org.apache.jetspeed.page.document.FolderHandler#getFolders(java.lang.String)
438      * @param path
439      * @return @throws
440      *         FolderNotFoundException
441      * @throws FolderNotFoundException
442      * @throws InvalidFolderException
443      * @throws NodeException
444      */
445     public NodeSet getFolders( String path ) throws FolderNotFoundException, InvalidFolderException, NodeException
446     {
447         File parent = new File(documentRootDir, path);
448         if (!parent.exists())
449         {
450             throw new FolderNotFoundException("No folder exists at the path: " + parent.getAbsolutePath());
451         }
452         else
453         {
454             String[] children = getChildrenNames(path, FOLDER_FILTER);
455             NodeSetImpl folders = new NodeSetImpl(path);
456             for (int i = 0; i < children.length; i++)
457             {
458                 if (path.endsWith(Folder.PATH_SEPARATOR))
459                 {
460                     folders.add(getFolder(path + children[i]));
461                 }
462                 else
463                 {
464                     folders.add(getFolder(path + Folder.PATH_SEPARATOR + children[i]));
465                 }
466             }
467             return folders;
468         }
469     }
470 
471     public class DocumentTypeFilter implements FilenameFilter
472     {
473         private String documentType;
474 
475         public DocumentTypeFilter( String documentType )
476         {
477             this.documentType = documentType;
478         }
479 
480         /***
481          * <p>
482          * accept
483          * </p>
484          * 
485          * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
486          * @param dir
487          * @param name
488          * @return
489          */
490         public boolean accept( File dir, String name )
491         {
492             return name.endsWith(documentType);
493         }
494 
495     }
496 
497     /***
498      * <p>
499      * list
500      * </p>
501      * 
502      * @see org.apache.jetspeed.page.document.FolderHandler#list(java.lang.String)
503      * @param documentType
504      * @return @throws
505      *         FolderNotFoundException
506      */
507     public String[] list( String folderPath, String documentType ) throws FolderNotFoundException
508     {
509         return getChildrenNames(folderPath, new DocumentTypeFilter(documentType));
510     }
511 
512     /***
513      * <p>
514      * listAll
515      * </p>
516      * 
517      * @see org.apache.jetspeed.page.document.FolderHandler#listAll(java.lang.String)
518      * @param folderPath
519      * @return @throws
520      *         FolderNotFoundException
521      */
522     public String[] listAll( String folderPath ) throws FolderNotFoundException
523     {
524         return getChildrenNames(folderPath, null);
525     }
526 
527     protected String[] getChildrenNames( String path, FilenameFilter filter ) throws FolderNotFoundException
528     {
529         File parent = new File(documentRootDir, path);
530         if (!parent.exists())
531         {
532             throw new FolderNotFoundException("No folder exists at the path: " + parent.getAbsolutePath());
533         }
534         else
535         {
536             if (filter != null)
537             {
538                 return parent.list(filter);
539             }
540             else
541             {
542                 return parent.list();
543             }
544         }
545     }
546 
547     /***
548      * <p>
549      * getChildNodes
550      * </p>
551      *
552      * @see org.apache.jetspeed.page.document.FolderHandler#getNodes(java.lang.String,boolean,java.lang.String)
553      * @param path
554      * @param regexp
555      * @param documentType
556      * @return NodeSet
557      * @throws FolderNotFoundException
558      * @throws DocumentException
559      * @throws InvalidFolderException
560      * @throws NodeException
561      */
562     public NodeSet getNodes(String path, boolean regexp, String documentType)
563         throws FolderNotFoundException, InvalidFolderException, NodeException
564     {
565         // path must be valid absolute path
566         if ((path == null) || ! path.startsWith(Folder.PATH_SEPARATOR))
567         {
568             throw new InvalidFolderException( "Invalid path specified " + path );
569         }
570 
571         // traverse folders and parse path from root,
572         // accumualting matches in node set
573         Folder folder = getFolder(Folder.PATH_SEPARATOR);
574         NodeSetImpl matched = new NodeSetImpl(null);
575         getNodes(folder,path,regexp,matched);
576 
577         // return matched nodes filtered by document type
578         if (documentType != null)
579         {
580             return matched.subset(documentType);
581         }
582         return matched;
583     }
584 
585     private void getNodes(Folder folder, String path, boolean regexp, NodeSet matched)
586         throws FolderNotFoundException, InvalidFolderException, NodeException
587     {
588         // test for trivial folder match
589         if (path.equals(Folder.PATH_SEPARATOR))
590         {
591             matched.add(folder);
592             return;
593         }
594 
595         // remove leading separator
596         if (path.startsWith(Folder.PATH_SEPARATOR))
597         {
598             path = path.substring(1);
599         }
600 
601         // parse path for folder path match
602         int separatorIndex = path.indexOf(Folder.PATH_SEPARATOR);
603         if (separatorIndex != -1)
604         {
605             // match folder name
606             String folderName = path.substring(0,separatorIndex);
607             String folderPath = (folder.getPath().endsWith(Folder.PATH_SEPARATOR) ? folder.getPath() : folder.getPath() + Folder.PATH_SEPARATOR) + folderName;
608             NodeSet matchedFolders = null;
609             if (regexp)
610             {
611                 // get regexp matched folders
612                 matchedFolders = ((FolderImpl)folder).getFolders(false).inclusiveSubset(folderPath);
613             }
614             else
615             {
616                 // get single matched folder
617                 Folder matchedFolder = getFolder(folderPath);
618                 if (matchedFolder != null)
619                 {
620                     matchedFolders = new NodeSetImpl(folder.getPath());
621                     matchedFolders.add(matchedFolder);
622                 }
623             }
624             if ((matchedFolders == null) || (matchedFolders.size() == 0))
625             {
626                 throw new FolderNotFoundException("Cannot find folder" + folderName + " in " + folder.getPath());
627             }
628 
629             // match recursively over matched folders
630             path = path.substring(separatorIndex);
631             Iterator matchedFoldersIter = matchedFolders.iterator();
632             while (matchedFoldersIter.hasNext())
633             {
634                 Folder matchedFolder = (Folder) matchedFoldersIter.next();
635                 getNodes(matchedFolder, path, regexp, matched);
636             }
637             return;
638         }
639 
640         // match node name
641         String nodeName = path;
642         String nodePath = (folder.getPath().endsWith(Folder.PATH_SEPARATOR) ? folder.getPath() : folder.getPath() + Folder.PATH_SEPARATOR) + nodeName;
643         if (regexp)
644         {
645             // get regexp matched nodes
646             Iterator addIter = ((FolderImpl)folder).getAllNodes().inclusiveSubset(nodePath).iterator();
647             while (addIter.hasNext())
648             {
649                 matched.add((Node) addIter.next());
650             }
651         }
652         else
653         {
654             // get single matched node
655             Iterator findIter = ((FolderImpl)folder).getAllNodes().iterator();
656             while (findIter.hasNext())
657             {
658                 Node addNode = (Node) findIter.next();
659                 if (addNode.getPath().equals(nodePath))
660                 {
661                     matched.add(addNode);
662                     break;
663                 }
664             }
665         }
666     }
667 
668 
669     /***
670      * <p>
671      * addToCache
672      * </p>
673      * 
674      * @param id
675      * @param objectToCache
676      */
677     protected void addToCache( String id, Object objectToCache )
678     {
679         synchronized (fileCache)
680         {
681             // store the document in the hash and reference it to the
682             // watcher
683             try
684             {
685                 fileCache.put(id, objectToCache, this.documentRootDir);
686 
687             }
688             catch (java.io.IOException e)
689             {
690 
691                 String msg = "Error storing Document in the FileCache: " + e.toString();
692                 log.error(msg);
693                 IllegalStateException ise = new IllegalStateException(msg);
694                 ise.initCause(e);
695             }
696         }
697     }
698 
699     /***
700      * <p>
701      * refresh
702      * </p>
703      * 
704      * @see org.apache.jetspeed.cache.file.FileCacheEventListener#refresh(org.apache.jetspeed.cache.file.FileCacheEntry)
705      * @param entry
706      * @throws Exception
707      */
708     public void refresh( FileCacheEntry entry ) throws Exception
709     {
710         if (entry.getDocument() instanceof Folder )
711         {
712             Folder folder = (Folder) entry.getDocument();            
713             entry.setDocument(getFolder(folder.getPath(), false));
714             if (((AbstractNode)folder).getParent(false) != null)
715             {
716                 FileCacheEntry parentEntry = fileCache.get(((AbstractNode)folder).getParent(false).getPath());
717                 refresh(parentEntry);                
718             }
719         }
720         else if(entry.getDocument() instanceof Document)
721         {
722             Document doc = (Document) entry.getDocument();
723             if (doc.getType().equals(FolderMetaDataImpl.DOCUMENT_TYPE))
724             {
725                 FileCacheEntry folderEntry = fileCache.get(((AbstractNode)doc).getParent().getPath());
726                 refresh(folderEntry);
727             }
728         }
729         
730         if(entry.getDocument() instanceof Reset)
731         {
732             ((Reset)entry.getDocument()).reset();
733         }
734 
735     }
736 
737     /***
738      * <p>
739      * evict
740      * </p>
741      * 
742      * @see org.apache.jetspeed.cache.file.FileCacheEventListener#evict(org.apache.jetspeed.cache.file.FileCacheEntry)
743      * @param entry
744      * @throws Exception
745      */
746     public void evict( FileCacheEntry entry ) throws Exception
747     {
748 
749     }
750 
751     public boolean isFolder(String path)
752     {
753         return new File(this.documentRootDir, path).isDirectory();        
754     }
755 }