View Javadoc

1   package org.apache.velocity.runtime.resource.loader;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.io.ByteArrayInputStream;
26  import java.io.InputStream;
27  import java.io.UnsupportedEncodingException;
28  import org.apache.commons.collections.ExtendedProperties;
29  import org.apache.commons.lang.StringUtils;
30  import org.apache.velocity.exception.ResourceNotFoundException;
31  import org.apache.velocity.exception.VelocityException;
32  import org.apache.velocity.runtime.resource.Resource;
33  import org.apache.velocity.runtime.resource.util.StringResource;
34  import org.apache.velocity.runtime.resource.util.StringResourceRepository;
35  import org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl;
36  import org.apache.velocity.util.ClassUtils;
37  
38  /**
39   * Resource loader that works with Strings. Users should manually add
40   * resources to the repository that is used by the resource loader instance.
41   *
42   * Below is an example configuration for this loader.
43   * Note that 'repository.class' is not necessary;
44   * if not provided, the factory will fall back on using 
45   * {@link StringResourceRepositoryImpl} as the default.
46   * <pre>
47   * resource.loader = string
48   * string.resource.loader.description = Velocity StringResource loader
49   * string.resource.loader.class = org.apache.velocity.runtime.resource.loader.StringResourceLoader
50   * string.resource.loader.repository.class = org.apache.velocity.runtime.resource.loader.StringResourceRepositoryImpl
51   * </pre>
52   * Resources can be added to the repository like this:
53   * <pre><code>
54   *   StringResourceRepository repo = StringResourceLoader.getRepository();
55   *
56   *   String myTemplateName = "/some/imaginary/path/hello.vm";
57   *   String myTemplate = "Hi, ${username}... this is some template!";
58   *   repo.putStringResource(myTemplateName, myTemplate);
59   * </code></pre>
60   *
61   * After this, the templates can be retrieved as usual.
62   * <br>
63   * <p>If there will be multiple StringResourceLoaders used in an application,
64   * you should consider specifying a 'string.resource.loader.repository.name = foo'
65   * property in order to keep you string resources in a non-default repository.
66   * This can help to avoid conflicts between different frameworks or components
67   * that are using StringResourceLoader.
68   * You can then retrieve your named repository like this:
69   * <pre><code>
70   *   StringResourceRepository repo = StringResourceLoader.getRepository("foo");
71   * </code></pre>
72   * and add string resources to the repo just as in the previous example.
73   * </p>
74   * <p>If you have concerns about memory leaks or for whatever reason do not wish
75   * to have your string repository stored statically as a class member, then you
76   * should set 'string.resource.loader.repository.static = false' in your properties.
77   * This will tell the resource loader that the string repository should be stored
78   * in the Velocity application attributes.  To retrieve the repository, do:
79   * <pre><code>
80   *   StringResourceRepository repo = velocityEngine.getApplicationAttribute("foo");
81   * </code></pre>
82   * If you did not specify a name for the repository, then it will be stored under the
83   * class name of the repository implementation class (for which the default is 
84   * 'org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl'). 
85   * Incidentally, this is also true for the default statically stored repository.
86   * </p>
87   * <p>Whether your repository is stored statically or in Velocity's application
88   * attributes, you can also manually create and set it prior to Velocity
89   * initialization.  For a static repository, you can do something like this:
90   * <pre><code>
91   *   StringResourceRepository repo = new MyStringResourceRepository();
92   *   repo.magicallyAddSomeStringResources();
93   *   StringResourceLoader.setRepository("foo", repo);
94   * </code></pre>
95   * Or for a non-static repository:
96   * <pre><code>
97   *   StringResourceRepository repo = new MyStringResourceRepository();
98   *   repo.magicallyAddSomeStringResources();
99   *   velocityEngine.setApplicationAttribute("foo", repo);
100  * </code></pre>
101  * Then, assuming the 'string.resource.loader.repository.name' property is
102  * set to 'some.name', the StringResourceLoader will use that already created
103  * repository, rather than creating a new one.
104  * </p>
105  *
106  * @author <a href="mailto:eelco.hillenius@openedge.nl">Eelco Hillenius</a>
107  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
108  * @author Nathan Bubna
109  * @version $Id: StringResourceLoader.java 687518 2008-08-21 00:18:03Z nbubna $
110  * @since 1.5
111  */
112 public class StringResourceLoader extends ResourceLoader
113 {
114     /**
115      * Key to determine whether the repository should be set as the static one or not.
116      * @since 1.6
117      */
118     public static final String REPOSITORY_STATIC = "repository.static";
119 
120     /**
121      * By default, repositories are stored statically (shared across the VM).
122      * @since 1.6
123      */
124     public static final boolean REPOSITORY_STATIC_DEFAULT = true;
125 
126     /** Key to look up the repository implementation class. */
127     public static final String REPOSITORY_CLASS = "repository.class";
128 
129     /** The default implementation class. */
130     public static final String REPOSITORY_CLASS_DEFAULT =
131         StringResourceRepositoryImpl.class.getName();
132 
133     /**
134      * Key to look up the name for the repository to be used.
135      * @since 1.6
136      */
137     public static final String REPOSITORY_NAME = "repository.name";
138 
139     /** The default name for string resource repositories
140      * ('org.apache.velocity.runtime.resource.util.StringResourceRepository').
141      * @since 1.6
142      */
143     public static final String REPOSITORY_NAME_DEFAULT =
144         StringResourceRepository.class.getName();
145 
146     /** Key to look up the repository char encoding. */
147     public static final String REPOSITORY_ENCODING = "repository.encoding";
148 
149     /** The default repository encoding. */
150     public static final String REPOSITORY_ENCODING_DEFAULT = "UTF-8";
151 
152 
153     protected static final Map STATIC_REPOSITORIES =
154         Collections.synchronizedMap(new HashMap());
155 
156     /**
157      * Returns a reference to the default static repository.
158      */
159     public static StringResourceRepository getRepository()
160     {
161         return getRepository(REPOSITORY_NAME_DEFAULT);
162     }
163 
164     /**
165      * Returns a reference to the repository stored statically under the
166      * specified name.
167      * @since 1.6
168      */
169     public static StringResourceRepository getRepository(String name)
170     {
171         return (StringResourceRepository)STATIC_REPOSITORIES.get(name);
172     }
173 
174     /**
175      * Sets the specified {@link StringResourceRepository} in static storage
176      * under the specified name.
177      * @since 1.6
178      */
179     public static void setRepository(String name, StringResourceRepository repo)
180     {
181         STATIC_REPOSITORIES.put(name, repo);
182     }
183 
184     /**
185      * Removes the {@link StringResourceRepository} stored under the specified
186      * name.
187      * @since 1.6
188      */
189     public static StringResourceRepository removeRepository(String name)
190     {
191         return (StringResourceRepository)STATIC_REPOSITORIES.remove(name);
192     }
193 
194     /**
195      * Removes all statically stored {@link StringResourceRepository}s.
196      * @since 1.6
197      */
198     public static void clearRepositories()
199     {
200         STATIC_REPOSITORIES.clear();
201     }
202 
203 
204     // the repository used internally by this resource loader
205     protected StringResourceRepository repository;
206 
207 
208     /**
209      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
210      */
211     public void init(final ExtendedProperties configuration)
212     {
213         log.trace("StringResourceLoader : initialization starting.");
214 
215         // get the repository configuration info
216         String repoClass = configuration.getString(REPOSITORY_CLASS, REPOSITORY_CLASS_DEFAULT);
217         String repoName = configuration.getString(REPOSITORY_NAME, REPOSITORY_NAME_DEFAULT);
218         boolean isStatic = configuration.getBoolean(REPOSITORY_STATIC, REPOSITORY_STATIC_DEFAULT);
219         String encoding = configuration.getString(REPOSITORY_ENCODING);
220 
221         // look for an existing repository of that name and isStatic setting
222         if (isStatic)
223         {
224             this.repository = getRepository(repoName);
225             if (repository != null && log.isDebugEnabled())
226             {
227                 log.debug("Loaded repository '"+repoName+"' from static repo store");
228             }
229         }
230         else
231         {
232             this.repository = (StringResourceRepository)rsvc.getApplicationAttribute(repoName);
233             if (repository != null && log.isDebugEnabled())
234             {
235                 log.debug("Loaded repository '"+repoName+"' from application attributes");
236             }
237         }
238 
239         if (this.repository == null)
240         {
241             // since there's no repository under the repo name, create a new one
242             this.repository = createRepository(repoClass, encoding);
243 
244             // and store it according to the isStatic setting
245             if (isStatic)
246             {
247                 setRepository(repoName, this.repository);
248             }
249             else
250             {
251                 rsvc.setApplicationAttribute(repoName, this.repository);
252             }
253         }
254         else
255         {
256             // ok, we already have a repo
257             // warn them if they are trying to change the class of the repository
258             if (!this.repository.getClass().getName().equals(repoClass))
259             {
260                 log.debug("Cannot change class of string repository '"+repoName+
261                           "' from "+this.repository.getClass().getName()+" to "+repoClass+
262                           ". The change will be ignored.");
263             }
264 
265             // allow them to change the default encoding of the repo
266             if (encoding != null &&
267                 !this.repository.getEncoding().equals(encoding))
268             {
269                 if (log.isDebugEnabled())
270                 {
271                     log.debug("Changing the default encoding of string repository '"+repoName+
272                               "' from "+this.repository.getEncoding()+" to "+encoding);
273                 }
274                 this.repository.setEncoding(encoding);
275             }
276         }
277 
278         log.trace("StringResourceLoader : initialization complete.");
279     }
280 
281     /**
282      * @since 1.6
283      */
284     public StringResourceRepository createRepository(final String className,
285                                                      final String encoding)
286     {
287         if (log.isDebugEnabled())
288         {
289             log.debug("Creating string repository using class "+className+"...");
290         }
291 
292         StringResourceRepository repo;
293         try
294         {
295             repo = (StringResourceRepository) ClassUtils.getNewInstance(className);
296         }
297         catch (ClassNotFoundException cnfe)
298         {
299             throw new VelocityException("Could not find '" + className + "'", cnfe);
300         }
301         catch (IllegalAccessException iae)
302         {
303             throw new VelocityException("Could not access '" + className + "'", iae);
304         }
305         catch (InstantiationException ie)
306         {
307             throw new VelocityException("Could not instantiate '" + className + "'", ie);
308         }
309 
310         if (encoding != null)
311         {
312             repo.setEncoding(encoding);
313         }
314         else
315         {
316             repo.setEncoding(REPOSITORY_ENCODING_DEFAULT);
317         }
318 
319         if (log.isDebugEnabled())
320         {
321             log.debug("Default repository encoding is " + repo.getEncoding());
322         }
323         return repo;
324     }
325 
326     /**
327      * Overrides superclass for better performance.
328      * @since 1.6
329      */
330     public boolean resourceExists(final String name)
331     {
332         if (name == null)
333         {
334             return false;
335         }
336         return (this.repository.getStringResource(name) != null);
337     }
338 
339     /**
340      * Get an InputStream so that the Runtime can build a
341      * template with it.
342      *
343      * @param name name of template to get.
344      * @return InputStream containing the template.
345      * @throws ResourceNotFoundException Ff template not found
346      *         in the RepositoryFactory.
347      */
348     public InputStream getResourceStream(final String name)
349             throws ResourceNotFoundException
350     {
351         if (StringUtils.isEmpty(name))
352         {
353             throw new ResourceNotFoundException("No template name provided");
354         }
355 
356         StringResource resource = this.repository.getStringResource(name);
357         
358         if(resource == null)
359         {
360             throw new ResourceNotFoundException("Could not locate resource '" + name + "'");
361         }
362         
363         byte [] byteArray = null;
364     	
365         try
366         {
367             byteArray = resource.getBody().getBytes(resource.getEncoding());
368             return new ByteArrayInputStream(byteArray);
369         }
370         catch(UnsupportedEncodingException ue)
371         {
372             throw new VelocityException("Could not convert String using encoding " + resource.getEncoding(), ue);
373         }
374     }
375 
376     /**
377      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
378      */
379     public boolean isSourceModified(final Resource resource)
380     {
381         StringResource original = null;
382         boolean result = true;
383 
384         original = this.repository.getStringResource(resource.getName());
385 
386         if (original != null)
387         {
388             result =  original.getLastModified() != resource.getLastModified();
389         }
390 
391         return result;
392     }
393 
394     /**
395      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
396      */
397     public long getLastModified(final Resource resource)
398     {
399         StringResource original = null;
400 
401         original = this.repository.getStringResource(resource.getName());
402 
403         return (original != null)
404                 ? original.getLastModified()
405                 : 0;
406     }
407 
408 }
409