Return-Path: X-Original-To: apmail-felix-commits-archive@www.apache.org Delivered-To: apmail-felix-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 0D6417733 for ; Thu, 25 Aug 2011 20:50:18 +0000 (UTC) Received: (qmail 92722 invoked by uid 500); 25 Aug 2011 20:50:18 -0000 Delivered-To: apmail-felix-commits-archive@felix.apache.org Received: (qmail 92624 invoked by uid 500); 25 Aug 2011 20:50:17 -0000 Mailing-List: contact commits-help@felix.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@felix.apache.org Delivered-To: mailing list commits@felix.apache.org Received: (qmail 92617 invoked by uid 99); 25 Aug 2011 20:50:17 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 25 Aug 2011 20:50:17 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 25 Aug 2011 20:50:13 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 31EA32388900 for ; Thu, 25 Aug 2011 20:49:53 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1161751 - in /felix/trunk/framework/src: main/java/org/apache/felix/framework/cache/ main/java/org/apache/felix/framework/util/ test/java/org/apache/felix/framework/util/ Date: Thu, 25 Aug 2011 20:49:52 -0000 To: commits@felix.apache.org From: rickhall@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20110825204953.31EA32388900@eris.apache.org> Author: rickhall Date: Thu Aug 25 20:49:52 2011 New Revision: 1161751 URL: http://svn.apache.org/viewvc?rev=1161751&view=rev Log: Enable limits on the number of open files by the bundle cache. (FELIX-3071) Added: felix/trunk/framework/src/main/java/org/apache/felix/framework/util/Mutex.java felix/trunk/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java felix/trunk/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java Removed: felix/trunk/framework/src/main/java/org/apache/felix/framework/util/ZipFileX.java Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java felix/trunk/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java?rev=1161751&r1=1161750&r2=1161751&view=diff ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java (original) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java Thu Aug 25 20:49:52 2011 @@ -24,6 +24,7 @@ import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import org.apache.felix.framework.Logger; +import org.apache.felix.framework.util.WeakZipFileFactory; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; @@ -77,6 +78,7 @@ public class BundleArchive private final Logger m_logger; private final Map m_configMap; + private final WeakZipFileFactory m_zipFactory; private final File m_archiveRootDir; private final boolean m_isSingleBundleFile; @@ -121,11 +123,13 @@ public class BundleArchive * @param is input stream from which to read the bundle content. * @throws Exception if any error occurs. **/ - public BundleArchive(Logger logger, Map configMap, File archiveRootDir, long id, - int startLevel, String location, InputStream is) throws Exception + public BundleArchive(Logger logger, Map configMap, WeakZipFileFactory zipFactory, + File archiveRootDir, long id, int startLevel, String location, InputStream is) + throws Exception { m_logger = logger; m_configMap = configMap; + m_zipFactory = zipFactory; m_archiveRootDir = archiveRootDir; m_id = id; if (m_id <= 0) @@ -161,11 +165,13 @@ public class BundleArchive * @param configMap configMap for BundleArchive * @throws Exception if any error occurs. **/ - public BundleArchive(Logger logger, Map configMap, File archiveRootDir) + public BundleArchive(Logger logger, Map configMap, WeakZipFileFactory zipFactory, + File archiveRootDir) throws Exception { m_logger = logger; m_configMap = configMap; + m_zipFactory = zipFactory; m_archiveRootDir = archiveRootDir; String s = (String) m_configMap.get(BundleCache.CACHE_SINGLEBUNDLEFILE_PROP); @@ -852,25 +858,25 @@ public class BundleArchive if (BundleCache.getSecureAction().isFileDirectory(file)) { result = new DirectoryRevision(m_logger, m_configMap, - revisionRootDir, location); + m_zipFactory, revisionRootDir, location); } else { - result = new JarRevision(m_logger, m_configMap, revisionRootDir, - location, true); + result = new JarRevision(m_logger, m_configMap, + m_zipFactory, revisionRootDir, location, true, null); } } else if (location.startsWith(INPUTSTREAM_PROTOCOL)) { // Assume all input streams point to JAR files. - result = new JarRevision(m_logger, m_configMap, revisionRootDir, - location, false, is); + result = new JarRevision(m_logger, m_configMap, + m_zipFactory, revisionRootDir, location, false, is); } else { // Anything else is assumed to be a URL to a JAR file. - result = new JarRevision(m_logger, m_configMap, revisionRootDir, - location, false); + result = new JarRevision(m_logger, m_configMap, + m_zipFactory, revisionRootDir, location, false, null); } } catch (Exception ex) Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java?rev=1161751&r1=1161750&r2=1161751&view=diff ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java (original) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java Thu Aug 25 20:49:52 2011 @@ -25,6 +25,7 @@ import java.util.*; import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.SecureAction; +import org.apache.felix.framework.util.WeakZipFileFactory; import org.osgi.framework.Constants; /** @@ -74,19 +75,22 @@ public class BundleCache public static final String CACHE_BUFSIZE_PROP = "felix.cache.bufsize"; public static final String CACHE_ROOTDIR_PROP = "felix.cache.rootdir"; public static final String CACHE_LOCKING_PROP = "felix.cache.locking"; + public static final String CACHE_FILELIMIT_PROP = "felix.cache.filelimit"; // TODO: CACHE - Remove this once we migrate the cache format. public static final String CACHE_SINGLEBUNDLEFILE_PROP = "felix.cache.singlebundlefile"; protected static transient int BUFSIZE = 4096; - protected static transient final String CACHE_DIR_NAME = "felix-cache"; - protected static transient final String CACHE_ROOTDIR_DEFAULT = "."; - protected static transient final String CACHE_LOCK_NAME = "cache.lock"; - protected static transient final String BUNDLE_DIR_PREFIX = "bundle"; + + private static transient final String CACHE_DIR_NAME = "felix-cache"; + private static transient final String CACHE_ROOTDIR_DEFAULT = "."; + private static transient final String CACHE_LOCK_NAME = "cache.lock"; + static transient final String BUNDLE_DIR_PREFIX = "bundle"; private static final SecureAction m_secureAction = new SecureAction(); private final Logger m_logger; private final Map m_configMap; + private final WeakZipFileFactory m_zipFactory; private final Object m_lock; public BundleCache(Logger logger, Map configMap) @@ -95,6 +99,19 @@ public class BundleCache m_logger = logger; m_configMap = configMap; + String limitStr = (String) m_configMap.get(CACHE_FILELIMIT_PROP); + limitStr = (limitStr == null) ? "10" : limitStr; + int limit; + try + { + limit = Integer.parseInt(limitStr); + } + catch (NumberFormatException ex) + { + limit = 10; + } + m_zipFactory = new WeakZipFileFactory(limit); + // Create the cache directory, if it does not exist. File cacheDir = determineCacheDir(m_configMap); if (!getSecureAction().fileExists(cacheDir)) @@ -220,7 +237,9 @@ public class BundleCache // Recreate the bundle archive. try { - archiveList.add(new BundleArchive(m_logger, m_configMap, children[i])); + archiveList.add( + new BundleArchive( + m_logger, m_configMap, m_zipFactory, children[i])); } catch (Exception ex) { @@ -250,7 +269,8 @@ public class BundleCache // Create the archive and add it to the list of archives. BundleArchive ba = new BundleArchive( - m_logger, m_configMap, archiveRootDir, id, startLevel, location, is); + m_logger, m_configMap, m_zipFactory, archiveRootDir, + id, startLevel, location, is); return ba; } catch (Exception ex) @@ -346,8 +366,8 @@ public class BundleCache // We might be talking windows and native libs -- hence, // try to trigger a gc and try again. The hope is that // this releases the classloader that loaded the native - // lib and allows us to delete it because it then - // would not be used anymore. + // lib and allows us to delete it because it then + // would not be used anymore. System.gc(); System.gc(); return deleteDirectoryTreeRecursive(target); Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java?rev=1161751&r1=1161750&r2=1161751&view=diff ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java (original) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java Thu Aug 25 20:49:52 2011 @@ -25,6 +25,7 @@ import java.util.*; import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.FelixConstants; import org.apache.felix.framework.util.Util; +import org.apache.felix.framework.util.WeakZipFileFactory; import org.osgi.framework.Constants; public class DirectoryContent implements Content @@ -35,16 +36,18 @@ public class DirectoryContent implements private final Logger m_logger; private final Map m_configMap; + private final WeakZipFileFactory m_zipFactory; private final Object m_revisionLock; private final File m_rootDir; private final File m_dir; private Map m_nativeLibMap; - public DirectoryContent(Logger logger, Map configMap, Object revisionLock, - File rootDir, File dir) + public DirectoryContent(Logger logger, Map configMap, + WeakZipFileFactory zipFactory, Object revisionLock, File rootDir, File dir) { m_logger = logger; m_configMap = configMap; + m_zipFactory = zipFactory; m_revisionLock = revisionLock; m_rootDir = rootDir; m_dir = dir; @@ -163,7 +166,7 @@ public class DirectoryContent implements if (entryName.equals(FelixConstants.CLASS_PATH_DOT)) { return new DirectoryContent( - m_logger, m_configMap, m_revisionLock, m_rootDir, m_dir); + m_logger, m_configMap, m_zipFactory, m_revisionLock, m_rootDir, m_dir); } // Remove any leading slash, since all bundle class path @@ -180,7 +183,7 @@ public class DirectoryContent implements if (BundleCache.getSecureAction().isFileDirectory(file)) { return new DirectoryContent( - m_logger, m_configMap, m_revisionLock, m_rootDir, file); + m_logger, m_configMap, m_zipFactory, m_revisionLock, m_rootDir, file); } else if (BundleCache.getSecureAction().fileExists(file) && entryName.endsWith(".jar")) @@ -202,7 +205,8 @@ public class DirectoryContent implements } } return new JarContent( - m_logger, m_configMap, m_revisionLock, extractDir, file, null); + m_logger, m_configMap, m_zipFactory, m_revisionLock, + extractDir, file, null); } // The entry could not be found, so return null. Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java?rev=1161751&r1=1161750&r2=1161751&view=diff ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java (original) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java Thu Aug 25 20:49:52 2011 @@ -26,6 +26,7 @@ import java.util.jar.Manifest; import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.StringMap; +import org.apache.felix.framework.util.WeakZipFileFactory; /** *

