Modify cache so it makes a copy each time a native library is requested.
This is necessary for fragments containing native libraries, since they
can attach to multiple hosts. (FELIX-1134)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@793616 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java b/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
index 707f752..47ab08a 100644
--- a/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
@@ -219,7 +219,7 @@
String entryName = m_nativeLibraries[i].getEntryName();
if (entryName != null)
{
- if (m_content.getEntryAsNativeLibrary(entryName) == null)
+ if (!m_content.hasEntry(entryName))
{
throw new BundleException("Native library does not exist: " + entryName);
}
@@ -1567,6 +1567,9 @@
public class ModuleClassLoader extends SecureClassLoader implements BundleReference
{
private final Map m_jarContentToDexFile;
+ private Object[][] m_libs = new Object[0][];
+ private static final int LIBNAME_IDX = 0;
+ private static final int LIBPATH_IDX = 1;
public ModuleClassLoader(ClassLoader parent)
{
@@ -1882,16 +1885,46 @@
name = name.substring(1);
}
- R4Library[] libs = getNativeLibraries();
- for (int i = 0; (libs != null) && (i < libs.length); i++)
+ String result = null;
+ // CONCURRENCY: In the long run, we might want to break this
+ // sync block in two to avoid manipulating the cache while
+ // holding the lock, but for now we will do it the simple way.
+ synchronized (this)
{
- if (libs[i].match(m_configMap, name))
+ // Check to make sure we haven't already found this library.
+ for (int i = 0; (result == null) && (i < m_libs.length); i++)
{
- return getContent().getEntryAsNativeLibrary(libs[i].getEntryName());
+ if (m_libs[i][LIBNAME_IDX].equals(name))
+ {
+ result = (String) m_libs[i][LIBPATH_IDX];
+ }
+ }
+
+ // If we don't have a cached result, see if we have a matching
+ // native library.
+ if (result == null)
+ {
+ R4Library[] libs = getNativeLibraries();
+ for (int i = 0; (libs != null) && (i < libs.length); i++)
+ {
+ if (libs[i].match(m_configMap, name))
+ {
+ result = getContent().getEntryAsNativeLibrary(libs[i].getEntryName());
+ }
+ }
+
+ // Remember the result for future requests.
+ if (result != null)
+ {
+ Object[][] tmp = new Object[m_libs.length + 1][];
+ System.arraycopy(m_libs, 0, tmp, 0, m_libs.length);
+ tmp[m_libs.length] = new Object[] { name, result };
+ m_libs = tmp;
+ }
}
}
- return null;
+ return result;
}
public String toString()
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 25e1784..fd42c17 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
@@ -23,18 +23,21 @@
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.osgi.framework.Constants;
public class DirectoryContent implements IContent
{
private static final int BUFSIZE = 4096;
private static final transient String EMBEDDED_DIRECTORY = "-embedded";
- private static final transient String LIBRARY_DIRECTORY = "lib";
+ private static final transient String LIBRARY_DIRECTORY = "-lib";
private final Logger m_logger;
private final Map m_configMap;
private final Object m_revisionLock;
private final File m_rootDir;
private final File m_dir;
+ private int m_libCount = 0;
public DirectoryContent(Logger logger, Map configMap, Object revisionLock,
File rootDir, File dir)
@@ -155,15 +158,15 @@
else if (BundleCache.getSecureAction().fileExists(file)
&& entryName.endsWith(".jar"))
{
- File extractedDir = new File(embedDir,
+ File extractDir = new File(embedDir,
(entryName.lastIndexOf('/') >= 0)
? entryName.substring(0, entryName.lastIndexOf('/'))
: entryName);
synchronized (m_revisionLock)
{
- if (!BundleCache.getSecureAction().fileExists(extractedDir))
+ if (!BundleCache.getSecureAction().fileExists(extractDir))
{
- if (!BundleCache.getSecureAction().mkdirs(extractedDir))
+ if (!BundleCache.getSecureAction().mkdirs(extractDir))
{
m_logger.log(
Logger.LOG_ERROR,
@@ -171,7 +174,7 @@
}
}
}
- return new JarContent(m_logger, m_configMap, m_revisionLock, extractedDir, file);
+ return new JarContent(m_logger, m_configMap, m_revisionLock, extractDir, file);
}
// The entry could not be found, so return null.
@@ -179,9 +182,110 @@
}
// TODO: This will need to consider security.
- public synchronized String getEntryAsNativeLibrary(String name)
+ public synchronized String getEntryAsNativeLibrary(String entryName)
{
- return BundleCache.getSecureAction().getAbsolutePath(new File(m_rootDir, name));
+ // Return result.
+ String result = null;
+
+ // Remove any leading slash, since all bundle class path
+ // entries are relative to the root of the bundle.
+ entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName;
+
+ // Any embedded native library files will be extracted to the lib directory.
+ File libDir = new File(m_rootDir, m_dir.getName() + LIBRARY_DIRECTORY);
+
+ // The entry must exist and refer to a file, not a directory,
+ // since we are expecting it to be a native library.
+ File entryFile = new File(m_dir, entryName);
+ if (BundleCache.getSecureAction().fileExists(entryFile)
+ && !BundleCache.getSecureAction().isFileDirectory(entryFile))
+ {
+ // Extracting the embedded native library file impacts all other
+ // existing contents for this revision, so we have to grab the
+ // revision lock first before trying to extract the embedded JAR
+ // file to avoid a race condition.
+ synchronized (m_revisionLock)
+ {
+ // Since native libraries cannot be shared, we must extract a
+ // separate copy per request, so use the request library counter
+ // as part of the extracted path.
+ File libFile = new File(
+ libDir, Integer.toString(m_libCount) + File.separatorChar + entryName);
+ // Increment library request counter.
+ m_libCount++;
+
+ if (!BundleCache.getSecureAction().fileExists(libFile))
+ {
+ if (!BundleCache.getSecureAction().fileExists(libFile.getParentFile()))
+ {
+ if (!BundleCache.getSecureAction().mkdirs(libFile.getParentFile()))
+ {
+ m_logger.log(
+ Logger.LOG_ERROR,
+ "Unable to create library directory.");
+ }
+ else
+ {
+ InputStream is = null;
+
+ try
+ {
+ is = new BufferedInputStream(
+ new FileInputStream(entryFile),
+ BundleCache.BUFSIZE);
+ if (is == null)
+ {
+ throw new IOException("No input stream: " + entryName);
+ }
+
+ // Create the file.
+ BundleCache.copyStreamToFile(is, libFile);
+
+ // Perform exec permission command on extracted library
+ // if one is configured.
+ String command = (String) m_configMap.get(
+ Constants.FRAMEWORK_EXECPERMISSION);
+ if (command != null)
+ {
+ Properties props = new Properties();
+ props.setProperty("abspath", libFile.toString());
+ command = Util.substVars(command, "command", null, props);
+ Process p = BundleCache.getSecureAction().exec(command);
+ p.waitFor();
+ }
+
+ // Return the path to the extracted native library.
+ result = BundleCache.getSecureAction().getAbsolutePath(libFile);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ Logger.LOG_ERROR,
+ "Extracting native library.", ex);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ // Not much we can do.
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // Return the path to the extracted native library.
+ result = BundleCache.getSecureAction().getAbsolutePath(libFile);
+ }
+ }
+ }
+
+ return result;
}
public String toString()
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 ca6095c..ebb7fa0 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
@@ -38,7 +38,7 @@
{
private static final int BUFSIZE = 4096;
private static final transient String EMBEDDED_DIRECTORY = "-embedded";
- private static final transient String LIBRARY_DIRECTORY = "lib";
+ private static final transient String LIBRARY_DIRECTORY = "-lib";
private final Logger m_logger;
private final Map m_configMap;
@@ -46,6 +46,7 @@
private final File m_rootDir;
private final File m_file;
private JarFileX m_jarFile = null;
+ private int m_libCount = 0;
public JarContent(Logger logger, Map configMap, Object revisionLock, File rootDir, File file)
{
@@ -303,7 +304,7 @@
ZipEntry ze = m_jarFile.getEntry(entryName);
if ((ze != null) && ze.isDirectory())
{
- File extractedDir = new File(embedDir, entryName);
+ File extractDir = new File(embedDir, entryName);
// Extracting an embedded directory file impacts all other existing
// contents for this revision, so we have to grab the revision
@@ -311,9 +312,9 @@
// directory to avoid a race condition.
synchronized (m_revisionLock)
{
- if (!BundleCache.getSecureAction().fileExists(extractedDir))
+ if (!BundleCache.getSecureAction().fileExists(extractDir))
{
- if (!BundleCache.getSecureAction().mkdirs(extractedDir))
+ if (!BundleCache.getSecureAction().mkdirs(extractDir))
{
m_logger.log(
Logger.LOG_ERROR,
@@ -325,7 +326,7 @@
}
else if ((ze != null) && ze.getName().endsWith(".jar"))
{
- File extractedJar = new File(embedDir, entryName);
+ File extractJar = new File(embedDir, entryName);
// Extracting the embedded JAR file impacts all other existing
// contents for this revision, so we have to grab the revision
@@ -333,7 +334,7 @@
// to avoid a race condition.
synchronized (m_revisionLock)
{
- if (!BundleCache.getSecureAction().fileExists(extractedJar))
+ if (!BundleCache.getSecureAction().fileExists(extractJar))
{
try
{
@@ -349,7 +350,7 @@
}
return new JarContent(
m_logger, m_configMap, m_revisionLock,
- extractedJar.getParentFile(), extractedJar);
+ extractJar.getParentFile(), extractJar);
}
// The entry could not be found, so return null.
@@ -357,8 +358,11 @@
}
// TODO: This will need to consider security.
- public synchronized String getEntryAsNativeLibrary(String name)
+ public synchronized String getEntryAsNativeLibrary(String entryName)
{
+ // Return result.
+ String result = null;
+
// Open JAR file if not already opened.
if (m_jarFile == null)
{
@@ -370,83 +374,111 @@
{
m_logger.log(
Logger.LOG_ERROR,
- "JarContent: Unable to open JAR file.", ex);
+ "Unable to open JAR file.", ex);
return null;
}
+
}
- // Get bundle lib directory.
- File libDir = new File(m_rootDir, LIBRARY_DIRECTORY);
- // Get lib file.
- File libFile = new File(libDir, File.separatorChar + name);
- // Make sure that the library's parent directory exists;
- // it may be in a sub-directory.
- libDir = libFile.getParentFile();
- if (!BundleCache.getSecureAction().fileExists(libDir))
+ // Remove any leading slash.
+ entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName;
+
+ // Any embedded native libraries will be extracted to the lib directory.
+ // Since embedded library file names may clash when extracting from multiple
+ // embedded JAR files, the embedded lib directory is per embedded JAR file.
+ File libDir = new File(m_rootDir, m_file.getName() + LIBRARY_DIRECTORY);
+
+ // The entry name must refer to a file type, since it is
+ // a native library, not a directory.
+ ZipEntry ze = m_jarFile.getEntry(entryName);
+ if ((ze != null) && !ze.isDirectory())
{
- if (!BundleCache.getSecureAction().mkdirs(libDir))
+ // Extracting the embedded native library file impacts all other
+ // existing contents for this revision, so we have to grab the
+ // revision lock first before trying to extract the embedded JAR
+ // file to avoid a race condition.
+ synchronized (m_revisionLock)
{
- m_logger.log(
- Logger.LOG_ERROR,
- "JarContent: Unable to create library directory.");
- return null;
- }
- }
- // Extract the library from the JAR file if it does not
- // already exist.
- if (!BundleCache.getSecureAction().fileExists(libFile))
- {
- InputStream is = null;
+ // Since native libraries cannot be shared, we must extract a
+ // separate copy per request, so use the request library counter
+ // as part of the extracted path.
+ File libFile = new File(
+ libDir, Integer.toString(m_libCount) + File.separatorChar + entryName);
+ // Increment library request counter.
+ m_libCount++;
- try
- {
- ZipEntry ze = m_jarFile.getEntry(name);
- if (ze == null)
+ if (!BundleCache.getSecureAction().fileExists(libFile))
{
- return null;
- }
- is = new BufferedInputStream(
- m_jarFile.getInputStream(ze), BundleCache.BUFSIZE);
- if (is == null)
- {
- throw new IOException("No input stream: " + name);
- }
+ if (!BundleCache.getSecureAction().fileExists(libFile.getParentFile()))
+ {
+ if (!BundleCache.getSecureAction().mkdirs(libFile.getParentFile()))
+ {
+ m_logger.log(
+ Logger.LOG_ERROR,
+ "Unable to create library directory.");
+ }
+ else
+ {
+ InputStream is = null;
- // Create the file.
- BundleCache.copyStreamToFile(is, libFile);
+ try
+ {
+ is = new BufferedInputStream(
+ m_jarFile.getInputStream(ze),
+ BundleCache.BUFSIZE);
+ if (is == null)
+ {
+ throw new IOException("No input stream: " + entryName);
+ }
- // Perform exec permission command on extracted library
- // if one is configured.
- String command = (String) m_configMap.get(Constants.FRAMEWORK_EXECPERMISSION);
- if (command != null)
- {
- Properties props = new Properties();
- props.setProperty("abspath", libFile.toString());
- command = Util.substVars(command, "command", null, props);
- Process p = BundleCache.getSecureAction().exec(command);
- p.waitFor();
+ // Create the file.
+ BundleCache.copyStreamToFile(is, libFile);
+
+ // Perform exec permission command on extracted library
+ // if one is configured.
+ String command = (String) m_configMap.get(
+ Constants.FRAMEWORK_EXECPERMISSION);
+ if (command != null)
+ {
+ Properties props = new Properties();
+ props.setProperty("abspath", libFile.toString());
+ command = Util.substVars(command, "command", null, props);
+ Process p = BundleCache.getSecureAction().exec(command);
+ p.waitFor();
+ }
+
+ // Return the path to the extracted native library.
+ result = BundleCache.getSecureAction().getAbsolutePath(libFile);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ Logger.LOG_ERROR,
+ "Extracting native library.", ex);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ // Not much we can do.
+ }
+ }
+ }
+ }
}
- }
- catch (Exception ex)
- {
- m_logger.log(
- Logger.LOG_ERROR,
- "JarContent: Extracting native library.", ex);
- }
- finally
- {
- try
+ else
{
- if (is != null) is.close();
- }
- catch (IOException ex)
- {
- // Not much we can do.
+ // Return the path to the extracted native library.
+ result = BundleCache.getSecureAction().getAbsolutePath(libFile);
}
}
}
- return BundleCache.getSecureAction().getAbsolutePath(libFile);
+ return result;
}
public String toString()
diff --git a/framework/src/main/java/org/apache/felix/moduleloader/IContent.java b/framework/src/main/java/org/apache/felix/moduleloader/IContent.java
index 2b51c1e..93dcd9a 100644
--- a/framework/src/main/java/org/apache/felix/moduleloader/IContent.java
+++ b/framework/src/main/java/org/apache/felix/moduleloader/IContent.java
@@ -32,7 +32,7 @@
* is already closed, then calls on this method should have no effect.
* </p>
**/
- public void close();
+ void close();
/**
* <p>
@@ -44,7 +44,7 @@
* @return <tt>true</tt> if a corresponding entry was found, <tt>false</tt>
* otherwise.
**/
- public boolean hasEntry(String name);
+ boolean hasEntry(String name);
/**
* <p>
@@ -56,7 +56,7 @@
* </p>
* @returns An enumeration of entry names or <tt>null</tt>.
**/
- public Enumeration getEntries();
+ Enumeration getEntries();
/**
* <p>
@@ -66,7 +66,7 @@
* @return An array of bytes if the corresponding entry was found, <tt>null</tt>
* otherwise.
**/
- public byte[] getEntryAsBytes(String name);
+ byte[] getEntryAsBytes(String name);
/**
* <p>
@@ -77,8 +77,7 @@
* otherwise.
* @throws <tt>java.io.IOException</tt> if any error occurs.
**/
- public InputStream getEntryAsStream(String name)
- throws IOException;
+ InputStream getEntryAsStream(String name) throws IOException;
/**
* <p>
@@ -94,22 +93,20 @@
* @return An <tt>IContent</tt> instance if a corresponding entry was found,
* <tt>null</tt> otherwise.
**/
- public IContent getEntryAsContent(String name);
+ IContent getEntryAsContent(String name);
/**
* <p>
* This method returns the named entry as a file in the file system for
* use as a native library. It may not be possible for all content
* implementations (e.g., memory only) to implement this method, in which
- * case it is acceptable to return <tt>null</tt>.
+ * case it is acceptable to return <tt>null</tt>. Since native libraries
+ * can only be associated with a single class loader, this method should
+ * return a unique file per request.
* </p>
* @param name The name of the entry to retrieve as a file.
* @return A string corresponding to the absolute path of the file if a
* corresponding entry was found, <tt>null</tt> otherwise.
**/
-// TODO: CACHE - This method needs to be rethought once we start allowing
-// native libs in fragments to support multi-host attachement.
-// For now, our implementations of this interface will not
-// return a new file for every invocation.
- public String getEntryAsNativeLibrary(String name);
+ String getEntryAsNativeLibrary(String name);
}
\ No newline at end of file