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.runtime.RuntimeConstants; 25 import org.apache.velocity.runtime.resource.Resource; 26 import org.apache.velocity.util.ExtProperties; 27 28 import org.apache.commons.io.FilenameUtils; 29 import org.apache.commons.lang3.StringUtils; 30 31 import java.io.File; 32 import java.io.FileInputStream; 33 import java.io.FileNotFoundException; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.Reader; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.ListIterator; 42 import java.util.Map; 43 44 /** 45 * A loader for templates stored on the file system. Treats the template 46 * as relative to the configured root path. If the root path is empty 47 * treats the template name as an absolute path. 48 * 49 * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> 50 * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a> 51 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 52 * @version $Id$ 53 */ 54 public class FileResourceLoader extends ResourceLoader 55 { 56 /** 57 * The paths to search for templates. 58 */ 59 private List<String> paths = new ArrayList<>(); 60 61 /** 62 * Used to map the path that a template was found on 63 * so that we can properly check the modification 64 * times of the files. This is synchronizedMap 65 * instance. 66 */ 67 private Map<String, String> templatePaths = Collections.synchronizedMap(new HashMap<>()); 68 69 /** 70 * @see ResourceLoader#init(org.apache.velocity.util.ExtProperties) 71 */ 72 @Override init(ExtProperties configuration)73 public void init(ExtProperties configuration) 74 { 75 log.trace("FileResourceLoader: initialization starting."); 76 77 paths.addAll( configuration.getVector(RuntimeConstants.RESOURCE_LOADER_PATHS) ); 78 79 // trim spaces from all paths 80 for (ListIterator<String> it = paths.listIterator(); it.hasNext(); ) 81 { 82 String path = StringUtils.trim(it.next()); 83 it.set(path); 84 log.debug("FileResourceLoader: adding path '{}'", path); 85 } 86 log.trace("FileResourceLoader: initialization complete."); 87 } 88 89 /** 90 * Get a Reader so that the Runtime can build a 91 * template with it. 92 * 93 * @param templateName name of template to get 94 * @return Reader containing the template 95 * @throws ResourceNotFoundException if template not found 96 * in the file template path. 97 * @since 2.0 98 */ 99 @Override getResourceReader(String templateName, String encoding)100 public Reader getResourceReader(String templateName, String encoding) 101 throws ResourceNotFoundException 102 { 103 /* 104 * Make sure we have a valid templateName. 105 */ 106 if (org.apache.commons.lang3.StringUtils.isEmpty(templateName)) 107 { 108 /* 109 * If we don't get a properly formed templateName then 110 * there's not much we can do. So we'll forget about 111 * trying to search any more paths for the template. 112 */ 113 throw new ResourceNotFoundException( 114 "Need to specify a file name or file path!"); 115 } 116 117 String template = FilenameUtils.normalize( templateName, true ); 118 if ( template == null || template.length() == 0 ) 119 { 120 String msg = "File resource error: argument " + template + 121 " contains .. and may be trying to access " + 122 "content outside of template root. Rejected."; 123 124 log.error("FileResourceLoader: {}", msg); 125 126 throw new ResourceNotFoundException ( msg ); 127 } 128 129 int size = paths.size(); 130 for (String path : paths) 131 { 132 InputStream rawStream = null; 133 Reader reader = null; 134 135 try 136 { 137 rawStream = findTemplate(path, template); 138 if (rawStream != null) 139 { 140 reader = buildReader(rawStream, encoding); 141 } 142 } 143 catch (IOException ioe) 144 { 145 closeQuiet(rawStream); 146 String msg = "Exception while loading Template " + template; 147 log.error(msg, ioe); 148 throw new VelocityException(msg, ioe, rsvc.getLogContext().getStackTrace()); 149 } 150 if (reader != null) 151 { 152 /* 153 * Store the path that this template came 154 * from so that we can check its modification 155 * time. 156 */ 157 templatePaths.put(templateName, path); 158 return reader; 159 } 160 } 161 162 /* 163 * We have now searched all the paths for 164 * templates and we didn't find anything so 165 * throw an exception. 166 */ 167 throw new ResourceNotFoundException("FileResourceLoader: cannot find " + template); 168 } 169 170 /** 171 * Overrides superclass for better performance. 172 * @since 1.6 173 */ 174 @Override resourceExists(String name)175 public boolean resourceExists(String name) 176 { 177 if (name == null) 178 { 179 return false; 180 } 181 name = FilenameUtils.normalize(name); 182 if (name == null || name.length() == 0) 183 { 184 return false; 185 } 186 187 int size = paths.size(); 188 for (String path : paths) 189 { 190 try 191 { 192 File file = getFile(path, name); 193 if (file.canRead()) 194 { 195 return true; 196 } 197 } 198 catch (Exception ioe) 199 { 200 log.debug("Exception while checking for template {}", name); 201 } 202 } 203 return false; 204 } 205 206 /** 207 * Try to find a template given a normalized path. 208 * 209 * @param path a normalized path 210 * @param template name of template to find 211 * @return InputStream input stream that will be parsed 212 * 213 */ findTemplate(final String path, final String template)214 private InputStream findTemplate(final String path, final String template) 215 throws IOException 216 { 217 try 218 { 219 File file = getFile(path, template); 220 221 if (file.canRead()) 222 { 223 FileInputStream fis = null; 224 try 225 { 226 fis = new FileInputStream(file.getAbsolutePath()); 227 return fis; 228 } 229 catch (IOException e) 230 { 231 closeQuiet(fis); 232 throw e; 233 } 234 } 235 else 236 { 237 return null; 238 } 239 } 240 catch(FileNotFoundException fnfe) 241 { 242 /* 243 * log and convert to a general Velocity ResourceNotFoundException 244 */ 245 return null; 246 } 247 } 248 closeQuiet(final InputStream is)249 private void closeQuiet(final InputStream is) 250 { 251 if (is != null) 252 { 253 try 254 { 255 is.close(); 256 } 257 catch(IOException ioe) 258 { 259 // Ignore 260 } 261 } 262 } 263 264 /** 265 * How to keep track of all the modified times 266 * across the paths. Note that a file might have 267 * appeared in a directory which is earlier in the 268 * path; so we should search the path and see if 269 * the file we find that way is the same as the one 270 * that we have cached. 271 * @param resource 272 * @return True if the source has been modified. 273 */ 274 @Override isSourceModified(Resource resource)275 public boolean isSourceModified(Resource resource) 276 { 277 /* 278 * we assume that the file needs to be reloaded; 279 * if we find the original file and it's unchanged, 280 * then we'll flip this. 281 */ 282 boolean modified = true; 283 284 String fileName = resource.getName(); 285 String path = templatePaths.get(fileName); 286 File currentFile = null; 287 288 for (int i = 0; currentFile == null && i < paths.size(); i++) 289 { 290 String testPath = (String) paths.get(i); 291 File testFile = getFile(testPath, fileName); 292 if (testFile.canRead()) 293 { 294 currentFile = testFile; 295 } 296 } 297 File file = getFile(path, fileName); 298 if (currentFile == null || !file.exists()) 299 { 300 /* 301 * noop: if the file is missing now (either the cached 302 * file is gone, or the file can no longer be found) 303 * then we leave modified alone (it's set to true); a 304 * reload attempt will be done, which will either use 305 * a new template or fail with an appropriate message 306 * about how the file couldn't be found. 307 */ 308 } 309 else if (currentFile.equals(file) && file.canRead()) 310 { 311 /* 312 * if only if currentFile is the same as file and 313 * file.lastModified() is the same as 314 * resource.getLastModified(), then we should use the 315 * cached version. 316 */ 317 modified = (file.lastModified() != resource.getLastModified()); 318 } 319 320 /* 321 * rsvc.debug("isSourceModified for " + fileName + ": " + modified); 322 */ 323 return modified; 324 } 325 326 /** 327 * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource) 328 */ 329 @Override getLastModified(Resource resource)330 public long getLastModified(Resource resource) 331 { 332 String path = templatePaths.get(resource.getName()); 333 File file = getFile(path, resource.getName()); 334 335 if (file.canRead()) 336 { 337 return file.lastModified(); 338 } 339 else 340 { 341 return 0; 342 } 343 } 344 345 346 /** 347 * Create a File based on either a relative path if given, or absolute path otherwise 348 */ getFile(String path, String template)349 private File getFile(String path, String template) 350 { 351 352 File file = null; 353 354 if("".equals(path)) 355 { 356 file = new File( template ); 357 } 358 else 359 { 360 /* 361 * if a / leads off, then just nip that :) 362 */ 363 if (template.startsWith("/")) 364 { 365 template = template.substring(1); 366 } 367 368 file = new File ( path, template ); 369 } 370 371 return file; 372 } 373 } 374