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