View Javadoc

1   /**
2    * Copyright (C) 2005-2009 Alfresco Software Limited.
3    *
4    * This file is part of the Spring Surf Extension project.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.springframework.extensions.webscripts;
20  
21  import java.io.File;
22  import java.io.FileFilter;
23  import java.io.FileInputStream;
24  import java.io.FileReader;
25  import java.io.FileWriter;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.Reader;
29  import java.net.MalformedURLException;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.List;
33  import java.util.regex.Pattern;
34  
35  import javax.servlet.ServletContext;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.springframework.extensions.surf.exception.PlatformRuntimeException;
40  import org.springframework.extensions.webscripts.AbstractStore;
41  import org.springframework.extensions.webscripts.ScriptContent;
42  import org.springframework.extensions.webscripts.ScriptLoader;
43  import org.springframework.extensions.webscripts.WebScript;
44  import org.springframework.web.context.ServletContextAware;
45  
46  import freemarker.cache.TemplateLoader;
47  
48  /**
49   * Simple implementation of a local store file system.
50   * 
51   * This is extremely light weight and is used as a base case for comparing other
52   * store performance vs. the local file system.
53   * 
54   * @author muzquiano
55   */
56  public class LocalFileSystemStore extends AbstractStore implements ServletContextAware
57  {
58      private static Log logger = LogFactory.getLog(LocalFileSystemStore.class);
59  
60      private String root;
61      private String path;
62      private File rootDir;
63      private ServletContext servletContext;
64  
65      /* (non-Javadoc)
66       * @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext)
67       */
68      public void setServletContext(ServletContext servletContext)
69      {
70          this.servletContext = servletContext;        
71      }
72      
73      public ServletContext getServletContext()
74      {
75          return this.servletContext;
76      }
77  
78      protected File getRootDir()
79      {
80          if (this.rootDir == null)
81          {
82              this.rootDir = new File(getBasePath());
83          }
84  
85          return this.rootDir;
86      }
87  
88      /**
89       * @param root the root path
90       */
91      public void setRoot(String root)
92      {
93          this.root = root;
94      }
95      
96      public String getRoot()
97      {
98          return this.root;
99      }
100 
101     /**
102      * @param path the relative path to set
103      */
104     public void setPath(String path)
105     {
106         this.path = path;
107     }
108     
109     public String getPath()
110     {
111         return this.path;
112     }
113 
114     /*
115      * (non-Javadoc)
116      * 
117      * @see org.alfresco.web.scripts.Store#init()
118      */
119     public void init()
120     {
121         if (this.path == null)
122         {
123             this.path = "";
124         }
125     }
126 
127     /*
128      * (non-Javadoc)
129      * 
130      * @see org.alfresco.web.scripts.Store#isSecure()
131      */
132     public boolean isSecure()
133     {
134         return false;
135     }
136 
137     /*
138      * (non-Javadoc)
139      * 
140      * @see org.alfresco.web.scripts.Store#exists()
141      */
142     public boolean exists()
143     {
144         if (getRootDir() == null)
145         {
146             if (logger.isDebugEnabled())
147                 logger.debug("Root directory for Store does not exist");
148 
149             return false;
150         }
151         return getRootDir().exists();
152     }
153 
154     /*
155      * (non-Javadoc)
156      * 
157      * @see org.alfresco.web.scripts.Store#hasDocument(java.lang.String)
158      */
159     public boolean hasDocument(String documentPath)
160     {
161         File file = new File(toAbsolutePath(documentPath));
162         return (file != null && file.exists() && file.isFile());
163     }
164 
165     /*
166      * (non-Javadoc)
167      * 
168      * @see org.alfresco.web.scripts.Store#lastModified(java.lang.String)
169      */
170     public long lastModified(String documentPath) throws IOException
171     {
172         File file = new File(toAbsolutePath(documentPath));
173         if (file == null)
174         {
175             throw new IOException(
176                     "Unable to locate file to check modification time: " + documentPath);
177         }
178 
179         return file.lastModified();
180     }
181 
182     /*
183      * (non-Javadoc)
184      * 
185      * @see org.alfresco.web.scripts.Store#updateDocument(java.lang.String,
186      *      java.lang.String)
187      */
188     public void updateDocument(String documentPath, String content)
189             throws IOException
190     {
191         File file = new File(toAbsolutePath(documentPath));
192         if (file == null)
193         {
194             throw new IOException(
195                     "Unable to locate file for update: " + documentPath);
196         }
197 
198         FileWriter fw = new FileWriter(file);
199         fw.write(content);
200         fw.close();
201     }
202 
203     /*
204      * (non-Javadoc)
205      * 
206      * @see org.alfresco.web.scripts.Store#removeDocument(java.lang.String)
207      */
208     public boolean removeDocument(String documentPath) throws IOException
209     {
210         File file = new File(toAbsolutePath(documentPath));
211         if (file == null)
212         {
213             throw new IOException(
214                     "Update to remove document failed, file not found: " + documentPath);
215         }
216 
217         return file.delete();
218     }
219 
220     /*
221      * (non-Javadoc)
222      * 
223      * @see org.alfresco.web.scripts.Store#createDocument(java.lang.String,
224      *      java.lang.String)
225      */
226     public void createDocument(String documentPath, String content)
227             throws IOException
228     {
229         // check whether a file already exists
230         if (hasDocument(documentPath))
231         {
232             throw new IOException(
233                     "Unable to create document, already exists: " + documentPath);
234         }
235 
236         File file = new File(toAbsolutePath(documentPath));
237 
238         FileWriter fw = new FileWriter(file);
239         fw.write(content);
240         fw.close();
241     }
242 
243     /*
244      * (non-Javadoc)
245      * 
246      * @see org.alfresco.web.scripts.Store#getDocument(java.lang.String)
247      */
248     public InputStream getDocument(String documentPath) throws IOException
249     {
250         File file = new File(toAbsolutePath(documentPath));
251         if (file == null)
252         {
253             throw new IOException(
254                     "Unable to get input stream from document: " + documentPath);
255         }
256 
257         return new FileInputStream(file);
258     }
259 
260     /*
261      * (non-Javadoc)
262      * 
263      * @see org.alfresco.web.scripts.Store#getAllDocumentPaths()
264      */
265     public String[] getAllDocumentPaths()
266     {
267         List<String> list = new ArrayList<String>(256);
268 
269         // exhaustive traverse of absolute paths
270         gatherAbsolutePaths(getRootDir().getAbsolutePath(), list);
271 
272         // convert to array
273         String[] array = list.toArray(new String[list.size()]);
274 
275         // down shift to relative paths
276         String absRootPath = getRootDir().getAbsolutePath() + File.separatorChar;
277         int absRootPathLen = absRootPath.length();
278         for (int i = 0; i < array.length; i++)
279         {
280             array[i] = array[i].substring(absRootPathLen);
281             
282             // so as to be consistent with expected store syntax
283             array[i] = array[i].replace("\\", "/");            
284         }
285 
286         return array;
287     }
288     
289     /* (non-Javadoc)
290      * @see org.alfresco.web.scripts.Store#getDocumentPaths(java.lang.String, boolean, java.lang.String)
291      */
292     public String[] getDocumentPaths(String path, boolean includedSubPaths, String documentPattern)
293     {
294         String regexPattern = documentPattern.replaceAll("\\*", ".*");
295         
296         if (!regexPattern.startsWith(".*"))
297         {
298             regexPattern = ".*" + regexPattern;
299         }
300         
301         return getDocumentPathsByRegEx(path, regexPattern, includedSubPaths);
302     }
303     
304     /**
305      * Performs a pattern filter look up using a regex and starting
306      * from a given path.
307      * 
308      * Returns an array of valid document paths
309      * 
310      * @param path
311      * @param regexPattern
312      * @param traverseChildren
313      * 
314      * @return document paths
315      */
316     protected String[] getDocumentPathsByRegEx(String path, String regexPattern, boolean traverseChildren)
317     {
318         PatternFileFilter filter = new PatternFileFilter(regexPattern);
319 
320         String absParentPath = toAbsolutePath(path);
321         int absParentPathLen = absParentPath.length() - 1;
322         File f = new File(absParentPath);
323 
324         List<File> fileList = listPath(f, filter, traverseChildren);
325 
326         String[] paths = new String[fileList.size()];
327         for (int i = 0; i < fileList.size(); i++)
328         {
329             String thePath = ((File) fileList.get(i)).getPath();
330             paths[i] = thePath.substring(absParentPathLen);
331             
332             // so as to be consistent with expected store syntax
333             paths[i] = paths[i].replace("\\", "/");
334         }
335 
336         return paths;
337     }
338         
339     /*
340      * (non-Javadoc)
341      * 
342      * @see org.alfresco.web.scripts.Store#getDescriptionDocumentPaths()
343      */
344     public String[] getDescriptionDocumentPaths()
345     {
346         return getDocumentPathsByRegEx("/", ".*\\.desc\\.xml", true);
347     }
348 
349     /*
350      * (non-Javadoc)
351      * 
352      * @see org.alfresco.web.scripts.Store#getScriptDocumentPaths(org.alfresco.web.scripts.WebScript)
353      */
354     public String[] getScriptDocumentPaths(WebScript script)
355     {
356         String scriptPaths = script.getDescription().getId() + ".*";
357         return getDocumentPathsByRegEx("/", scriptPaths, false);
358     }
359 
360     /*
361      * (non-Javadoc)
362      * 
363      * @see org.alfresco.web.scripts.Store#getScriptLoader()
364      */
365     public ScriptLoader getScriptLoader()
366     {
367         return new LocalFileSystemStoreScriptLoader();
368     }
369 
370     /*
371      * (non-Javadoc)
372      * 
373      * @see org.alfresco.web.scripts.Store#getTemplateLoader()
374      */
375     public TemplateLoader getTemplateLoader()
376     {
377         return new LocalFileSystemStoreTemplateLoader();
378     }
379 
380     /*
381      * (non-Javadoc)
382      * 
383      * @see org.alfresco.web.scripts.Store#getBasePath()
384      */
385     public String getBasePath()
386     {
387         String fullPath = this.path;
388 
389         if (this.root != null)
390         {
391             if (!root.endsWith("/"))
392             {
393                 root += "/";
394             }
395 
396             if (root.startsWith("."))
397             {
398                 // make relative to the web app real path
399                 fullPath = getRealPath(this.root.substring(1)) + this.path;
400             }
401             else
402             {
403                 fullPath = this.root + this.path;
404             }
405         }
406 
407         return fullPath;
408     }
409     
410     /**
411      * Helper function for converting a web application path
412      * to a real system path.
413      * 
414      * The input path might be /products/product.xml and the
415      * generated real path would be the full system path to the
416      * file in the servlet container's web application.
417      * 
418      * For example, d:/tomcat/webapps/surf/products/product.xml
419      * 
420      * @param path the relative path
421      * 
422      * @return the system path
423      */
424     public String getRealPath(String path)
425     {
426         String realPath = servletContext.getRealPath(path);
427         if (realPath != null && realPath.endsWith(java.io.File.separator))
428         {
429             realPath = realPath.substring(0, realPath.length() - 1);
430         }
431         
432         // clean up the path
433         realPath = realPath.replace("/", java.io.File.separator);
434         realPath = realPath.replace("\\", java.io.File.separator);
435         
436         return realPath;
437     }
438 
439     /**
440      * Returns the absolute path relative to the root of the store
441      * given a particular document path.
442      * 
443      * @param documentPath
444      * @return
445      */
446     protected String toAbsolutePath(String documentPath)
447     {
448         return getRootDir().getAbsolutePath() + File.separatorChar + documentPath;
449     }
450 
451     protected void gatherAbsolutePaths(String absPath, List<String> list)
452     {
453         File file = new File(absPath);
454         if (file.exists())
455         {
456             if (file.isFile())
457             {
458                 list.add(absPath);
459             }
460             else if (file.isDirectory())
461             {
462                 // get all of the children
463                 String[] childDocumentPaths = file.list();
464                 for (int i = 0; i < childDocumentPaths.length; i++)
465                 {
466                     String childAbsPath = absPath + File.separatorChar + childDocumentPaths[i];
467                     gatherAbsolutePaths(childAbsPath, list);
468                 }
469             }
470         }
471     }
472 
473     /**
474      * Local File System Store implementation of a Script Loader
475      * 
476      * @author muzquiano
477      */
478     protected class LocalFileSystemStoreScriptLoader implements ScriptLoader
479     {
480         /**
481          * @see org.springframework.extensions.webscripts.ScriptLoader#getScript(java.lang.String)
482          */
483         public ScriptContent getScript(String path)
484         {
485             ScriptContent sc = null;
486             if (hasDocument(path))
487             {
488                 sc = new LocalFileSystemStoreScriptContent(path);
489             }
490             return sc;
491         }
492     }
493 
494     /**
495      * Local File System Store implementation of a Template Loader
496      * 
497      * @author muzquiano
498      */
499     private class LocalFileSystemStoreTemplateLoader implements TemplateLoader
500     {
501         /**
502          * @see freemarker.cache.TemplateLoader#closeTemplateSource(java.lang.Object)
503          */
504         public void closeTemplateSource(Object templateSource)
505                 throws IOException
506         {
507             // nothing to do - we return a reader to fully retrieved in-memory
508             // data
509         }
510 
511         /**
512          * @see freemarker.cache.TemplateLoader#findTemplateSource(java.lang.String)
513          */
514         public Object findTemplateSource(String name) throws IOException
515         {
516             LocalFileSystemStoreTemplateSource source = null;
517             if (hasDocument(name))
518             {
519                 source = new LocalFileSystemStoreTemplateSource(name);
520             }
521             return source;
522         }
523 
524         /**
525          * @see freemarker.cache.TemplateLoader#getLastModified(java.lang.Object)
526          */
527         public long getLastModified(Object templateSource)
528         {
529             return ((LocalFileSystemStoreTemplateSource) templateSource).lastModified();
530         }
531 
532         /**
533          * @see freemarker.cache.TemplateLoader#getReader(java.lang.Object,
534          *      java.lang.String)
535          */
536         public Reader getReader(Object templateSource, String encoding)
537                 throws IOException
538         {
539             return ((LocalFileSystemStoreTemplateSource) templateSource).getReader(encoding);
540         }
541     }
542 
543     /**
544      * Template Source - loads from a Local File System Store.
545      * 
546      * @author muzquiano
547      */
548     private class LocalFileSystemStoreTemplateSource
549     {
550         private String templatePath;
551 
552         private LocalFileSystemStoreTemplateSource(String path)
553         {
554             this.templatePath = path;
555         }
556 
557         private long lastModified()
558         {
559             try
560             {
561                 return LocalFileSystemStore.this.lastModified(templatePath);
562             }
563             catch (IOException e)
564             {
565                 return -1;
566             }
567         }
568 
569         private Reader getReader(String encoding) throws IOException
570         {
571             Reader reader = null;
572 
573             File f = new File(toAbsolutePath(templatePath));
574             if (f.exists())
575             {
576                 reader = new FileReader(f);
577             }
578 
579             return reader;
580         }
581     }
582 
583     /**
584      * Script Content - loads from a Local File System Store.
585      * 
586      * @author muzquiano
587      */
588     private class LocalFileSystemStoreScriptContent implements ScriptContent
589     {
590         private String scriptPath;
591 
592         /**
593          * Constructor
594          * 
595          * @param path Path to remote script content
596          */
597         private LocalFileSystemStoreScriptContent(String path)
598         {
599             this.scriptPath = path;
600         }
601 
602         /**
603          * @see org.springframework.extensions.webscripts.ScriptContent#getPath()
604          */
605         public String getPath()
606         {
607             return getBasePath() + '/' + this.scriptPath;
608         }
609 
610         /**
611          * @see org.springframework.extensions.webscripts.ScriptContent#getPathDescription()
612          */
613         public String getPathDescription()
614         {
615             return getBasePath() + '/' + this.scriptPath;
616         }
617 
618         /**
619          * @see org.springframework.extensions.webscripts.ScriptContent#getInputStream()
620          */
621         public InputStream getInputStream()
622         {
623             InputStream is = null;
624 
625             try
626             {
627                 File f = new File(toAbsolutePath(scriptPath));
628                 if (f.exists())
629                 {
630                     is = new FileInputStream(f);
631                 }
632             }
633             catch (IOException e)
634             {
635                 throw new PlatformRuntimeException(
636                         "Unable to load script: " + scriptPath, e);
637             }
638 
639             return is;
640         }
641 
642         /**
643          * @see org.springframework.extensions.webscripts.ScriptContent#getReader()
644          */
645         public Reader getReader()
646         {
647             Reader reader = null;
648 
649             try
650             {
651                 File f = new File(toAbsolutePath(scriptPath));
652                 if (f.exists())
653                 {
654                     reader = new FileReader(f);
655                 }
656             }
657             catch (IOException e)
658             {
659                 throw new PlatformRuntimeException(
660                         "Unable to load script: " + scriptPath, e);
661             }
662 
663             return reader;
664         }
665 
666         /**
667          * @see org.springframework.extensions.webscripts.ScriptContent#isSecure()
668          */
669         public boolean isSecure()
670         {
671             return false;
672         }
673 
674         /**
675          * @see org.springframework.extensions.webscripts.ScriptContent#isCachable()
676          */
677         public boolean isCachable()
678         {
679             return false;
680         }
681     }
682 
683     private class PatternFileFilter implements FileFilter
684     {
685         Pattern pattern;
686 
687         public PatternFileFilter(String pat)
688         {
689             this.pattern = Pattern.compile(pat);
690         }
691 
692         public boolean accept(File pathname)
693         {
694             boolean accept = false;
695             
696             if (pathname.isDirectory())
697             {
698                 accept = false;
699             }
700             else
701             {
702                 String path = null;
703                 try
704                 {
705                     path = pathname.toURL().toExternalForm();
706                     if (path.endsWith("\\") || path.endsWith("/"))
707                     {
708                         path = path.substring(0, path.length() - 1);
709                     }
710                     
711                     accept = pattern.matcher(path).matches();
712                 }
713                 catch(MalformedURLException mue) { }
714             }
715             
716             return accept;
717         }
718     }
719 
720     private List<File> listPath(File path, FileFilter filter,
721             boolean listChildren)
722     {
723         List<File> results = new ArrayList<File>();
724 
725         listPath(path, filter, results, listChildren);
726 
727         return results;
728     }
729 
730     private void listPath(File path, FileFilter filter, List<File> results,
731             boolean listChildren)
732     {
733         // list of files in this dir
734         File files[] = path.listFiles(filter);
735         if (files.length > 0)
736         {
737             // Sort with help of Collections API
738             Arrays.sort(files);
739 
740             // add into the results
741             for (int i = 0; i < files.length; i++)
742             {
743                 results.add(files[i]);
744             }
745         }
746 
747         // dive down into the subdirectories?
748         if (listChildren)
749         {
750             // list of all files
751             files = path.listFiles();
752             for (int i = 0; i < files.length; i++)
753             {
754                 // walk through children if deemed to be thus
755                 if (files[i].isDirectory())
756                 {
757                     // recursively descend dir tree
758                     listPath(files[i], filter, results, listChildren);
759                 }
760             }
761         }
762     }
763 }