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    }