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.FileNotFoundException;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.OutputStream;
28  import java.io.PrintWriter;
29  import java.io.Reader;
30  import java.io.UnsupportedEncodingException;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.springframework.beans.BeansException;
37  import org.springframework.context.ApplicationContext;
38  import org.springframework.context.ApplicationContextAware;
39  import org.springframework.core.io.FileSystemResource;
40  import org.springframework.core.io.Resource;
41  import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
42  import org.springframework.core.io.support.ResourcePatternResolver;
43  import org.springframework.extensions.surf.exception.WebScriptsPlatformException;
44  
45  import freemarker.cache.ClassTemplateLoader;
46  import freemarker.cache.TemplateLoader;
47  
48  
49  /**
50   * ClassPath based Web Script Store
51   *
52   * @author davidc
53   */
54  public class ClassPathStore extends AbstractStore implements ApplicationContextAware, Store
55  {
56      // Logger
57      private static final Log logger = LogFactory.getLog(ClassPathStore.class);
58  
59      ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
60      protected boolean mustExist = false;
61      protected String classPath;
62      protected Resource storeResource;
63      protected String storeResourcePath;
64      protected int storeResourcePathLength;
65      protected File storeDir;
66  
67  
68      /**
69       * Sets whether the class path must exist
70       *
71       * If it must exist, but it doesn't exist, an exception is thrown
72       * on initialisation of the store
73       *
74       * @param mustExist
75       */
76      public void setMustExist(boolean mustExist)
77      {
78          this.mustExist = mustExist;
79      }
80  
81      /**
82       * Sets the class path
83       *
84       * @param classPath  classpath
85       */
86      public void setClassPath(String classPath)
87      {
88          String cleanClassPath = (classPath.endsWith("/")) ? classPath.substring(0, classPath.length() -1) : classPath;
89          this.classPath = cleanClassPath;
90      }
91  
92  
93      /* (non-Javadoc)
94       * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
95       */
96      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
97      {
98          this.resolver = applicationContext;
99      }
100 
101     /* (non-Javadoc)
102      * @see org.alfresco.web.scripts.Store#init()
103      */
104     public void init()
105     {
106         try
107         {
108             // NOTE: Locate root of web script store
109             // NOTE: Following awkward approach is used to mirror lookup of web scripts within store.  This
110             //       ensures root paths match.
111             Resource rootResource = null;
112             Resource[] resources = resolver.getResources("classpath*:" + classPath + "*");
113             for (Resource resource : resources)
114             {
115                 String externalForm = resource.getURL().toExternalForm();
116                 if (externalForm.endsWith(classPath) || externalForm.endsWith(classPath + "/"))
117                 {
118                     // we've found the right resource, let's now bind using string constructor
119                     // so that Spring 3 will correctly create relative paths
120                     String directoryPath = resource.getFile().getAbsolutePath();
121                     if (resource.getFile().isDirectory() && !directoryPath.endsWith("/"))
122                     {
123                         directoryPath += "/";
124                     }
125                     rootResource = new FileSystemResource(directoryPath);
126                     //rootResource = resource;
127                     break;
128                 }
129             }
130 
131             if (rootResource != null && rootResource.exists())
132             {
133                 storeResource = rootResource;
134                 storeResourcePath = storeResource.getURL().toExternalForm();
135                 String cleanStoreResourcePath = (storeResourcePath.endsWith("/")) ? storeResourcePath.substring(0, storeResourcePath.length() -1) : storeResourcePath;
136                 storeResourcePathLength = cleanStoreResourcePath.length();
137                 if (logger.isTraceEnabled())
138                     logger.trace("Provided classpath: " + classPath + " , storeRootPath: " + storeResourcePath + ", storeRootPathLength: " + storeResourcePathLength);
139 
140                 try
141                 {
142                     // retrieve file system directory
143                     storeDir = resources[0].getFile();
144                 }
145                 catch(FileNotFoundException e)
146                 {
147                     // NOTE: this means that installation of web scripts is not possible
148                 }
149             }
150             else if (mustExist)
151             {
152                 throw new WebScriptException("Web Script Store classpath:" + classPath + " must exist; it was not found");
153             }
154         }
155         catch(IOException e)
156         {
157             throw new WebScriptException("Failed to initialise Web Script Store classpath: " + classPath, e);
158         }
159     }
160 
161     /* (non-Javadoc)
162      * @see org.alfresco.web.scripts.Store#exists()
163      */
164     public boolean exists()
165     {
166         return (storeResource != null);
167     }
168 
169     /* (non-Javadoc)
170      * @see org.alfresco.web.scripts.Store#getBasePath()
171      */
172     public String getBasePath()
173     {
174         return "classpath:" + classPath;
175     }
176 
177 
178     /* (non-Javadoc)
179      * @see org.alfresco.web.scripts.Store#isSecure()
180      */
181     public boolean isSecure()
182     {
183         return true;
184     }
185 
186     /* (non-Javadoc)
187      * @see org.alfresco.web.scripts.Store#getAllDocumentPaths()
188      */
189     public String[] getAllDocumentPaths()
190     {
191         String[] paths;
192 
193         try
194         {
195             List<String> documentPaths = getPaths("classpath*:" + classPath + "/**/*");
196             paths = documentPaths.toArray(new String[documentPaths.size()]);
197         }
198         catch (IOException e)
199         {
200             // Note: Ignore: no documents found
201             paths = new String[0];
202         }
203 
204         return paths;
205     }
206 
207     /* (non-Javadoc)
208      * @see org.alfresco.web.scripts.Store#getDocumentPaths(java.lang.String, boolean, java.lang.String)
209      */
210     public String[] getDocumentPaths(String path, boolean includeSubPaths, String documentPattern)
211         throws IOException
212     {
213         if ((path == null) || (path.length() == 0))
214         {
215             path = "/";
216         }
217 
218         if (! path.startsWith("/"))
219         {
220             path = "/" + path;
221         }
222 
223         if (! path.endsWith("/"))
224         {
225             path = path + "/";
226         }
227 
228         if ((documentPattern == null) || (documentPattern.length() == 0))
229         {
230             documentPattern = "*";
231         }
232 
233         final StringBuilder pattern = new StringBuilder(128);
234         pattern.append("classpath*:").append(classPath)
235                .append(path)
236                .append((includeSubPaths ? "**/" : ""))
237                .append(documentPattern);
238 
239         List<String> documentPaths = getPaths(pattern.toString());
240         return documentPaths.toArray(new String[documentPaths.size()]);
241     }
242 
243     /* (non-Javadoc)
244      * @see org.alfresco.web.scripts.Store#getDescriptionDocumentPaths()
245      */
246     public String[] getDescriptionDocumentPaths() throws IOException
247     {
248         return getDocumentPaths("/", true, "*.desc.xml");
249     }
250 
251     /* (non-Javadoc)
252      * @see org.alfresco.web.scripts.Store#getScriptDocumentPaths(org.alfresco.web.scripts.WebScript)
253      */
254     public String[] getScriptDocumentPaths(WebScript script) throws IOException
255     {
256         String scriptPaths = script.getDescription().getId() + ".*";
257         return getDocumentPaths("/", false, scriptPaths);
258     }
259 
260     /**
261      * Helper to return a list of resource document paths based on a search pattern.
262      */
263     private List<String> getPaths(String pattern)
264         throws IOException
265     {
266         Resource[] resources = resolver.getResources(pattern);
267         List<String> documentPaths = new ArrayList<String>(resources.length);
268         for (Resource resource : resources)
269         {
270             // don't deal with JAR files (will throw IOException)
271             try
272             {
273                 if (resource.getFile().isFile())
274                 {
275                     if (resource.getURL().toExternalForm().startsWith(storeResourcePath))
276                     {
277                         String resourcePath = resource.getURL().toExternalForm();
278                         String documentPath = resourcePath.substring(storeResourcePathLength +1);
279                         documentPath = documentPath.replace('\\', '/');
280                         if (logger.isTraceEnabled())
281                             logger.trace("Item resource path: " + resourcePath + " , item path: " + documentPath);
282                         documentPaths.add(documentPath);
283                     }
284                 }
285             }
286             catch (IOException ioe)
287             {
288                 // fail silently for jar files that are picked up - class path store does not handle them
289             }
290         }
291         return documentPaths;
292     }
293 
294     /* (non-Javadoc)
295      * @see org.alfresco.web.scripts.Store#lastModified(java.lang.String)
296      */
297     public long lastModified(String documentPath)
298         throws IOException
299     {
300         Resource document = createRelative(storeResource, documentPath);
301         return document.getURL().openConnection().getLastModified();
302     }
303 
304     /* (non-Javadoc)
305      * @see org.alfresco.web.scripts.Store#hasDocument(java.lang.String)
306      */
307     public boolean hasDocument(String documentPath)
308     {
309         boolean exists = false;
310         try
311         {
312             Resource document = createRelative(storeResource, documentPath);
313             exists = document.exists();
314         }
315         catch(IOException e)
316         {
317         }
318         return exists;
319     }
320 
321     /* (non-Javadoc)
322      * @see org.alfresco.web.scripts.Store#getDescriptionDocument(java.lang.String)
323      */
324     public InputStream getDocument(String documentPath)
325         throws IOException
326     {
327         Resource document = createRelative(storeResource, documentPath);
328         if (logger.isTraceEnabled())
329             logger.trace("getDocument: documentPath: " + documentPath + " , storePath: " + document.getURL().toExternalForm());
330 
331         if (!document.exists())
332         {
333             throw new IOException("Document " + documentPath + " does not exist within store " + getBasePath());
334         }
335         return document.getInputStream();
336     }
337 
338     /* (non-Javadoc)
339      * @see org.alfresco.web.scripts.Store#createDocument(java.lang.String, java.lang.String)
340      */
341     public void createDocument(String documentPath, String content) throws IOException
342     {
343         File document = new File(storeDir, documentPath);
344 
345         // create directory
346         File path = document.getParentFile();
347         path.mkdirs();
348 
349         // create file
350         if (!document.createNewFile())
351         {
352             throw new IOException("Document " + documentPath + " already exists");
353         }
354         OutputStream output = new FileOutputStream(document);
355         try
356         {
357             PrintWriter writer = new PrintWriter(output);
358             writer.write(content);
359             writer.flush();
360         }
361         finally
362         {
363             output.flush();
364             output.close();
365         }
366     }
367 
368     /* (non-Javadoc)
369      * @see org.alfresco.web.scripts.Store#updateDocument(java.lang.String, java.lang.String)
370      */
371     public void updateDocument(String documentPath, String content) throws IOException
372     {
373         File document = new File(storeDir, documentPath);
374 
375         // check for write access
376         if (!document.canWrite())
377         {
378             throw new IOException("Document " + documentPath + " is not writable");
379         }
380         OutputStream output = new FileOutputStream(document);
381         try
382         {
383             PrintWriter writer = new PrintWriter(output);
384             writer.write(content);
385             writer.flush();
386         }
387         finally
388         {
389             output.flush();
390             output.close();
391         }
392     }
393 
394     /* (non-Javadoc)
395      * @see org.alfresco.web.scripts.Store#removeDocument(java.lang.String)
396      */
397     public boolean removeDocument(String documentPath)
398         throws IOException
399     {
400         // do not allow for deletion of documents from the classpath
401         // the classpath is read-only
402         return false;
403     }
404 
405     /* (non-Javadoc)
406      * @see org.alfresco.web.scripts.Store#getTemplateLoader()
407      */
408     public TemplateLoader getTemplateLoader()
409     {
410         // ensure classpath starts and ends with /
411         String templateClassPath = (classPath.charAt(0) == '/') ? classPath : "/" + classPath;
412         return new ClassTemplateLoader(ClassPathStore.class, templateClassPath);
413     }
414 
415     /* (non-Javadoc)
416      * @see org.alfresco.web.scripts.Store#getScriptLoader()
417      */
418     public ScriptLoader getScriptLoader()
419     {
420         return new ClassPathScriptLoader();
421     }
422 
423     /**
424      * Construct a relative resource
425      *
426      * @param resource  root resource
427      * @param path  relative path
428      * @return  relative resource
429      * @throws IOException
430      */
431     private Resource createRelative(Resource resource, String path)
432         throws IOException
433     {
434         if (storeResourcePath.endsWith("/"))
435         {
436             return resource.createRelative(path);            
437         }
438 
439         int prefixIdx = storeResourcePath.lastIndexOf("/");
440         String prefix = (prefixIdx != -1) ? storeResourcePath.substring(prefixIdx) : "";
441         return resource.createRelative(prefix + "/" + path);        
442     }
443     
444     /**
445      * Helper method for creating relative paths
446      * 
447      * @param path
448      * @param relativePath
449      * @return
450      */
451     public static String createPath(String path, String relativePath) 
452     {
453         if (path.endsWith("/"))
454         {
455             path = path.substring(0, path.length() - 1);
456         }
457         
458         if (relativePath.startsWith("/"))
459         {
460             relativePath = relativePath.substring(1);
461         }
462         
463         return path + "/" + relativePath;
464     }
465     
466 
467     /* (non-Javadoc)
468      * @see java.lang.Object#toString()
469      */
470     @Override
471     public String toString()
472     {
473         return this.storeResourcePath;
474     }
475 
476 
477     /**
478      * Class path based script loader
479      *
480      * @author davidc
481      */
482     private class ClassPathScriptLoader implements ScriptLoader
483     {
484 
485         /* (non-Javadoc)
486          * @see org.alfresco.web.scripts.ScriptLoader#getScriptLocation(java.lang.String)
487          */
488         public ScriptContent getScript(String path)
489         {
490             ScriptContent location = null;
491             try
492             {
493                 Resource script = createRelative(storeResource, path);
494                 if (script.exists())
495                 {
496                     location = new ClassPathScriptLocation(storeResource, path, script);
497                 }
498             }
499             catch(IOException e)
500             {
501             }
502             return location;
503         }
504     }
505 
506     /**
507      * Class path script location
508      *
509      * @author davidc
510      */
511     private static class ClassPathScriptLocation implements ScriptContent
512     {
513         private Resource store;
514         private String path;
515         private Resource location;
516 
517         /**
518          * Construct
519          *
520          * @param store
521          * @param path
522          * @param location
523          */
524         public ClassPathScriptLocation(Resource store, String path, Resource location)
525         {
526             this.store = store;
527             this.path = path;
528             this.location = location;
529         }
530 
531         /* (non-Javadoc)
532          * @see org.alfresco.service.cmr.repository.ScriptLocation#getInputStream()
533          */
534         public InputStream getInputStream()
535         {
536             try
537             {
538                 return location.getInputStream();
539             }
540             catch (IOException e)
541             {
542                 throw new WebScriptException("Unable to retrieve input stream for script " + getPathDescription());
543             }
544         }
545 
546         /* (non-Javadoc)
547          * @see org.alfresco.service.cmr.repository.ScriptLocation#getReader()
548          */
549         public Reader getReader()
550         {
551             try
552             {
553                 return new InputStreamReader(getInputStream(), "UTF-8");
554             }
555             catch (UnsupportedEncodingException e)
556             {
557                 throw new WebScriptsPlatformException("Unsupported Encoding", e);
558             }
559         }
560 
561         /* (non-Javadoc)
562          * @see org.alfresco.web.scripts.ScriptContent#getPath()
563          */
564         public String getPath()
565         {
566             String path = "<unknown path>";
567             try
568             {
569                 path = location.getURL().toExternalForm();
570             }
571             catch(IOException ioe)
572             {
573             };
574             return path;
575         }
576 
577         /* (non-Javadoc)
578          * @see org.alfresco.web.scripts.ScriptContent#getPathDescription()
579          */
580         public String getPathDescription()
581         {
582             String desc = "<unknown path>";
583             try
584             {
585                 desc = "/" + path + " (in classpath store " + store.getURL().toExternalForm() + ")";
586             }
587             catch(IOException ioe)
588             {
589             };
590             return desc;
591         }
592 
593         /* (non-Javadoc)
594          * @see org.alfresco.web.scripts.ScriptContent#isCachable()
595          */
596         public boolean isCachable()
597         {
598             return true;
599         }
600 
601         /* (non-Javadoc)
602          * @see org.alfresco.web.scripts.ScriptContent#isSecure()
603          */
604         public boolean isSecure()
605         {
606             return true;
607         }
608 
609         @Override
610         public String toString()
611         {
612             return getPathDescription();
613         }
614     }
615 }