001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 package org.apache.felix.framework.cache; 020 021 import java.io.*; 022 import java.util.*; 023 024 import org.apache.felix.framework.Logger; 025 import org.apache.felix.framework.util.SecureAction; 026 import org.osgi.framework.Constants; 027 028 /** 029 * <p> 030 * This class, combined with <tt>BundleArchive</tt>, and concrete 031 * <tt>BundleRevision</tt> subclasses, implement the Felix bundle cache. 032 * It is possible to configure the default behavior of this class by 033 * passing properties into Felix' constructor. The configuration properties 034 * for this class are (properties starting with "<tt>felix</tt>" are specific 035 * to Felix, while those starting with "<tt>org.osgi</tt>" are standard OSGi 036 * properties): 037 * </p> 038 * <ul> 039 * <li><tt>org.osgi.framework.storage</tt> - Sets the directory to use as 040 * the bundle cache; by default bundle cache directory is 041 * <tt>felix-cache</tt> in the current working directory. The value 042 * should be a valid directory name. The directory name can be either absolute 043 * or relative. Relative directory names are relative to the current working 044 * directory. The specified directory will be created if it does 045 * not exist. 046 * </li> 047 * <li><tt>felix.cache.rootdir</tt> - Sets the root directory to use to 048 * calculate the bundle cache directory for relative directory names. If 049 * <tt>org.osgi.framework.storage</tt> is set to a relative name, by 050 * default it is relative to the current working directory. If this 051 * property is set, then it will be calculated as being relative to 052 * the specified root directory. 053 * </li> 054 * <li><tt>felix.cache.bufsize</tt> - Sets the buffer size to be used by 055 * the cache; the default value is 4096. The integer value of this 056 * string provides control over the size of the internal buffer of the 057 * disk cache for performance reasons. 058 * </li> 059 * <p> 060 * For specific information on how to configure the Felix framework, refer 061 * to the Felix framework usage documentation. 062 * </p> 063 * @see org.apache.felix.framework.cache.BundleArchive 064 **/ 065 public class BundleCache 066 { 067 public static final String CACHE_BUFSIZE_PROP = "felix.cache.bufsize"; 068 public static final String CACHE_ROOTDIR_PROP = "felix.cache.rootdir"; 069 070 protected static transient int BUFSIZE = 4096; 071 protected static transient final String CACHE_DIR_NAME = "felix-cache"; 072 protected static transient final String CACHE_ROOTDIR_DEFAULT = "."; 073 protected static transient final String BUNDLE_DIR_PREFIX = "bundle"; 074 075 private static final SecureAction m_secureAction = new SecureAction(); 076 077 private final Logger m_logger; 078 private final Map m_configMap; 079 080 public BundleCache(Logger logger, Map configMap) 081 { 082 m_logger = logger; 083 m_configMap = configMap; 084 } 085 086 /* package */ static SecureAction getSecureAction() 087 { 088 return m_secureAction; 089 } 090 091 public synchronized void delete() throws Exception 092 { 093 // Delete the cache directory. 094 File cacheDir = determineCacheDir(m_configMap); 095 deleteDirectoryTree(cacheDir); 096 } 097 098 public BundleArchive[] getArchives() 099 throws Exception 100 { 101 // Get buffer size value. 102 try 103 { 104 String sBufSize = (String) m_configMap.get(CACHE_BUFSIZE_PROP); 105 if (sBufSize != null) 106 { 107 BUFSIZE = Integer.parseInt(sBufSize); 108 } 109 } 110 catch (NumberFormatException ne) 111 { 112 // Use the default value. 113 } 114 115 // Create the cache directory, if it does not exist. 116 File cacheDir = determineCacheDir(m_configMap); 117 if (!getSecureAction().fileExists(cacheDir)) 118 { 119 if (!getSecureAction().mkdirs(cacheDir)) 120 { 121 m_logger.log( 122 Logger.LOG_ERROR, 123 "Unable to create cache directory: " + cacheDir); 124 throw new RuntimeException("Unable to create cache directory."); 125 } 126 } 127 128 // Create the existing bundle archives in the directory, if any exist. 129 List archiveList = new ArrayList(); 130 File[] children = getSecureAction().listDirectory(cacheDir); 131 for (int i = 0; (children != null) && (i < children.length); i++) 132 { 133 // Ignore directories that aren't bundle directories or 134 // is the system bundle directory. 135 if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX) && 136 !children[i].getName().equals(BUNDLE_DIR_PREFIX + Long.toString(0))) 137 { 138 // Recreate the bundle archive. 139 try 140 { 141 archiveList.add(new BundleArchive(m_logger, m_configMap, children[i])); 142 } 143 catch (Exception ex) 144 { 145 // Log exception and remove bundle archive directory. 146 m_logger.log(Logger.LOG_ERROR, 147 "Error reloading cached bundle, removing it: " + children[i], ex); 148 deleteDirectoryTree(children[i]); 149 } 150 } 151 } 152 153 return (BundleArchive[]) 154 archiveList.toArray(new BundleArchive[archiveList.size()]); 155 } 156 157 public BundleArchive create(long id, String location, InputStream is) 158 throws Exception 159 { 160 File cacheDir = determineCacheDir(m_configMap); 161 162 // Construct archive root directory. 163 File archiveRootDir = 164 new File(cacheDir, BUNDLE_DIR_PREFIX + Long.toString(id)); 165 166 try 167 { 168 // Create the archive and add it to the list of archives. 169 BundleArchive ba = 170 new BundleArchive(m_logger, m_configMap, archiveRootDir, id, location, is); 171 return ba; 172 } 173 catch (Exception ex) 174 { 175 if (m_secureAction.fileExists(archiveRootDir)) 176 { 177 if (!BundleCache.deleteDirectoryTree(archiveRootDir)) 178 { 179 m_logger.log( 180 Logger.LOG_ERROR, 181 "Unable to delete the archive directory: " 182 + archiveRootDir); 183 } 184 } 185 throw ex; 186 } 187 } 188 189 /** 190 * Provides the system bundle access to its private storage area; this 191 * special case is necessary since the system bundle is not really a 192 * bundle and therefore must be treated in a special way. 193 * @param fileName the name of the file in the system bundle's private area. 194 * @return a <tt>File</tt> object corresponding to the specified file name. 195 * @throws Exception if any error occurs. 196 **/ 197 public File getSystemBundleDataFile(String fileName) 198 throws Exception 199 { 200 // Make sure system bundle directory exists. 201 File sbDir = new File(determineCacheDir(m_configMap), BUNDLE_DIR_PREFIX + Long.toString(0)); 202 203 // If the system bundle directory exists, then we don't 204 // need to initialize since it has already been done. 205 if (!getSecureAction().fileExists(sbDir)) 206 { 207 // Create system bundle directory, if it does not exist. 208 if (!getSecureAction().mkdirs(sbDir)) 209 { 210 m_logger.log( 211 Logger.LOG_ERROR, 212 "Unable to create system bundle directory."); 213 throw new IOException("Unable to create system bundle directory."); 214 } 215 } 216 217 // Do some sanity checking. 218 if ((fileName.length() > 0) && (fileName.charAt(0) == File.separatorChar)) 219 throw new IllegalArgumentException("The data file path must be relative, not absolute."); 220 else if (fileName.indexOf("..") >= 0) 221 throw new IllegalArgumentException("The data file path cannot contain a reference to the \"..\" directory."); 222 223 // Return the data file. 224 return new File(sbDir, fileName); 225 } 226 227 // 228 // Static file-related utility methods. 229 // 230 231 /** 232 * This method copies an input stream to the specified file. 233 * @param is the input stream to copy. 234 * @param outputFile the file to which the input stream should be copied. 235 **/ 236 static void copyStreamToFile(InputStream is, File outputFile) 237 throws IOException 238 { 239 OutputStream os = null; 240 241 try 242 { 243 os = getSecureAction().getFileOutputStream(outputFile); 244 os = new BufferedOutputStream(os, BUFSIZE); 245 byte[] b = new byte[BUFSIZE]; 246 int len = 0; 247 while ((len = is.read(b)) != -1) 248 { 249 os.write(b, 0, len); 250 } 251 } 252 finally 253 { 254 if (is != null) is.close(); 255 if (os != null) os.close(); 256 } 257 } 258 259 static boolean deleteDirectoryTree(File target) 260 { 261 if (!deleteDirectoryTreeRecursive(target)) 262 { 263 // We might be talking windows and native libs -- hence, 264 // try to trigger a gc and try again. The hope is that 265 // this releases the classloader that loaded the native 266 // lib and allows us to delete it because it then 267 // would not be used anymore. 268 System.gc(); 269 System.gc(); 270 return deleteDirectoryTreeRecursive(target); 271 } 272 return true; 273 } 274 275 // 276 // Private methods. 277 // 278 279 private static File determineCacheDir(Map configMap) 280 { 281 File cacheDir; 282 283 // Check to see if the cache directory is specified in the storage 284 // configuration property. 285 String cacheDirStr = (String) configMap.get(Constants.FRAMEWORK_STORAGE); 286 // Get the cache root directory for relative paths; the default is ".". 287 String rootDirStr = (String) configMap.get(CACHE_ROOTDIR_PROP); 288 rootDirStr = (rootDirStr == null) ? CACHE_ROOTDIR_DEFAULT : rootDirStr; 289 if (cacheDirStr != null) 290 { 291 // If the specified cache directory is relative, then use the 292 // root directory to calculate the absolute path. 293 cacheDir = new File(cacheDirStr); 294 if (!cacheDir.isAbsolute()) 295 { 296 cacheDir = new File(rootDirStr, cacheDirStr); 297 } 298 } 299 else 300 { 301 // If no cache directory was specified, then use the default name 302 // in the root directory. 303 cacheDir = new File(rootDirStr, CACHE_DIR_NAME); 304 } 305 306 return cacheDir; 307 } 308 309 private static boolean deleteDirectoryTreeRecursive(File target) 310 { 311 if (!getSecureAction().fileExists(target)) 312 { 313 return true; 314 } 315 316 if (getSecureAction().isFileDirectory(target)) 317 { 318 File[] files = getSecureAction().listDirectory(target); 319 for (int i = 0; i < files.length; i++) 320 { 321 deleteDirectoryTreeRecursive(files[i]); 322 } 323 } 324 325 return getSecureAction().deleteFile(target); 326 } 327 }