View Javadoc

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