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