001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.cache; 029 030import org.opencms.file.CmsResource; 031import org.opencms.main.CmsLog; 032import org.opencms.util.CmsFileUtil; 033 034import java.io.File; 035import java.io.FileInputStream; 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.UnsupportedEncodingException; 039 040import org.apache.commons.codec.digest.DigestUtils; 041import org.apache.commons.logging.Log; 042 043/** 044 * Implements a name based RFS file based disk cache, that handles parameter based versions of VFS files.<p> 045 * 046 * This RFS cache operates on file names, plus a hash code calculated from 047 * {@link org.opencms.file.CmsResource#getDateLastModified()}, {@link org.opencms.file.CmsResource#getDateCreated()} 048 * and {@link org.opencms.file.CmsResource#getLength()}. Optional parameters can be appended to this name, 049 * which will be added as a second hash code. This way a file can have multiple versions based on different parameters.<p> 050 * 051 * This cache is usable for resources from the online AND the offline project at the same time, 052 * because any change to a resource will result in a changed hash code. This means a resource changed in the offline 053 * project will have a new hash code compared to the online project. If the resource is identical in the online and 054 * the offline project, the generated hash codes will be the same.<p> 055 * 056 * @since 6.2.0 057 */ 058public class CmsVfsNameBasedDiskCache { 059 060 /** Logger instance for this class. */ 061 private static final Log LOG = CmsLog.getLog(CmsVfsNameBasedDiskCache.class); 062 063 /** The name of the cache base repository folder in the RFS. */ 064 private String m_rfsRepository; 065 066 /** 067 * Creates a new disk cache.<p> 068 * 069 * @param basepath the base path for the cache in the RFS 070 * @param foldername the folder name for this cache, to be used a sub-folder for the base folder 071 */ 072 public CmsVfsNameBasedDiskCache(String basepath, String foldername) { 073 074 // normalize the given folder name 075 m_rfsRepository = CmsFileUtil.normalizePath(basepath + foldername + File.separatorChar); 076 } 077 078 /** 079 * Creates the file for the requested disk cache entry and its parent folders.<p> 080 * 081 * @param rfsName the file RFS name to create 082 * 083 * @return the cache file 084 */ 085 public File createCacheFile(String rfsName) { 086 087 return CmsVfsDiskCache.createFile(rfsName); 088 } 089 090 /** 091 * Returns the content of the requested file in the disk cache, or <code>null</code> if the 092 * file is not found in the cache, or is found but outdated.<p> 093 * 094 * @param rfsName the file RFS name to look up in the cache 095 * 096 * @return the content of the requested file in the disk cache, or <code>null</code> 097 */ 098 public byte[] getCacheContent(String rfsName) { 099 100 try { 101 File f = new File(rfsName); 102 if (f.exists()) { 103 long age = f.lastModified(); 104 if ((System.currentTimeMillis() - age) > 3600000) { 105 // file has not been touched for 1 hour, touch the file with the current date 106 f.setLastModified(System.currentTimeMillis()); 107 } 108 return CmsFileUtil.readFile(f); 109 } 110 } catch (IOException e) { 111 // unable to read content 112 LOG.debug("Unable to read file " + rfsName, e); 113 } 114 return null; 115 } 116 117 /** 118 * Returns the file for the requested disk cache entry, or <code>null</code> if the file is not available.<p> 119 * 120 * @param rfsName the file RFS name to look up in the cache 121 * 122 * @return the cache file, or <code>null</code> 123 */ 124 public File getCacheFile(String rfsName) { 125 126 File f = new File(rfsName); 127 if (f.exists()) { 128 long age = f.lastModified(); 129 if ((System.currentTimeMillis() - age) > 3600000) { 130 // file has not been touched for 1 hour, touch the file with the current date 131 f.setLastModified(System.currentTimeMillis()); 132 } 133 return f; 134 } 135 return null; 136 } 137 138 /** 139 * Returns an input stream for the requested disk cache entry, or <code>null</code> if the file is not available.<p> 140 * 141 * @param rfsName the file RFS name to look up in the cache 142 * 143 * @return the cache input stream, or <code>null</code> 144 * 145 * @throws IOException in case of disk access errors 146 */ 147 public InputStream getCacheInputStream(String rfsName) throws IOException { 148 149 File cacheFile = getCacheFile(rfsName); 150 if (cacheFile != null) { 151 return new FileInputStream(cacheFile); 152 } 153 return null; 154 } 155 156 /** 157 * Returns the RFS name to use for caching the given VFS resource with parameters in the disk cache.<p> 158 * 159 * @param resource the VFS resource to generate the cache name for 160 * @param parameters the parameters of the request to the VFS resource 161 * 162 * @return the RFS name to use for caching the given VFS resource with parameters 163 */ 164 public String getCacheName(CmsResource resource, String parameters) { 165 166 // calculate the base cache path for the resource 167 String rfsName = m_rfsRepository + resource.getRootPath(); 168 String extension = CmsFileUtil.getExtension(rfsName); 169 170 // create a StringBuffer for the result 171 StringBuffer buf = new StringBuffer(rfsName.length() + 24); 172 buf.append(rfsName.substring(0, rfsName.length() - extension.length())); 173 174 // calculate a hash code that contains the resource DateLastModified, DateCreated and Length 175 StringBuffer ext = new StringBuffer(48); 176 ext.append(resource.getDateLastModified()); 177 ext.append(';'); 178 ext.append(resource.getDateCreated()); 179 if (resource.getLength() > 0) { 180 ext.append(';'); 181 ext.append(resource.getLength()); 182 } 183 // append hash code to the result buffer 184 buf.append('_'); 185 buf.append(ext.toString().hashCode()); 186 187 // check if parameters are provided, if so add them as well 188 if (parameters != null) { 189 buf.append('_'); 190 try { 191 buf.append(DigestUtils.md5Hex(parameters.getBytes("UTF-8"))); 192 } catch (UnsupportedEncodingException e) { 193 // can't happen 194 LOG.error(e.getLocalizedMessage(), e); 195 } 196 } 197 198 // finally append the extension from the original file name 199 buf.append(extension); 200 return buf.toString(); 201 } 202 203 /** 204 * Returns the absolute path of the cache repository in the RFS.<p> 205 * 206 * @return the absolute path of the cache repository in the RFS 207 */ 208 public String getRepositoryPath() { 209 210 return m_rfsRepository; 211 } 212 213 /** 214 * Returns the the requested file is available within the cache.<p> 215 * 216 * @param rfsName the file name 217 * 218 * @return <code>true</code> if the file is available 219 */ 220 public boolean hasCacheContent(String rfsName) { 221 222 File f = new File(rfsName); 223 if (f.exists()) { 224 return true; 225 } 226 return false; 227 } 228 229 /** 230 * Saves the given file content in the disk cache.<p> 231 * 232 * @param rfsName the RFS name of the file to save the content in 233 * @param content the content of the file to save 234 * 235 * @throws IOException in case of disk access errors 236 */ 237 public void saveCacheFile(String rfsName, byte[] content) throws IOException { 238 239 CmsVfsDiskCache.saveFile(rfsName, content); 240 } 241 242 /** 243 * Saves the given input stream content in the disk cache.<p> 244 * 245 * @param rfsName the RFS name of the file to save the content in 246 * @param content the input stream to save 247 * 248 * @throws IOException in case of disk access errors 249 */ 250 public void saveCacheFile(String rfsName, InputStream content) throws IOException { 251 252 CmsVfsDiskCache.saveFile(rfsName, content); 253 } 254}