@@ -36,12 +37,15 @@ import org.apache.felix.framework.util.S **/ class DirectoryRevision extends BundleArchiveRevision { + private final WeakZipFileFactory m_zipFactory; private final File m_refDir; public DirectoryRevision( - Logger logger, Map configMap, File revisionRootDir, String location) throws Exception + Logger logger, Map configMap, WeakZipFileFactory zipFactory, + File revisionRootDir, String location) throws Exception { super(logger, configMap, revisionRootDir, location); + m_zipFactory = zipFactory; m_refDir = new File(location.substring( location.indexOf(BundleArchive.FILE_PROTOCOL) + BundleArchive.FILE_PROTOCOL.length())); @@ -95,7 +99,8 @@ class DirectoryRevision extends BundleAr public synchronized Content getContent() throws Exception { - return new DirectoryContent(getLogger(), getConfig(), this, getRevisionRootDir(), m_refDir); + return new DirectoryContent(getLogger(), getConfig(), m_zipFactory, + this, getRevisionRootDir(), m_refDir); } protected void close() throws Exception Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java?rev=1161751&r1=1161750&r2=1161751&view=diff ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java (original) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java Thu Aug 25 20:49:52 2011 @@ -32,8 +32,9 @@ import java.util.Properties; import java.util.zip.ZipEntry; import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.FelixConstants; -import org.apache.felix.framework.util.ZipFileX; import org.apache.felix.framework.util.Util; +import org.apache.felix.framework.util.WeakZipFileFactory; +import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile; import org.osgi.framework.Constants; public class JarContent implements Content @@ -44,22 +45,39 @@ public class JarContent implements Conte private final Logger m_logger; private final Map m_configMap; + private final WeakZipFileFactory m_zipFactory; private final Object m_revisionLock; private final File m_rootDir; private final File m_file; - private final ZipFileX m_zipFile; + private final WeakZipFile m_zipFile; private final boolean m_isZipFileOwner; private Map m_nativeLibMap; - public JarContent(Logger logger, Map configMap, Object revisionLock, File rootDir, - File file, ZipFileX zipFile) + public JarContent(Logger logger, Map configMap, WeakZipFileFactory zipFactory, + Object revisionLock, File rootDir, File file, WeakZipFile zipFile) { m_logger = logger; m_configMap = configMap; + m_zipFactory = zipFactory; m_revisionLock = revisionLock; m_rootDir = rootDir; m_file = file; - m_zipFile = (zipFile == null) ? openZipFile(m_file) : zipFile; + if (zipFile == null) + { + try + { + m_zipFile = m_zipFactory.create(m_file); + } + catch (IOException ex) + { + throw new RuntimeException( + "Unable to open JAR file, probably deleted: " + ex.getMessage()); + } + } + else + { + m_zipFile = zipFile; + } m_isZipFileOwner = (zipFile == null); } @@ -209,7 +227,7 @@ public class JarContent implements Conte // just return it immediately. if (entryName.equals(FelixConstants.CLASS_PATH_DOT)) { - return new JarContent(m_logger, m_configMap, m_revisionLock, + return new JarContent(m_logger, m_configMap, m_zipFactory, m_revisionLock, m_rootDir, m_file, m_zipFile); } @@ -272,7 +290,7 @@ public class JarContent implements Conte } } return new JarContent( - m_logger, m_configMap, m_revisionLock, + m_logger, m_configMap, m_zipFactory, m_revisionLock, extractJar.getParentFile(), extractJar, null); } @@ -477,19 +495,6 @@ public class JarContent implements Conte } } - private static ZipFileX openZipFile(File file) throws RuntimeException - { - try - { - return BundleCache.getSecureAction().openZipFile(file); - } - catch (IOException ex) - { - throw new RuntimeException( - "Unable to open JAR file, probably deleted: " + ex.getMessage()); - } - } - private static class EntriesEnumeration implements Enumeration { private final Enumeration m_enumeration; Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java?rev=1161751&r1=1161750&r2=1161751&view=diff ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java (original) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java Thu Aug 25 20:49:52 2011 @@ -29,9 +29,10 @@ import java.util.Map; import java.util.zip.ZipEntry; import org.apache.felix.framework.Logger; -import org.apache.felix.framework.util.ZipFileX; import org.apache.felix.framework.util.StringMap; import org.apache.felix.framework.util.Util; +import org.apache.felix.framework.util.WeakZipFileFactory; +import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile; /** *

@@ -48,24 +49,19 @@ class JarRevision extends BundleArchiveR { private static final transient String BUNDLE_JAR_FILE = "bundle.jar"; - private File m_bundleFile = null; - private final ZipFileX m_zipFile; + private final WeakZipFileFactory m_zipFactory; + private final File m_bundleFile; + private final WeakZipFile m_zipFile; public JarRevision( - Logger logger, Map configMap, File revisionRootDir, - String location, boolean byReference) - throws Exception - { - this(logger, configMap, revisionRootDir, location, byReference, null); - } - - public JarRevision( - Logger logger, Map configMap, File revisionRootDir, String location, - boolean byReference, InputStream is) + Logger logger, Map configMap, WeakZipFileFactory zipFactory, + File revisionRootDir, String location, boolean byReference, InputStream is) throws Exception { super(logger, configMap, revisionRootDir, location); + m_zipFactory = zipFactory; + if (byReference) { m_bundleFile = new File(location.substring( @@ -81,11 +77,11 @@ class JarRevision extends BundleArchiveR initialize(byReference, is); // Open shared copy of the JAR file. - ZipFileX zipFile = null; + WeakZipFile zipFile = null; try { // Open bundle JAR file. - zipFile = BundleCache.getSecureAction().openZipFile(m_bundleFile); + zipFile = m_zipFactory.create(m_bundleFile); // Error if no jar file. if (zipFile == null) { @@ -111,8 +107,8 @@ class JarRevision extends BundleArchiveR public synchronized Content getContent() throws Exception { - return new JarContent(getLogger(), getConfig(), this, getRevisionRootDir(), - m_bundleFile, m_zipFile); + return new JarContent(getLogger(), getConfig(), m_zipFactory, + this, getRevisionRootDir(), m_bundleFile, m_zipFile); } protected void close() throws Exception @@ -198,7 +194,7 @@ class JarRevision extends BundleArchiveR // The idea is to not open the jar file as a java.util.jarfile but // read the mainfest from the zipfile directly and parse it manually // to use less memory and be faster. - private static void getMainAttributes(Map result, ZipFileX zipFile) throws Exception + private static void getMainAttributes(Map result, WeakZipFile zipFile) throws Exception { ZipEntry entry = zipFile.getEntry("META-INF/MANIFEST.MF"); Added: felix/trunk/framework/src/main/java/org/apache/felix/framework/util/Mutex.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/util/Mutex.java?rev=1161751&view=auto ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/util/Mutex.java (added) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/util/Mutex.java Thu Aug 25 20:49:52 2011 @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.felix.framework.util; + +public class Mutex +{ + private int m_count; + + public Mutex() + { + m_count = 1; + } + + public synchronized void down() throws InterruptedException + { + while (m_count <= 0) + { + wait(); + } + m_count = 0; + } + + public synchronized void up() + { + m_count = 1; + notifyAll(); + } +} \ No newline at end of file Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java?rev=1161751&r1=1161750&r2=1161751&view=diff ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java (original) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java Thu Aug 25 20:49:52 2011 @@ -26,6 +26,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; +import java.util.zip.ZipFile; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; @@ -594,7 +595,7 @@ public class SecureAction } } - public ZipFileX openZipFile(File file) throws IOException + public ZipFile openZipFile(File file) throws IOException { if (System.getSecurityManager() != null) { @@ -602,7 +603,7 @@ public class SecureAction { Actions actions = (Actions) m_actions.get(); actions.set(Actions.OPEN_ZIPFILE_ACTION, file); - return (ZipFileX) AccessController.doPrivileged(actions, m_acc); + return (ZipFile) AccessController.doPrivileged(actions, m_acc); } catch (PrivilegedActionException ex) { @@ -615,7 +616,7 @@ public class SecureAction } else { - return new ZipFileX(file); + return new ZipFile(file); } } @@ -1587,7 +1588,7 @@ public class SecureAction case MAKE_DIRECTORY_ACTION: return ((File) arg1).mkdir() ? Boolean.TRUE : Boolean.FALSE; case OPEN_ZIPFILE_ACTION: - return new ZipFileX((File) arg1); + return new ZipFile((File) arg1); case OPEN_URLCONNECTION_ACTION: return ((URL) arg1).openConnection(); case RENAME_FILE_ACTION: Added: felix/trunk/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java?rev=1161751&view=auto ============================================================================== --- felix/trunk/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java (added) +++ felix/trunk/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java Thu Aug 25 20:49:52 2011 @@ -0,0 +1,746 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.felix.framework.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * This class implements a factory for creating weak zip files, which behave + * mostly like a ZipFile, but can be weakly closed to limit the number of + * open files. + */ +public class WeakZipFileFactory +{ + private static final int WEAKLY_CLOSED = 0; + private static final int OPEN = 1; + private static final int CLOSED = 2; + + private static final SecureAction m_secureAction = new SecureAction(); + + private final List m_zipFiles = new ArrayList(); + private final List m_openFiles = new ArrayList(); + private final Mutex m_globalMutex = new Mutex(); + private final int m_limit; + + /** + * Constructs a weak zip file factory with the specified file limit. A limit + * of zero signified no limit. + * @param limit maximum number of open zip files at any given time. + */ + public WeakZipFileFactory(int limit) + { + if (limit < 0) + { + throw new IllegalArgumentException("Limit must be non-negative."); + } + m_limit = limit; + } + + /** + * Factory method used to create weak zip file. + * @param file the target zip file. + * @return the created weak zip file. + * @throws IOException if the zip file could not be opened. + */ + public WeakZipFile create(File file) throws IOException + { + WeakZipFile wzf = new WeakZipFile(file); + + if (m_limit > 0) + { + try + { + m_globalMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while acquiring global zip file mutex."); + } + + try + { + m_zipFiles.add(wzf); + m_openFiles.add(wzf); + if (m_openFiles.size() > m_limit) + { + WeakZipFile candidate = m_openFiles.get(0); + for (WeakZipFile tmp : m_openFiles) + { + if (candidate.m_timestamp > tmp.m_timestamp) + { + candidate = tmp; + } + } + candidate._closeWeakly(); + } + } + finally + { + m_globalMutex.up(); + } + } + + return wzf; + } + + /** + * Only used for testing. + * @return unclosed weak zip files. + **/ + List getZipZiles() + { + try + { + m_globalMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + return Collections.EMPTY_LIST; + } + + try + { + return m_zipFiles; + } + finally + { + m_globalMutex.up(); + } + } + + /** + * Only used for testing. + * @return open weak zip files. + **/ + List getOpenZipZiles() + { + try + { + m_globalMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + return Collections.EMPTY_LIST; + } + + try + { + return m_openFiles; + } + finally + { + m_globalMutex.up(); + } + } + + /** + * This class wraps a ZipFile to making it possible to weakly close it; + * this means the underlying zip file will be automatically reopened on demand + * if anyone tries to use it. + */ + public class WeakZipFile + { + private final File m_file; + private final Mutex m_localMutex = new Mutex(); + private ZipFile m_zipFile; + private int m_status = OPEN; + private long m_timestamp; + + /** + * Constructor is private since instances need to be centrally + * managed. + * @param file the target zip file. + * @throws IOException if the zip file could not be opened. + */ + private WeakZipFile(File file) throws IOException + { + m_file = file; + m_zipFile = m_secureAction.openZipFile(m_file); + m_timestamp = System.currentTimeMillis(); + } + + /** + * Returns the specified entry from the zip file. + * @param name the name of the entry to return. + * @return the zip entry associated with the specified name or null + * if it does not exist. + */ + public ZipEntry getEntry(String name) + { + ensureZipFileIsOpen(); + + try + { + ZipEntry ze = null; + ze = m_zipFile.getEntry(name); + if ((ze != null) && (ze.getSize() == 0) && !ze.isDirectory()) + { + //The attempts to fix an apparent bug in the JVM in versions + // 1.4.2 and lower where directory entries in ZIP/JAR files are + // not correctly identified. + ZipEntry dirEntry = m_zipFile.getEntry(name + '/'); + if (dirEntry != null) + { + ze = dirEntry; + } + } + return ze; + } + finally + { + m_localMutex.up(); + } + } + + /** + * Returns an enumeration of zip entries from the zip file. + * @return an enumeration of zip entries. + */ + public Enumeration entries() + { + ensureZipFileIsOpen(); + + try + { + // We need to suck in all of the entries since the zip + // file may get weakly closed during iteration. Technically, + // this may not be 100% correct either since if the zip file + // gets weakly closed and reopened, then the zip entries + // will be from a different zip file. It is not clear if this + // will cause any issues. + Enumeration e = m_zipFile.entries(); + List entries = new ArrayList(); + while (e.hasMoreElements()) + { + entries.add(e.nextElement()); + } + return Collections.enumeration(entries); + } + finally + { + m_localMutex.up(); + } + } + + /** + * Returns an input stream for the specified zip entry. + * @param ze the zip entry whose input stream is to be retrieved. + * @return an input stream to the zip entry. + * @throws IOException if the input stream cannot be opened. + */ + public InputStream getInputStream(ZipEntry ze) throws IOException + { + ensureZipFileIsOpen(); + + try + { + InputStream is = m_zipFile.getInputStream(ze); + return new WeakZipInputStream(ze.getName(), is); + } + finally + { + m_localMutex.up(); + } + } + + /** + * Weakly closes the zip file, which means that it will be reopened + * if anyone tries to use it again. + */ + void closeWeakly() + { + try + { + m_globalMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + throw new IllegalStateException( + "Interrupted while acquiring global zip file mutex."); + } + + try + { + _closeWeakly(); + } + finally + { + m_globalMutex.up(); + } + } + + /** + * This method is used internally to weakly close a zip file. It should + * only be called when already holding the global lock, otherwise use + * closeWeakly(). + */ + private void _closeWeakly() + { + try + { + m_localMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + throw new IllegalStateException( + "Interrupted while acquiring local zip file mutex."); + } + + try + { + if (m_status == OPEN) + { + try + { + m_status = WEAKLY_CLOSED; + if (m_zipFile != null) + { + m_zipFile.close(); + m_zipFile = null; + } + m_openFiles.remove(this); + } + catch (IOException ex) + { + __close(); + } + } + } + finally + { + m_localMutex.up(); + } + } + + /** + * This method permanently closes the zip file. + * @throws IOException if any error occurs while trying to close the + * zip file. + */ + public void close() throws IOException + { + if (m_limit > 0) + { + try + { + m_globalMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + throw new IllegalStateException( + "Interrupted while acquiring global zip file mutex."); + } + try + { + m_localMutex.down(); + } + catch (InterruptedException ex) + { + m_globalMutex.up(); + Thread.currentThread().interrupt(); + throw new IllegalStateException( + "Interrupted while acquiring local zip file mutex."); + } + } + + try + { + ZipFile tmp = m_zipFile; + __close(); + if (tmp != null) + { + tmp.close(); + } + } + finally + { + m_localMutex.up(); + m_globalMutex.up(); + } + } + + /** + * This internal method is used to clear the zip file from the data + * structures and reset its state. It should only be called when + * holding the global and local mutexes. + */ + private void __close() + { + m_status = CLOSED; + m_zipFile = null; + m_zipFiles.remove(this); + m_openFiles.remove(this); + } + + /** + * This method ensures that the zip file associated with this + * weak zip file instance is actually open and acquires the + * local weak zip file mutex. If the underlying zip file is closed, + * then the local mutex is released and an IllegalStateException is + * thrown. If the zip file is weakly closed, then it is reopened. + * If the zip file is already opened, then no additional action is + * necessary. If this method does not throw an exception, then + * the end result is the zip file member field is non-null and the + * local mutex has been acquired. + */ + private void ensureZipFileIsOpen() + { + if (m_limit == 0) + { + return; + } + + // Get mutex for zip file. + try + { + m_localMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + throw new IllegalStateException( + "Interrupted while acquiring local zip file mutex."); + } + + // If zip file is closed, then just return null. + if (m_status == CLOSED) + { + m_localMutex.up(); + throw new IllegalStateException("Zip file is closed: " + m_file); + } + + // If zip file is weakly closed, we need to reopen it, + // but we have to release the zip mutex to acquire the + // global mutex, then reacquire the zip mutex. This + // ensures that the global mutex is always acquired + // before any local mutex to avoid deadlocks. + IOException cause = null; + if (m_status == WEAKLY_CLOSED) + { + m_localMutex.up(); + + try + { + m_globalMutex.down(); + } + catch (InterruptedException ex) + { + Thread.currentThread().interrupt(); + throw new IllegalStateException( + "Interrupted while acquiring global zip file mutex."); + } + try + { + m_localMutex.down(); + } + catch (InterruptedException ex) + { + m_globalMutex.up(); + Thread.currentThread().interrupt(); + throw new IllegalStateException( + "Interrupted while acquiring local zip file mutex."); + } + + // Double check status since it may have changed. + if (m_status == CLOSED) + { + m_localMutex.up(); + m_globalMutex.up(); + throw new IllegalStateException("Zip file is closed: " + m_file); + } + else if (m_status == WEAKLY_CLOSED) + { + try + { + __reopenZipFile(); + } + catch (IOException ex) + { + cause = ex; + } + } + + // Release the global mutex, since it should no longer be necessary. + m_globalMutex.up(); + } + + // It is possible that reopening the zip file failed, so we check + // for that case and throw an exception. + if (m_zipFile == null) + { + m_localMutex.up(); + IllegalStateException ise = + new IllegalStateException("Zip file is closed: " + m_file); + if (cause != null) + { + ise.initCause(cause); + } + throw ise; + } + } + + /** + * Thie internal method is used to reopen a weakly closed zip file. + * It makes a best effort, but may fail and leave the zip file member + * field null. Any failure reopening a zip file results in it being + * permanently closed. This method should only be invoked when holding + * the global and local mutexes. + */ + private void __reopenZipFile() throws IOException + { + if (m_status == WEAKLY_CLOSED) + { + try + { + m_zipFile = m_secureAction.openZipFile(m_file); + m_status = OPEN; + m_timestamp = System.currentTimeMillis(); + } + catch (IOException ex) + { + __close(); + throw ex; + } + + if (m_zipFile != null) + { + m_openFiles.add(this); + if (m_openFiles.size() > m_limit) + { + WeakZipFile candidate = m_openFiles.get(0); + for (WeakZipFile tmp : m_openFiles) + { + if (candidate.m_timestamp > tmp.m_timestamp) + { + candidate = tmp; + } + } + candidate._closeWeakly(); + } + } + } + } + + /** + * This is an InputStream wrapper that will properly reopen the underlying + * zip file if it is weakly closed and create the underlying input stream. + */ + class WeakZipInputStream extends InputStream + { + private final String m_entryName; + private InputStream m_is; + private int m_currentPos = 0; + private ZipFile m_zipFileSnapshot; + + WeakZipInputStream(String entryName, InputStream is) + { + m_entryName = entryName; + m_is = is; + m_zipFileSnapshot = m_zipFile; + } + + /** + * This internal method ensures that the zip file is open and that + * the underlying input stream is valid. Upon successful completion, + * the underlying input stream will be valid and the local mutex + * will be held. + * @throws IOException if the was an error handling the input stream. + */ + private void ensureInputStreamIsValid() throws IOException + { + if (m_limit == 0) + { + return; + } + + ensureZipFileIsOpen(); + + // If the underlying zip file changed, then we need + // to get the input stream again. + if (m_zipFileSnapshot != m_zipFile) + { + m_zipFileSnapshot = m_zipFile; + + if (m_is != null) + { + try + { + m_is.close(); + } + catch (Exception ex) + { + // Not much we can do. + } + } + try + { + m_is = m_zipFile.getInputStream(m_zipFile.getEntry(m_entryName)); + m_is.skip(m_currentPos); + } + catch (IOException ex) + { + m_localMutex.up(); + throw ex; + } + } + } + + @Override + public int available() throws IOException + { + ensureInputStreamIsValid(); + try + { + return m_is.available(); + } + finally + { + m_localMutex.up(); + } + } + + @Override + public void close() throws IOException + { + ensureInputStreamIsValid(); + try + { + InputStream is = m_is; + m_is = null; + if (is != null) + { + is.close(); + } + } + finally + { + m_localMutex.up(); + } + } + + @Override + public void mark(int i) + { + // Not supported. + } + + @Override + public boolean markSupported() + { + // Not supported. + return false; + } + + public int read() throws IOException + { + ensureInputStreamIsValid(); + try + { + int len = m_is.read(); + if (len > 0) + { + m_currentPos++; + } + return len; + } + finally + { + m_localMutex.up(); + } + } + + @Override + public int read(byte[] bytes) throws IOException + { + ensureInputStreamIsValid(); + try + { + int len = m_is.read(bytes); + if (len > 0) + { + m_currentPos += len; + } + return len; + } + finally + { + m_localMutex.up(); + } + } + + @Override + public int read(byte[] bytes, int i, int i1) throws IOException + { + ensureInputStreamIsValid(); + try + { + int len = m_is.read(bytes, i, i1); + if (len > 0) + { + m_currentPos += len; + } + return len; + } + finally + { + m_localMutex.up(); + } + } + + @Override + public void reset() throws IOException + { + throw new IOException("Unsupported operation"); + } + + @Override + public long skip(long l) throws IOException + { + ensureInputStreamIsValid(); + try + { + long len = m_is.skip(l); + if (len > 0) + { + m_currentPos += len; + } + return len; + } + finally + { + m_localMutex.up(); + } + } + } + } +} \ No newline at end of file Added: felix/trunk/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java?rev=1161751&view=auto ============================================================================== --- felix/trunk/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java (added) +++ felix/trunk/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java Thu Aug 25 20:49:52 2011 @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.felix.framework.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import junit.framework.TestCase; +import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile; + +public class WeakZipFileTest extends TestCase +{ + private static final String ENTRY_NAME = "entry.txt"; + + public void testWeakClose() + { + // Create a reasonably big random string. + byte[] contentBytes = new byte[16384]; + for (int i = 0; i < contentBytes.length; i++) + { + contentBytes[i] = (byte) ((i % 65) + 65); + } + String contentString = new String(contentBytes); + + // Create a temporary zip file. + File tmpZip = null; + try + { + tmpZip = File.createTempFile("felix.test", ".zip"); + tmpZip.deleteOnExit(); + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip)); + ZipEntry ze = new ZipEntry(ENTRY_NAME); + zos.putNextEntry(ze); + zos.write(contentBytes, 0, contentBytes.length); + zos.close(); + } + catch (IOException ex) + { + fail("Unable to create temporary zip file: " + ex); + } + + try + { + WeakZipFileFactory factory = new WeakZipFileFactory(1); + WeakZipFile zipFile = factory.create(tmpZip); + assertTrue("Zip file not recorded.", + factory.getZipZiles().contains(zipFile)); + assertTrue("Open zip file not recorded.", + factory.getOpenZipZiles().contains(zipFile)); + ZipEntry ze = zipFile.getEntry(ENTRY_NAME); + assertNotNull("Zip entry not found", ze); + byte[] firstHalf = new byte[contentBytes.length / 2]; + byte[] secondHalf = new byte[contentBytes.length - firstHalf.length]; + InputStream is = zipFile.getInputStream(ze); + is.read(firstHalf); + zipFile.closeWeakly(); + assertTrue("Zip file not recorded.", + factory.getZipZiles().contains(zipFile)); + assertFalse("Open zip file still recorded.", + factory.getOpenZipZiles().contains(zipFile)); + is.read(secondHalf); + assertTrue("Zip file not recorded.", + factory.getZipZiles().contains(zipFile)); + assertTrue("Open zip file not recorded.", + factory.getOpenZipZiles().contains(zipFile)); + byte[] complete = new byte[firstHalf.length + secondHalf.length]; + System.arraycopy(firstHalf, 0, complete, 0, firstHalf.length); + System.arraycopy(secondHalf, 0, complete, firstHalf.length, secondHalf.length); + String completeString = new String(complete); + assertEquals(contentString, completeString); + zipFile.close(); + } + catch (IOException ex) + { + fail("Unable to read zip file entry: " + ex); + } + } +} \ No newline at end of file