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