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