001/* 002 * Copyright 2013 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.site; 017 018import java.io.File; 019import java.io.IOException; 020 021import org.apache.cocoon.ProcessingException; 022import org.apache.cocoon.components.source.SourceUtil; 023import org.apache.cocoon.environment.SourceResolver; 024import org.apache.commons.codec.digest.DigestUtils; 025import org.apache.commons.io.FilenameUtils; 026import org.apache.commons.lang.StringUtils; 027import org.apache.excalibur.source.Source; 028import org.apache.excalibur.source.SourceException; 029import org.apache.excalibur.source.impl.FileSource; 030 031import org.ametys.core.util.URLEncoder; 032import org.ametys.runtime.config.Config; 033import org.ametys.runtime.servlet.RuntimeConfig; 034 035/** 036 * Class providing helper methods for cache files. 037 */ 038public final class SiteCacheHelper 039{ 040 041 private static final String __MD5_PREFIX = "md5$"; 042 043 private SiteCacheHelper() 044 { 045 // Hidden. 046 } 047 048 /** 049 * Return the root cache file 050 * @return The root cache File 051 */ 052 public static File getRootCache() 053 { 054 return new File(RuntimeConfig.getInstance().getAmetysHome(), "cache"); 055 } 056 057 /** 058 * Test if a file is valid. 059 * @param file the file to test. 060 * @return true if the file is valid, false otherwise. 061 */ 062 public static boolean isValid(File file) 063 { 064 boolean valid = true; 065 066 long maxLength = Config.getInstance().getValue("org.ametys.site.cache.max.filename.length"); 067 068 try 069 { 070 file.getCanonicalPath(); 071 file.toURI(); 072 073 valid = file.getName().length() <= maxLength; 074 075 File parent = file.getParentFile(); 076 while (valid && parent != null) 077 { 078 valid = parent.getName().length() <= maxLength; 079 parent = parent.getParentFile(); 080 } 081 } 082 catch (IOException e) 083 { 084 valid = false; 085 } 086 087 return valid; 088 } 089 090 /** 091 * Test if a file source is valid. 092 * @param fileSource the file source to test. 093 * @return true if the file is valid, false otherwise. 094 */ 095 public static boolean isValid(FileSource fileSource) 096 { 097 return isValid(fileSource.getFile()); 098 } 099 100 /** 101 * Get a hashed file path for the given file. 102 * @param file the original resource file. 103 * @return the hashed file path. 104 */ 105 public static String getHashedFilePath(File file) 106 { 107 return getHashedFilePath(file, false); 108 } 109 110 /** 111 * Get a hashed file path for the given file. 112 * @param file the original resource file. 113 * @param encode true to encode the folder parts, false otherwise. 114 * @return the hashed file path. 115 */ 116 public static String getHashedFilePath(File file, boolean encode) 117 { 118 return getHashedFilePath(file.getPath(), encode); 119 } 120 121 /** 122 * Get a hashed version of the given file path. 123 * @param filePath the original file path. 124 * @return the hashed file path. 125 */ 126 public static String getHashedFilePath(String filePath) 127 { 128 return getHashedFilePath(filePath, false); 129 } 130 131 /** 132 * Get a hashed version of the given file path. 133 * @param filePath the original file path. 134 * @param encode true to encode the folder parts, false otherwise. 135 * @return the hashed file path. 136 */ 137 public static String getHashedFilePath(String filePath, boolean encode) 138 { 139 long maxLength = Config.getInstance().getValue("org.ametys.site.cache.max.filename.length"); 140 141 String prefix = FilenameUtils.getPrefix(filePath); 142 String basePath = FilenameUtils.getPath(filePath); 143 String baseName = FilenameUtils.getBaseName(filePath); 144 String name = FilenameUtils.getName(filePath); 145 146 String encodedBasePath = hashPath(basePath, encode); 147 148 if (name.length() > maxLength) 149 { 150 String extension = FilenameUtils.getExtension(filePath); 151 name = __MD5_PREFIX + DigestUtils.md5Hex(baseName) + "." + extension; 152 } 153 154 String validPath = prefix + encodedBasePath + name; 155 156 return validPath; 157 } 158 159 /** 160 * Release the given file source and resolve a hashed version of the file to replace it. 161 * @param resolver the source resolver. 162 * @param source the invalid file source to hash. 163 * @return the hashed file source. 164 * @throws IOException if an IO error occurs. 165 * @throws ProcessingException if the new source couldn't be resolved. 166 */ 167 public static Source getHashedFileSource(SourceResolver resolver, FileSource source) throws IOException, ProcessingException 168 { 169 File file = source.getFile(); 170 171 String validPath = SiteCacheHelper.getHashedFilePath(file, true); 172 String prefix = source.getScheme() + ":"; 173 174 try 175 { 176 // Release the old source 177 resolver.release(source); 178 179 // Resolve the new source. 180 return resolver.resolveURI(prefix + validPath); 181 } 182 catch (SourceException e) 183 { 184 throw SourceUtil.handle("Error during resolving of '" + validPath + "'.", e); 185 } 186 } 187 188 /** 189 * Hash the resource path 190 * @param path the resource path 191 * @param encode true to encode the un-hashed paths, false otherwise. 192 * @return the hashed and encoded URI 193 */ 194 private static String hashPath(String path, boolean encode) 195 { 196 long maxLength = Config.getInstance().getValue("org.ametys.site.cache.max.filename.length"); 197 198 StringBuilder sb = new StringBuilder(); 199 200 String[] parts = StringUtils.split(path, "/\\"); 201 for (int i = 0; i < parts.length; i++) 202 { 203 if (i > 0 || path.charAt(0) == File.separatorChar) 204 { 205 sb.append(File.separatorChar); 206 } 207 208 if (parts[i].length() > maxLength) 209 { 210 // Hash the path part. 211 sb.append(__MD5_PREFIX).append(DigestUtils.md5Hex(parts[i])); 212 } 213 else if (encode) 214 { 215 // Encode the path part. 216 sb.append(URLEncoder.encodePath(parts[i])); 217 } 218 else 219 { 220 // Leave the path part as is. 221 sb.append(parts[i]); 222 } 223 } 224 225 if (sb.charAt(sb.length() - 1) != File.separatorChar) 226 { 227 sb.append(File.separatorChar); 228 } 229 230 return sb.toString(); 231 } 232 233}