Enable limits on the number of open files by the bundle cache. (FELIX-3071)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1161751 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java b/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
index a09b96d..7829e42 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
@@ -24,6 +24,7 @@
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 @@
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 @@
* @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 @@
* @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 @@
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)
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java b/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
index 9505425..ff79158 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
@@ -25,6 +25,7 @@
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 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 @@
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 @@
// 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 @@
// 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 @@
// 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);
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java
index 8e2af9c..b6b5b54 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java
@@ -25,6 +25,7 @@
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 @@
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 @@
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 @@
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 @@
}
}
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.
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
index efaf855..c1269c0 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
@@ -26,6 +26,7 @@
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.StringMap;
+import org.apache.felix.framework.util.WeakZipFileFactory;
/**
* <p>
@@ -36,12 +37,15 @@
**/
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 @@
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
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java b/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
index f8d0bd5..409f12e 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
@@ -32,8 +32,9 @@
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 @@
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 @@
// 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 @@
}
}
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 @@
}
}
- 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<String>
{
private final Enumeration m_enumeration;
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
index b953767..7a539da 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
@@ -29,9 +29,10 @@
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;
/**
* <p>
@@ -48,24 +49,19 @@
{
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 @@
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 @@
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 @@
// 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");
diff --git a/framework/src/main/java/org/apache/felix/framework/util/Mutex.java b/framework/src/main/java/org/apache/felix/framework/util/Mutex.java
new file mode 100644
index 0000000..fdec057
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/util/Mutex.java
@@ -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
diff --git a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
index ead1ad9..50d3a13 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
@@ -26,6 +26,7 @@
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 ZipFileX openZipFile(File file) throws IOException
+ public ZipFile openZipFile(File file) throws IOException
{
if (System.getSecurityManager() != null)
{
@@ -602,7 +603,7 @@
{
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 @@
}
else
{
- return new ZipFileX(file);
+ return new ZipFile(file);
}
}
@@ -1587,7 +1588,7 @@
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:
diff --git a/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java b/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java
new file mode 100644
index 0000000..b8dc046
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/util/WeakZipFileFactory.java
@@ -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<WeakZipFile> m_zipFiles = new ArrayList<WeakZipFile>();
+ private final List<WeakZipFile> m_openFiles = new ArrayList<WeakZipFile>();
+ 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<WeakZipFile> 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<WeakZipFile> 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<ZipEntry> 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<? extends ZipEntry> e = m_zipFile.entries();
+ List<ZipEntry> entries = new ArrayList<ZipEntry>();
+ 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
diff --git a/framework/src/main/java/org/apache/felix/framework/util/ZipFileX.java b/framework/src/main/java/org/apache/felix/framework/util/ZipFileX.java
deleted file mode 100644
index 550a680..0000000
--- a/framework/src/main/java/org/apache/felix/framework/util/ZipFileX.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.util.jar.JarEntry;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * The purpose of this class is 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.
-**/
-public class ZipFileX extends ZipFile
-{
- public ZipFileX(File file) throws IOException
- {
- super(file);
- }
-
-
- public ZipFileX(File file, int mode) throws IOException
- {
- super(file, mode);
- }
-
- public ZipFileX(String name) throws IOException
- {
- super(name);
- }
-
- public ZipEntry getEntry(String name)
- {
- ZipEntry entry = super.getEntry(name);
- if ((entry != null) && (entry.getSize() == 0) && !entry.isDirectory())
- {
- ZipEntry dirEntry = super.getEntry(name + '/');
- if (dirEntry != null)
- {
- entry = dirEntry;
- }
- }
- return entry;
- }
-}
\ No newline at end of file
diff --git a/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java b/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java
new file mode 100644
index 0000000..1847056
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/util/WeakZipFileTest.java
@@ -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