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 535935 2007-05-07 17:24:04Z nbubna $
110  */
111 public class StringResourceLoader extends ResourceLoader
112 {
113     /** Key to determine whether the repository should be set as the static one or not. */
114     public static final String REPOSITORY_STATIC = "repository.static";
115 
116     /** By default, repositories are stored statically (shared across the VM). */
117     public static final boolean REPOSITORY_STATIC_DEFAULT = true;
118 
119     /** Key to look up the repository implementation class. */
120     public static final String REPOSITORY_CLASS = "repository.class";
121 
122     /** The default implementation class. */
123     public static final String REPOSITORY_CLASS_DEFAULT =
124         StringResourceRepositoryImpl.class.getName();
125 
126     /** Key to look up the name for the repository to be used. */
127     public static final String REPOSITORY_NAME = "repository.name";
128 
129     /** The default name for string resource repositories
130      * ('org.apache.velocity.runtime.resource.util.StringResourceRepository'). */
131     public static final String REPOSITORY_NAME_DEFAULT =
132         StringResourceRepository.class.getName();
133 
134     /** Key to look up the repository char encoding. */
135     public static final String REPOSITORY_ENCODING = "repository.encoding";
136 
137     /** The default repository encoding. */
138     public static final String REPOSITORY_ENCODING_DEFAULT = "UTF-8";
139 
140 
141     protected static final Map STATIC_REPOSITORIES =
142         Collections.synchronizedMap(new HashMap());
143 
144     /**
145      * Returns a reference to the default static repository.
146      */
147     public static StringResourceRepository getRepository()
148     {
149         return getRepository(REPOSITORY_NAME_DEFAULT);
150     }
151 
152     /**
153      * Returns a reference to the repository stored statically under the
154      * specified name.
155      */
156     public static StringResourceRepository getRepository(String name)
157     {
158         return (StringResourceRepository)STATIC_REPOSITORIES.get(name);
159     }
160 
161     /**
162      * Sets the specified {@link StringResourceRepository} in static storage
163      * under the specified name.
164      */
165     public static void setRepository(String name, StringResourceRepository repo)
166     {
167         STATIC_REPOSITORIES.put(name, repo);
168     }
169 
170     /**
171      * Removes the {@link StringResourceRepository} stored under the specified
172      * name.
173      */
174     public static StringResourceRepository removeRepository(String name)
175     {
176         return (StringResourceRepository)STATIC_REPOSITORIES.remove(name);
177     }
178 
179     /**
180      * Removes all statically stored {@link StringResourceRepository}s.
181      */
182     public static void clearRepositories()
183     {
184         STATIC_REPOSITORIES.clear();
185     }
186 
187 
188     // the repository used internally by this resource loader
189     protected StringResourceRepository repository;
190 
191 
192     /**
193      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
194      */
195     public void init(final ExtendedProperties configuration)
196     {
197         log.trace("StringResourceLoader : initialization starting.");
198 
199         // get the repository configuration info
200         String repoClass = configuration.getString(REPOSITORY_CLASS, REPOSITORY_CLASS_DEFAULT);
201         String repoName = configuration.getString(REPOSITORY_NAME, REPOSITORY_NAME_DEFAULT);
202         boolean isStatic = configuration.getBoolean(REPOSITORY_STATIC, REPOSITORY_STATIC_DEFAULT);
203         String encoding = configuration.getString(REPOSITORY_ENCODING);
204 
205         // look for an existing repository of that name and isStatic setting
206         if (isStatic)
207         {
208             this.repository = getRepository(repoName);
209             if (repository != null && log.isDebugEnabled())
210             {
211                 log.debug("Loaded repository '"+repoName+"' from static repo store");
212             }
213         }
214         else
215         {
216             this.repository = (StringResourceRepository)rsvc.getApplicationAttribute(repoName);
217             if (repository != null && log.isDebugEnabled())
218             {
219                 log.debug("Loaded repository '"+repoName+"' from application attributes");
220             }
221         }
222 
223         if (this.repository == null)
224         {
225             // since there's no repository under the repo name, create a new one
226             this.repository = createRepository(repoClass, encoding);
227 
228             // and store it according to the isStatic setting
229             if (isStatic)
230             {
231                 setRepository(repoName, this.repository);
232             }
233             else
234             {
235                 rsvc.setApplicationAttribute(repoName, this.repository);
236             }
237         }
238         else
239         {
240             // ok, we already have a repo
241             // warn them if they are trying to change the class of the repository
242             if (!this.repository.getClass().getName().equals(repoClass))
243             {
244                 log.warn("Cannot change class of string repository '"+repoName+
245                           "' from "+this.repository.getClass().getName()+" to "+repoClass);
246             }
247 
248             // allow them to change the default encoding of the repo
249             if (encoding != null &&
250                 !this.repository.getEncoding().equals(encoding))
251             {
252                 if (log.isInfoEnabled())
253                 {
254                     log.info("Changing the default encoding of string repository '"+repoName+
255                              "' from "+this.repository.getEncoding()+" to "+encoding);
256                 }
257                 this.repository.setEncoding(encoding);
258             }
259         }
260 
261         log.trace("StringResourceLoader : initialization complete.");
262     }
263 
264 
265     public StringResourceRepository createRepository(final String className,
266                                                      final String encoding)
267     {
268         if (log.isDebugEnabled())
269         {
270             log.debug("Creating string repository using class "+className+"...");
271         }
272 
273         StringResourceRepository repo;
274         try
275         {
276             repo = (StringResourceRepository) ClassUtils.getNewInstance(className);
277         }
278         catch (ClassNotFoundException cnfe)
279         {
280             throw new VelocityException("Could not find '" + className + "'", cnfe);
281         }
282         catch (IllegalAccessException iae)
283         {
284             throw new VelocityException("Could not access '" + className + "'", iae);
285         }
286         catch (InstantiationException ie)
287         {
288             throw new VelocityException("Could not instantiate '" + className + "'", ie);
289         }
290 
291         if (encoding != null)
292         {
293             repo.setEncoding(encoding);
294         }
295         else
296         {
297             repo.setEncoding(REPOSITORY_ENCODING_DEFAULT);
298         }
299 
300         if (log.isDebugEnabled())
301         {
302             log.debug("Default repository encoding is " + repo.getEncoding());
303         }
304         return repo;
305     }
306 
307 
308     /**
309      * Get an InputStream so that the Runtime can build a
310      * template with it.
311      *
312      * @param name name of template to get.
313      * @return InputStream containing the template.
314      * @throws ResourceNotFoundException Ff template not found
315      *         in the RepositoryFactory.
316      */
317     public InputStream getResourceStream(final String name)
318             throws ResourceNotFoundException
319     {
320         if (StringUtils.isEmpty(name))
321         {
322             throw new ResourceNotFoundException("No template name provided");
323         }
324 
325         StringResource resource = this.repository.getStringResource(name);
326         
327         if(resource == null)
328         {
329             throw new ResourceNotFoundException("Could not locate resource '" + name + "'");
330         }
331         
332         byte [] byteArray = null;
333     	
334         try
335         {
336             byteArray = resource.getBody().getBytes(resource.getEncoding());
337             return new ByteArrayInputStream(byteArray);
338         }
339         catch(UnsupportedEncodingException ue)
340         {
341             throw new VelocityException("Could not convert String using encoding " + resource.getEncoding(), ue);
342         }
343     }
344 
345     /**
346      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
347      */
348     public boolean isSourceModified(final Resource resource)
349     {
350         StringResource original = null;
351         boolean result = true;
352 
353         original = this.repository.getStringResource(resource.getName());
354 
355         if (original != null)
356         {
357             result =  original.getLastModified() != resource.getLastModified();
358         }
359 
360         return result;
361     }
362 
363     /**
364      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
365      */
366     public long getLastModified(final Resource resource)
367     {
368         StringResource original = null;
369 
370         original = this.repository.getStringResource(resource.getName());
371 
372         return (original != null)
373                 ? original.getLastModified()
374                 : 0;
375     }
376 
377 }
378