Implemented initial support for embedded directories on the Bundle-ClassPath.
In the process uncovered an apparent bug in the JVM where ZIP entries
that correspond to directories are not correctly being identified as
directories.
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@375617 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java
index a4d895c..7b93491 100644
--- a/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java
+++ b/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java
@@ -19,13 +19,11 @@
import java.io.*;
import java.security.*;
import java.util.*;
-import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.*;
-import org.apache.felix.framework.util.Util;
import org.apache.felix.moduleloader.*;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
@@ -694,28 +692,17 @@
m_dir, REVISION_DIRECTORY + getRefreshCount() + "." + revision);
// Get the embedded resource.
- JarFile jarFile = null;
+ JarFileX jarFile = null;
try
{
- // Create JarFile object using privileged block.
- if (System.getSecurityManager() != null)
- {
- jarFile = (JarFile) AccessController.doPrivileged(
- new PrivilegedAction(
- PrivilegedAction.OPEN_BUNDLE_JAR_ACTION, this, revisionDir));
- }
- else
- {
- jarFile = openBundleJarUnchecked(revisionDir);
- }
-
+ // Open bundle JAR file.
+ jarFile = openBundleJar(revisionDir);
// Error if no jar file.
if (jarFile == null)
{
throw new IOException("No JAR file found.");
}
-
// Get manifest.
Manifest mf = jarFile.getManifest();
// Create a case insensitive map of manifest attributes.
@@ -728,20 +715,40 @@
}
return map;
- } catch (PrivilegedActionException ex) {
- throw ((PrivilegedActionException) ex).getException();
- } finally {
+ }
+ finally
+ {
if (jarFile != null) jarFile.close();
}
}
- private JarFile openBundleJarUnchecked(File revisionDir)
+ private JarFileX openBundleJarUnchecked(File revisionDir)
throws Exception
{
// Get bundle jar file.
File bundleJar = new File(revisionDir, BUNDLE_JAR_FILE);
// Get bundle jar file.
- return new JarFile(bundleJar);
+ return new JarFileX(bundleJar);
+ }
+
+ private JarFileX openBundleJar(File revisionDir) throws Exception
+ {
+ // Create JarFile object using privileged block.
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ return (JarFileX) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.OPEN_BUNDLE_JAR_ACTION, this, revisionDir));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+
+ return openBundleJarUnchecked(revisionDir);
}
private IContent getContentUnchecked(int revision)
@@ -823,28 +830,47 @@
}
// Create the bundles class path.
- IContent self = new JarContent(new File(revisionDir, BUNDLE_JAR_FILE));
- IContent[] classPath = new IContent[classPathStrings.length];
- for (int i = 0; i < classPathStrings.length; i++)
+ JarFileX bundleJar = null;
+ try
{
- if (classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT))
+ bundleJar = openBundleJar(revisionDir);
+ IContent self = new JarContent(new File(revisionDir, BUNDLE_JAR_FILE));
+ IContent[] classPath = new IContent[classPathStrings.length];
+ for (int i = 0; i < classPathStrings.length; i++)
{
- classPath[i] = self;
+ if (classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT))
+ {
+ classPath[i] = self;
+ }
+ else
+ {
+ // Determine if the class path entry is a file or directory
+ // in the bundle JAR file.
+ ZipEntry entry = bundleJar.getEntry(classPathStrings[i]);
+ if ((entry != null) && entry.isDirectory())
+ {
+ classPath[i] = new ContentDirectoryContent(self, classPathStrings[i]);
+ }
+ else
+ {
+ classPath[i] = new JarContent(new File(embedDir, classPathStrings[i]));
+ }
+ }
}
- else
+
+ // If there is nothing on the class path, then include
+ // "." by default, as per the spec.
+ if (classPath.length == 0)
{
- classPath[i] = new JarContent(new File(embedDir, classPathStrings[i]));
+ classPath = new IContent[] { self };
}
- }
- // If there is nothing on the class path, then include
- // "." by default, as per the spec.
- if (classPath.length == 0)
+ return classPath;
+ }
+ finally
{
- classPath = new IContent[] { self };
+ if (bundleJar != null) bundleJar.close();
}
-
- return classPath;
}
public IContent[] getContentPath(int revision)
@@ -906,7 +932,7 @@
// already exist.
if (!libFile.exists())
{
- JarFile jarFile = null;
+ JarFileX jarFile = null;
InputStream is = null;
try
@@ -1077,7 +1103,9 @@
byte[] b = new byte[DefaultBundleCache.BUFSIZE];
int len = 0;
while ((len = is.read(b)) != -1)
+ {
os.write(b, 0, len);
+ }
}
finally
{
@@ -1132,17 +1160,7 @@
}
// Find class path meta-data.
- String classPath = null;
- Iterator iter = map.entrySet().iterator();
- while ((classPath == null) && iter.hasNext())
- {
- Map.Entry entry = (Map.Entry) iter.next();
- if (entry.getKey().toString().toLowerCase().equals(
- FelixConstants.BUNDLE_CLASSPATH.toLowerCase()))
- {
- classPath = entry.getValue().toString();
- }
- }
+ String classPath = map.get(FelixConstants.BUNDLE_CLASSPATH).toString();
// Parse the class path into strings.
String[] classPathStrings = Util.parseDelimitedString(
@@ -1183,45 +1201,48 @@
// Remove leading slash if present.
jarPath = (jarPath.charAt(0) == '/') ? jarPath.substring(1) : jarPath;
- // If JAR is already extracted, then don't
- // re-extract it...
+ // If JAR is already extracted, then don't re-extract it...
File jarFile = new File(
revisionDir, EMBEDDED_DIRECTORY + File.separatorChar + jarPath);
if (!jarFile.exists())
{
- // Make sure that the embedded JAR's parent directory exists;
- // it may be in a sub-directory.
- File jarDir = jarFile.getParentFile();
- if (!jarDir.exists())
- {
- if (!jarDir.mkdirs())
- {
- throw new IOException("Unable to create embedded JAR directory.");
- }
- }
-
- // Extract embedded JAR into its directory.
- JarFile bundleJar = null;
+ JarFileX bundleJar = null;
InputStream is = null;
-
try
{
+ // Make sure class path entry is a JAR file.
bundleJar = openBundleJarUnchecked(revisionDir);
ZipEntry ze = bundleJar.getEntry(jarPath);
if (ze == null)
{
throw new IOException("No JAR entry: " + jarPath);
}
- is = new BufferedInputStream(bundleJar.getInputStream(ze), DefaultBundleCache.BUFSIZE);
- if (is == null)
+ // If the zip entry is a directory, then ignore it since
+ // we don't need to extact it; otherwise, it points to an
+ // embedded JAR file, so extract it.
+ else if (!ze.isDirectory())
{
- throw new IOException("No input stream: " + jarPath);
+ // Make sure that the embedded JAR's parent directory exists;
+ // it may be in a sub-directory.
+ File jarDir = jarFile.getParentFile();
+ if (!jarDir.exists())
+ {
+ if (!jarDir.mkdirs())
+ {
+ throw new IOException("Unable to create embedded JAR directory.");
+ }
+ }
+
+ // Extract embedded JAR into its directory.
+ is = new BufferedInputStream(bundleJar.getInputStream(ze), DefaultBundleCache.BUFSIZE);
+ if (is == null)
+ {
+ throw new IOException("No input stream: " + jarPath);
+ }
+ // Copy the file.
+ copy(is, jarFile);
}
-
- // Create the file.
- copy(is, jarFile);
-
}
finally
{
diff --git a/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/ContentDirectoryContent.java b/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/ContentDirectoryContent.java
new file mode 100644
index 0000000..2003f86
--- /dev/null
+++ b/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/ContentDirectoryContent.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed 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.moduleloader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+
+public class ContentDirectoryContent implements IContent
+{
+ private IContent m_content = null;
+ private String m_path = null;
+ private boolean m_opened = false;
+
+ public ContentDirectoryContent(IContent content, String path)
+ {
+ m_content = content;
+ // Add a '/' to the end if not present.
+ m_path = (path.length() > 0) && (path.charAt(path.length() - 1) != '/')
+ ? path + "/" : path;
+ }
+
+ protected void finalize()
+ {
+ if (m_content != null)
+ {
+ m_content.close();
+ }
+ }
+
+ public void open()
+ {
+ m_content.open();
+ m_opened = true;
+ }
+
+ public synchronized void close()
+ {
+ try
+ {
+ if (m_content != null)
+ {
+ m_content.close();
+ }
+ }
+ catch (Exception ex)
+ {
+ System.err.println("JarContent: " + ex);
+ }
+
+ m_content = null;
+ m_opened = false;
+ }
+
+ public synchronized boolean hasEntry(String name) throws IllegalStateException
+ {
+ if (!m_opened)
+ {
+ throw new IllegalStateException("ContentDirectoryContent is not open");
+ }
+
+ if (name.charAt(0) == '/')
+ {
+ name = name.substring(1);
+ }
+
+ return m_content.hasEntry(m_path + name);
+ }
+
+ public synchronized byte[] getEntry(String name) throws IllegalStateException
+ {
+ if (!m_opened)
+ {
+ throw new IllegalStateException("ContentDirectoryContent is not open");
+ }
+
+ if (name.charAt(0) == '/')
+ {
+ name = name.substring(1);
+ }
+
+ return m_content.getEntry(m_path + name);
+ }
+
+ public synchronized InputStream getEntryAsStream(String name)
+ throws IllegalStateException, IOException
+ {
+ if (!m_opened)
+ {
+ throw new IllegalStateException("ContentDirectoryContent is not open");
+ }
+
+ if (name.charAt(0) == '/')
+ {
+ name = name.substring(1);
+ }
+
+ return m_content.getEntryAsStream(m_path + name);
+ }
+
+ public synchronized Enumeration getEntryPaths(String path)
+ {
+ if (!m_opened)
+ {
+ throw new IllegalStateException("ContentDirectoryContent is not open");
+ }
+
+ if (path.charAt(0) == '/')
+ {
+ path = path.substring(1);
+ }
+
+ return m_content.getEntryPaths(m_path + path);
+ }
+
+ public String toString()
+ {
+ return "CONTENT DIR " + m_path + " (" + m_content + ")";
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/JarContent.java b/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/JarContent.java
index 60184a9..c4ab58c 100644
--- a/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/JarContent.java
+++ b/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/JarContent.java
@@ -19,7 +19,6 @@
import java.io.*;
import java.util.Enumeration;
import java.util.NoSuchElementException;
-import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
public class JarContent implements IContent
@@ -27,7 +26,7 @@
private static final int BUFSIZE = 4096;
private File m_file = null;
- private JarFile m_jarFile = null;
+ private JarFileX m_jarFile = null;
private boolean m_opened = false;
public JarContent(File file)
@@ -255,7 +254,7 @@
{
if (m_jarFile == null)
{
- m_jarFile = new JarFile(m_file);
+ m_jarFile = new JarFileX(m_file);
}
}
diff --git a/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/JarFileX.java b/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/JarFileX.java
new file mode 100644
index 0000000..d64796f
--- /dev/null
+++ b/org.apache.felix.framework/src/main/java/org/apache/felix/moduleloader/JarFileX.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed 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.moduleloader;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+/**
+ * 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 JarFileX extends JarFile
+{
+ public JarFileX(File file) throws IOException
+ {
+ super(file);
+ }
+
+ public JarFileX(File file, boolean verify) throws IOException
+ {
+ super(file, verify);
+ }
+
+ public JarFileX(File file, boolean verify, int mode) throws IOException
+ {
+ super(file, verify, mode);
+ }
+
+ public JarFileX(String name) throws IOException
+ {
+ super(name);
+ }
+
+ public JarFileX(String name, boolean verify) throws IOException
+ {
+ super(name, verify);
+ }
+
+ 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;
+ }
+
+ public JarEntry getJarEntry(String name)
+ {
+ JarEntry entry = super.getJarEntry(name);
+ if ((entry != null) && (entry.getSize() == 0) && !entry.isDirectory())
+ {
+ JarEntry dirEntry = super.getJarEntry(name + '/');
+ if (dirEntry != null)
+ {
+ entry = dirEntry;
+ }
+ }
+ return entry;
+ }
+
+
+}