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 }