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.velocity.exception.ResourceNotFoundException; 23 import org.apache.velocity.exception.VelocityException; 24 import org.apache.velocity.io.UnicodeInputStream; 25 import org.apache.velocity.runtime.RuntimeConstants; 26 import org.apache.velocity.runtime.RuntimeServices; 27 import org.apache.velocity.runtime.resource.Resource; 28 import org.apache.velocity.runtime.resource.ResourceCacheImpl; 29 import org.apache.velocity.util.ExtProperties; 30 31 import org.slf4j.Logger; 32 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.InputStreamReader; 36 import java.io.Reader; 37 import java.io.UnsupportedEncodingException; 38 39 /** 40 * This is abstract class the all text resource loaders should 41 * extend. 42 * 43 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 44 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 45 * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a> 46 * @version $Id$ 47 */ 48 public abstract class ResourceLoader 49 { 50 /** 51 * Does this loader want templates produced with it 52 * cached in the Runtime. 53 */ 54 protected boolean isCachingOn = false; 55 56 /** 57 * This property will be passed on to the templates 58 * that are created with this loader. 59 */ 60 protected long modificationCheckInterval = 2; 61 62 /** 63 * Class name for this loader, for logging/debuggin 64 * purposes. 65 */ 66 protected String className = null; 67 68 protected RuntimeServices rsvc = null; 69 protected Logger log = null; 70 71 /** 72 * This initialization is used by all resource 73 * loaders and must be called to set up common 74 * properties shared by all resource loaders 75 * 76 * @param rs 77 * @param configuration 78 */ commonInit(RuntimeServices rs, ExtProperties configuration)79 public void commonInit(RuntimeServices rs, ExtProperties configuration) 80 { 81 this.rsvc = rs; 82 String loaderName = configuration.getString(RuntimeConstants.RESOURCE_LOADER_IDENTIFIER); 83 log = rsvc.getLog("loader." + (loaderName == null ? this.getClass().getSimpleName() : loaderName)); 84 85 /* 86 * these two properties are not required for all loaders. 87 * For example, for ClasspathLoader, what would cache mean? 88 * so adding default values which I think are the safest 89 * 90 * don't cache, and modCheckInterval irrelevant... 91 */ 92 93 try 94 { 95 isCachingOn = configuration.getBoolean(RuntimeConstants.RESOURCE_LOADER_CACHE, false); 96 } 97 catch (Exception e) 98 { 99 isCachingOn = false; 100 String msg = "Exception parsing cache setting: " + configuration.getString(RuntimeConstants.RESOURCE_LOADER_CACHE); 101 log.error(msg, e); 102 throw new VelocityException(msg, e); 103 } 104 try 105 { 106 modificationCheckInterval = configuration.getLong(RuntimeConstants.RESOURCE_LOADER_CHECK_INTERVAL, 0); 107 } 108 catch (Exception e) 109 { 110 modificationCheckInterval = 0; 111 String msg = "Exception parsing modificationCheckInterval setting: " + RuntimeConstants.RESOURCE_LOADER_CHECK_INTERVAL; 112 log.error(msg, e); 113 throw new VelocityException(msg, e); 114 } 115 116 /* 117 * this is a must! 118 */ 119 className = ResourceCacheImpl.class.getName(); 120 try 121 { 122 className = configuration.getString(RuntimeConstants.RESOURCE_LOADER_CLASS, className); 123 } 124 catch (Exception e) 125 { 126 String msg = "Exception retrieving resource cache class name"; 127 log.error(msg, e); 128 throw new VelocityException(msg, e); 129 } 130 } 131 132 /** 133 * Initialize the template loader with a 134 * a resources class. 135 * 136 * @param configuration 137 */ init(ExtProperties configuration)138 public abstract void init(ExtProperties configuration); 139 140 /** 141 * Get the Reader that the Runtime will parse 142 * to create a template. 143 * 144 * @param source 145 * @param encoding 146 * @return The reader for the requested resource. 147 * @throws ResourceNotFoundException 148 * @since 2.0 149 */ getResourceReader(String source, String encoding)150 public abstract Reader getResourceReader(String source, String encoding) 151 throws ResourceNotFoundException; 152 153 /** 154 * Given a template, check to see if the source of InputStream 155 * has been modified. 156 * 157 * @param resource 158 * @return True if the resource has been modified. 159 */ isSourceModified(Resource resource)160 public abstract boolean isSourceModified(Resource resource); 161 162 /** 163 * Get the last modified time of the InputStream source 164 * that was used to create the template. We need the template 165 * here because we have to extract the name of the template 166 * in order to locate the InputStream source. 167 * 168 * @param resource 169 * @return Time in millis when the resource has been modified. 170 */ getLastModified(Resource resource)171 public abstract long getLastModified(Resource resource); 172 173 /** 174 * Return the class name of this resource Loader 175 * 176 * @return Class name of the resource loader. 177 */ getClassName()178 public String getClassName() 179 { 180 return className; 181 } 182 183 /** 184 * Set the caching state. If true, then this loader 185 * would like the Runtime to cache templates that 186 * have been created with InputStreams provided 187 * by this loader. 188 * 189 * @param value 190 */ setCachingOn(boolean value)191 public void setCachingOn(boolean value) 192 { 193 isCachingOn = value; 194 } 195 196 /** 197 * The Runtime uses this to find out whether this 198 * template loader wants the Runtime to cache 199 * templates created with InputStreams provided 200 * by this loader. 201 * 202 * @return True if this resource loader caches. 203 */ isCachingOn()204 public boolean isCachingOn() 205 { 206 return isCachingOn; 207 } 208 209 /** 210 * Set the interval at which the InputStream source 211 * should be checked for modifications. 212 * 213 * @param modificationCheckInterval 214 */ setModificationCheckInterval(long modificationCheckInterval)215 public void setModificationCheckInterval(long modificationCheckInterval) 216 { 217 this.modificationCheckInterval = modificationCheckInterval; 218 } 219 220 /** 221 * Get the interval at which the InputStream source 222 * should be checked for modifications. 223 * 224 * @return The modification check interval. 225 */ getModificationCheckInterval()226 public long getModificationCheckInterval() 227 { 228 return modificationCheckInterval; 229 } 230 231 /** 232 * Check whether any given resource exists. This is not really 233 * a very efficient test and it can and should be overridden in the 234 * subclasses extending ResourceLoader2. 235 * 236 * @param resourceName The name of a resource. 237 * @return true if a resource exists and can be accessed. 238 * @since 1.6 239 */ resourceExists(final String resourceName)240 public boolean resourceExists(final String resourceName) 241 { 242 Reader reader = null; 243 try 244 { 245 reader = getResourceReader(resourceName, null); 246 } 247 catch (ResourceNotFoundException e) 248 { 249 log.debug("Could not load resource '{}' from ResourceLoader {}", 250 resourceName, this.getClass().getName()); 251 } 252 finally 253 { 254 try 255 { 256 if (reader != null) 257 { 258 reader.close(); 259 } 260 } 261 catch (Exception e) 262 { 263 String msg = "While closing InputStream for resource '" + 264 resourceName + "' from ResourceLoader " + 265 this.getClass().getName(); 266 log.error(msg, e); 267 throw new VelocityException(msg, e); 268 } 269 } 270 return (reader != null); 271 } 272 273 /** 274 * Builds a Reader given a raw InputStream and an encoding. Should be use 275 * by every subclass that whishes to accept optional BOMs in resources. 276 * This method does *not* close the given input stream whenever an exception is thrown. 277 * 278 * @param rawStream The raw input stream. 279 * @param encoding The asked encoding. 280 * @return found reader 281 * @throws IOException 282 * @throws UnsupportedEncodingException 283 * @since 2.0 284 */ buildReader(InputStream rawStream, String encoding)285 protected Reader buildReader(InputStream rawStream, String encoding) 286 throws IOException 287 { 288 UnicodeInputStream inputStream = new UnicodeInputStream(rawStream); 289 /* 290 * Check encoding 291 */ 292 String foundEncoding = inputStream.getEncodingFromStream(); 293 if (foundEncoding != null && encoding != null && !UnicodeInputStream.sameEncoding(foundEncoding, encoding)) 294 { 295 log.warn("Found BOM encoding '{}' differs from asked encoding: '{}' - using BOM encoding to read resource.", foundEncoding, encoding); 296 encoding = foundEncoding; 297 } 298 if (encoding == null) 299 { 300 if (foundEncoding == null) 301 { 302 encoding = rsvc.getString(RuntimeConstants.INPUT_ENCODING); 303 } else 304 { 305 encoding = foundEncoding; 306 } 307 } 308 309 try 310 { 311 return new InputStreamReader(inputStream, encoding); 312 } 313 catch (UnsupportedEncodingException uee) 314 { 315 try 316 { 317 inputStream.close(); 318 } 319 catch (IOException ioe) {} 320 throw uee; 321 } 322 } 323 324 } 325