View Javadoc

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