| /* |
| * Copyright 2005 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.osgi.framework.cache; |
| |
| 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.osgi.framework.LogWrapper; |
| import org.apache.osgi.framework.util.*; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleActivator; |
| |
| /** |
| * <p> |
| * This class, combined with <tt>DefaultBundleCache</tt>, implements the |
| * default file system-based bundle cache for Felix. |
| * </p> |
| * @see org.apache.osgi.framework.util.DefaultBundleCache |
| **/ |
| public class DefaultBundleArchive implements BundleArchive |
| { |
| private static final transient String BUNDLE_JAR_FILE = "bundle.jar"; |
| private static final transient String BUNDLE_LOCATION_FILE = "bundle.location"; |
| private static final transient String BUNDLE_STATE_FILE = "bundle.state"; |
| private static final transient String BUNDLE_START_LEVEL_FILE = "bundle.startlevel"; |
| private static final transient String REFRESH_COUNTER_FILE = "refresh.counter"; |
| private static final transient String BUNDLE_ACTIVATOR_FILE = "bundle.activator"; |
| |
| private static final transient String REVISION_DIRECTORY = "version"; |
| private static final transient String EMBEDDED_DIRECTORY = "embedded"; |
| private static final transient String LIBRARY_DIRECTORY = "lib"; |
| private static final transient String DATA_DIRECTORY = "data"; |
| |
| private static final transient String ACTIVE_STATE = "active"; |
| private static final transient String INSTALLED_STATE = "installed"; |
| private static final transient String UNINSTALLED_STATE = "uninstalled"; |
| |
| private LogWrapper m_logger = null; |
| private long m_id = -1; |
| private File m_dir = null; |
| private String m_location = null; |
| private int m_persistentState = -1; |
| private int m_startLevel = -1; |
| private Map m_currentHeader = null; |
| |
| private long m_refreshCount = -1; |
| private int m_revisionCount = -1; |
| |
| public DefaultBundleArchive(LogWrapper logger, File dir, long id, String location, InputStream is) |
| throws Exception |
| { |
| this(logger, dir, id); |
| m_location = location; |
| |
| // Try to save and pre-process the bundle JAR. |
| try |
| { |
| initialize(is); |
| } |
| catch (Exception ex) |
| { |
| if (!deleteDirectoryTree(dir)) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "Unable to delete the archive directory: " + id); |
| } |
| throw ex; |
| } |
| } |
| |
| public DefaultBundleArchive(LogWrapper logger, File dir, long id) |
| { |
| m_logger = logger; |
| m_dir = dir; |
| m_id = id; |
| if (m_id <= 0) |
| { |
| throw new IllegalArgumentException( |
| "Bundle ID cannot be less than or equal to zero."); |
| } |
| } |
| |
| private void initialize(InputStream is) |
| throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.INITIALIZE_ACTION, this, is)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| initializeUnchecked(is); |
| } |
| } |
| |
| private void initializeUnchecked(InputStream is) |
| throws Exception |
| { |
| FileWriter fw = null; |
| BufferedWriter bw = null; |
| |
| try |
| { |
| // Create archive directory. |
| if (!m_dir.mkdir()) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "DefaultBundleArchive: Unable to create archive directory."); |
| throw new IOException("Unable to create archive directory."); |
| } |
| |
| // Save location string. |
| File file = new File(m_dir, BUNDLE_LOCATION_FILE); |
| fw = new FileWriter(file); |
| bw = new BufferedWriter(fw); |
| bw.write(m_location, 0, m_location.length()); |
| |
| // Create version/revision directory for bundle JAR. |
| // Since this is only called when the bundle JAR is |
| // first saved, the update and revision will always |
| // be "0.0" for the directory name. |
| File revisionDir = new File(m_dir, REVISION_DIRECTORY + "0.0"); |
| if (!revisionDir.mkdir()) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "DefaultBundleArchive: Unable to create revision directory."); |
| throw new IOException("Unable to create revision directory."); |
| } |
| |
| // Save the bundle jar file. |
| file = new File(revisionDir, BUNDLE_JAR_FILE); |
| copy(is, file); |
| |
| // This will always be revision zero. |
| preprocessBundleJar(0, revisionDir); |
| |
| } |
| finally |
| { |
| if (is != null) is.close(); |
| if (bw != null) bw.close(); |
| if (fw != null) fw.close(); |
| } |
| } |
| |
| public File getDirectory() |
| { |
| return m_dir; |
| } |
| |
| public long getId() |
| { |
| return m_id; |
| } |
| |
| public String getLocation() |
| throws Exception |
| { |
| if (m_location != null) |
| { |
| return m_location; |
| } |
| else if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| return (String) AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.GET_LOCATION_ACTION, this)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| return getLocationUnchecked(); |
| } |
| } |
| |
| private String getLocationUnchecked() |
| throws Exception |
| { |
| // Get bundle location file. |
| File locFile = new File(m_dir, BUNDLE_LOCATION_FILE); |
| |
| // Read bundle location. |
| FileReader fr = null; |
| BufferedReader br = null; |
| try |
| { |
| fr = new FileReader(locFile); |
| br = new BufferedReader(fr); |
| m_location = br.readLine(); |
| return m_location; |
| } |
| finally |
| { |
| if (br != null) br.close(); |
| if (fr != null) fr.close(); |
| } |
| } |
| |
| public int getPersistentState() |
| throws Exception |
| { |
| if (m_persistentState >= 0) |
| { |
| return m_persistentState; |
| } |
| else if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| return ((Integer) AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.GET_PERSISTENT_STATE_ACTION, this))).intValue(); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| return getPersistentStateUnchecked(); |
| } |
| } |
| |
| private int getPersistentStateUnchecked() |
| throws Exception |
| { |
| // Get bundle state file. |
| File stateFile = new File(m_dir, BUNDLE_STATE_FILE); |
| |
| // If the state file doesn't exist, then |
| // assume the bundle was installed. |
| if (!stateFile.exists()) |
| { |
| return Bundle.INSTALLED; |
| } |
| |
| // Read the bundle state. |
| FileReader fr = null; |
| BufferedReader br= null; |
| try |
| { |
| fr = new FileReader(stateFile); |
| br = new BufferedReader(fr); |
| String s = br.readLine(); |
| if (s.equals(ACTIVE_STATE)) |
| { |
| m_persistentState = Bundle.ACTIVE; |
| } |
| else if (s.equals(UNINSTALLED_STATE)) |
| { |
| m_persistentState = Bundle.UNINSTALLED; |
| } |
| else |
| { |
| m_persistentState = Bundle.INSTALLED; |
| } |
| return m_persistentState; |
| } |
| finally |
| { |
| if (br != null) br.close(); |
| if (fr != null) fr.close(); |
| } |
| } |
| |
| public void setPersistentState(int state) |
| throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.SET_PERSISTENT_STATE_ACTION, this, state)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| setPersistentStateUnchecked(state); |
| } |
| } |
| |
| private void setPersistentStateUnchecked(int state) |
| throws Exception |
| { |
| // Get bundle state file. |
| File stateFile = new File(m_dir, BUNDLE_STATE_FILE); |
| |
| // Write the bundle state. |
| FileWriter fw = null; |
| BufferedWriter bw= null; |
| try |
| { |
| fw = new FileWriter(stateFile); |
| bw = new BufferedWriter(fw); |
| String s = null; |
| switch (state) |
| { |
| case Bundle.ACTIVE: |
| s = ACTIVE_STATE; |
| break; |
| case Bundle.UNINSTALLED: |
| s = UNINSTALLED_STATE; |
| break; |
| default: |
| s = INSTALLED_STATE; |
| break; |
| } |
| bw.write(s, 0, s.length()); |
| m_persistentState = state; |
| } |
| catch (IOException ex) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "DefaultBundleArchive: Unable to record state: " + ex); |
| throw ex; |
| } |
| finally |
| { |
| if (bw != null) bw.close(); |
| if (fw != null) fw.close(); |
| } |
| } |
| |
| public int getStartLevel() |
| throws Exception |
| { |
| if (m_startLevel >= 0) |
| { |
| return m_startLevel; |
| } |
| else if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| return ((Integer) AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.GET_START_LEVEL_ACTION, this))).intValue(); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| return getStartLevelUnchecked(); |
| } |
| } |
| |
| private int getStartLevelUnchecked() |
| throws Exception |
| { |
| // Get bundle start level file. |
| File levelFile = new File(m_dir, BUNDLE_START_LEVEL_FILE); |
| |
| // If the start level file doesn't exist, then |
| // return an error. |
| if (!levelFile.exists()) |
| { |
| return -1; |
| } |
| |
| // Read the bundle start level. |
| FileReader fr = null; |
| BufferedReader br= null; |
| try |
| { |
| fr = new FileReader(levelFile); |
| br = new BufferedReader(fr); |
| m_startLevel = Integer.parseInt(br.readLine()); |
| return m_startLevel; |
| } |
| finally |
| { |
| if (br != null) br.close(); |
| if (fr != null) fr.close(); |
| } |
| } |
| |
| public void setStartLevel(int level) |
| throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.SET_START_LEVEL_ACTION, this, level)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| setStartLevelUnchecked(level); |
| } |
| } |
| |
| private void setStartLevelUnchecked(int level) |
| throws Exception |
| { |
| // Get bundle start level file. |
| File levelFile = new File(m_dir, BUNDLE_START_LEVEL_FILE); |
| |
| // Write the bundle start level. |
| FileWriter fw = null; |
| BufferedWriter bw = null; |
| try |
| { |
| fw = new FileWriter(levelFile); |
| bw = new BufferedWriter(fw); |
| String s = Integer.toString(level); |
| bw.write(s, 0, s.length()); |
| m_startLevel = level; |
| } |
| catch (IOException ex) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "DefaultBundleArchive: Unable to record start leel: " + ex); |
| throw ex; |
| } |
| finally |
| { |
| if (bw != null) bw.close(); |
| if (fw != null) fw.close(); |
| } |
| } |
| |
| public File getDataFile(String fileName) |
| throws Exception |
| { |
| // Do some sanity checking. |
| if ((fileName.length() > 0) && (fileName.charAt(0) == File.separatorChar)) |
| throw new IllegalArgumentException("The data file path must be relative, not absolute."); |
| else if (fileName.indexOf("..") >= 0) |
| throw new IllegalArgumentException("The data file path cannot contain a reference to the \"..\" directory."); |
| |
| // Get bundle data directory. |
| File dataDir = new File(m_dir, DATA_DIRECTORY); |
| |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.CREATE_DATA_DIR_ACTION, this, dataDir)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| createDataDirectoryUnchecked(dataDir); |
| } |
| |
| // Return the data file. |
| return new File(dataDir, fileName); |
| } |
| |
| private void createDataDirectoryUnchecked(File dir) |
| throws Exception |
| { |
| // Create data directory if necessary. |
| if (!dir.exists()) |
| { |
| if (!dir.mkdir()) |
| { |
| throw new IOException("Unable to create bundle data directory."); |
| } |
| } |
| } |
| |
| public BundleActivator getActivator(ClassLoader loader) |
| throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| return (BundleActivator) AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.GET_ACTIVATOR_ACTION, this, loader)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| return getActivatorUnchecked(loader); |
| } |
| } |
| |
| private BundleActivator getActivatorUnchecked(ClassLoader loader) |
| throws Exception |
| { |
| // Get bundle activator file. |
| File activatorFile = new File(m_dir, BUNDLE_ACTIVATOR_FILE); |
| // If the activator file doesn't exist, then |
| // assume there isn't one. |
| if (!activatorFile.exists()) |
| return null; |
| |
| // Deserialize the activator object. |
| InputStream is = null; |
| ObjectInputStreamX ois = null; |
| try |
| { |
| is = new FileInputStream(activatorFile); |
| ois = new ObjectInputStreamX(is, loader); |
| Object o = ois.readObject(); |
| return (BundleActivator) o; |
| } |
| catch (Exception ex) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "DefaultBundleArchive: Trying to deserialize - " + ex); |
| } |
| finally |
| { |
| if (ois != null) ois.close(); |
| if (is != null) is.close(); |
| } |
| |
| return null; |
| } |
| |
| public void setActivator(Object obj) |
| throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.SET_ACTIVATOR_ACTION, this, obj)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| setActivatorUnchecked(obj); |
| } |
| } |
| |
| private void setActivatorUnchecked(Object obj) |
| throws Exception |
| { |
| if (!(obj instanceof Serializable)) |
| { |
| return; |
| } |
| |
| // Get bundle activator file. |
| File activatorFile = new File(m_dir, BUNDLE_ACTIVATOR_FILE); |
| |
| // Serialize the activator object. |
| OutputStream os = null; |
| ObjectOutputStream oos = null; |
| try |
| { |
| os = new FileOutputStream(activatorFile); |
| oos = new ObjectOutputStream(os); |
| oos.writeObject(obj); |
| } |
| catch (IOException ex) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "DefaultBundleArchive: Unable to serialize activator - " + ex); |
| throw ex; |
| } |
| finally |
| { |
| if (oos != null) oos.close(); |
| if (os != null) os.close(); |
| } |
| } |
| |
| public int getRevisionCount() |
| throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| return ((Integer) AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.GET_REVISION_COUNT_ACTION, this))).intValue(); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| return getRevisionCountUnchecked(); |
| } |
| } |
| |
| public int getRevisionCountUnchecked() |
| { |
| // We should always have at least one revision |
| // directory, so try to count them if the value |
| // has not been initialized yet. |
| if (m_revisionCount <= 0) |
| { |
| m_revisionCount = 0; |
| File[] children = m_dir.listFiles(); |
| for (int i = 0; (children != null) && (i < children.length); i++) |
| { |
| if (children[i].getName().startsWith(REVISION_DIRECTORY)) |
| { |
| m_revisionCount++; |
| } |
| } |
| } |
| return m_revisionCount; |
| } |
| |
| public Map getManifestHeader(int revision) |
| throws Exception |
| { |
| // If the request is for the current revision header, |
| // then return the cached copy if it is present. |
| if ((revision == (getRevisionCount() - 1)) && (m_currentHeader != null)) |
| { |
| return m_currentHeader; |
| } |
| |
| // Get the revision directory. |
| File revisionDir = new File( |
| m_dir, REVISION_DIRECTORY + getRefreshCount() + "." + revision); |
| |
| // Get the embedded resource. |
| JarFile 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); |
| } |
| |
| // 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. |
| Map map = new CaseInsensitiveMap(mf.getMainAttributes()); |
| // If the request is for the current revision's header, |
| // then cache it. |
| if (revision == (getRevisionCount() - 1)) |
| { |
| m_currentHeader = map; |
| } |
| return map; |
| |
| } catch (PrivilegedActionException ex) { |
| throw ((PrivilegedActionException) ex).getException(); |
| } finally { |
| if (jarFile != null) jarFile.close(); |
| } |
| } |
| |
| private JarFile 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); |
| } |
| |
| public String[] getClassPath(int revision) |
| throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| return (String []) AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.GET_CLASS_PATH_ACTION, this, revision)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| return getClassPathUnchecked(revision); |
| } |
| } |
| |
| private String[] getClassPathUnchecked(int revision) |
| throws Exception |
| { |
| // Get the revision directory. |
| File revisionDir = new File( |
| m_dir, REVISION_DIRECTORY + getRefreshCount() + "." + revision); |
| |
| // Get the bundle's manifest header. |
| Map map = getManifestHeader(revision); |
| if (map == null) |
| { |
| map = new HashMap(); |
| } |
| |
| // 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(); |
| } |
| } |
| |
| // Parse the class path into strings. |
| String[] classPathStrings = Util.parseDelimitedString( |
| classPath, FelixConstants.CLASS_PATH_SEPARATOR); |
| |
| if (classPathStrings == null) |
| { |
| classPathStrings = new String[0]; |
| } |
| |
| // Now, check for "." in the class path. |
| boolean includeDot = false; |
| for (int i = 0; !includeDot && (i < classPathStrings.length); i++) |
| { |
| if (classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT)) |
| { |
| includeDot = true; |
| } |
| } |
| |
| // Include all JARs in the embedded jar directory, since they |
| // were extracted when the bundle was initially saved. |
| File embedDir = new File(revisionDir, EMBEDDED_DIRECTORY); |
| String[] paths = null; |
| if (embedDir.exists()) |
| { |
| // The size of the paths array is the number of |
| // embedded JAR files plus one, if we need to include |
| // ".", otherwise it is just the number of JAR files. |
| // If "." is included, then it will be added to the |
| // first place in the path array below. |
| File[] children = embedDir.listFiles(); |
| int size = (children == null) ? 0 : children.length; |
| size = (includeDot) ? size + 1 : size; |
| paths = new String[size]; |
| for (int i = 0; i < children.length; i++) |
| { |
| // If we are including "." then skip the first slot, |
| // because this is where we will put the bundle JAR file. |
| paths[(includeDot) ? i + 1 : i] = children[i].getPath(); |
| } |
| } |
| |
| // If there is nothing on the class path, then include |
| // "." by default, as per the spec. |
| if ((paths == null) || (paths.length == 0)) |
| { |
| includeDot = true; |
| paths = new String[1]; |
| } |
| |
| // Put the bundle jar file first, if included. |
| if (includeDot) |
| { |
| paths[0] = revisionDir + File.separator + BUNDLE_JAR_FILE; |
| } |
| |
| return paths; |
| } |
| |
| // TODO: This will need to consider security. |
| public String findLibrary(int revision, String libName) |
| throws Exception |
| { |
| return findLibraryUnchecked(revision, libName); |
| } |
| |
| private String findLibraryUnchecked(int revision, String libName) |
| throws Exception |
| { |
| // Get the revision directory. |
| File revisionDir = new File( |
| m_dir.getAbsoluteFile(), |
| REVISION_DIRECTORY + getRefreshCount() + "." + revision); |
| |
| // Get bundle lib directory. |
| File libDir = new File(revisionDir, LIBRARY_DIRECTORY); |
| // Get lib file. |
| File libFile = new File(libDir, File.separatorChar + libName); |
| // Make sure that the library's parent directory exists; |
| // it may be in a sub-directory. |
| libDir = libFile.getParentFile(); |
| if (!libDir.exists()) |
| { |
| if (!libDir.mkdirs()) |
| { |
| throw new IOException("Unable to create library directory."); |
| } |
| } |
| // Extract the library from the JAR file if it does not |
| // already exist. |
| if (!libFile.exists()) |
| { |
| JarFile jarFile = null; |
| InputStream is = null; |
| |
| try |
| { |
| jarFile = openBundleJarUnchecked(revisionDir); |
| ZipEntry ze = jarFile.getEntry(libName); |
| if (ze == null) |
| { |
| throw new IOException("No JAR entry: " + libName); |
| } |
| is = new BufferedInputStream( |
| jarFile.getInputStream(ze), DefaultBundleCache.BUFSIZE); |
| if (is == null) |
| { |
| throw new IOException("No input stream: " + libName); |
| } |
| |
| // Create the file. |
| copy(is, libFile); |
| |
| } |
| finally |
| { |
| if (jarFile != null) jarFile.close(); |
| if (is != null) is.close(); |
| } |
| } |
| |
| return libFile.toString(); |
| } |
| |
| /** |
| * This utility method is used to retrieve the current refresh |
| * counter value for the bundle. This value is used when generating |
| * the bundle JAR directory name where native libraries are extracted. |
| * This is necessary because Sun's JVM requires a one-to-one mapping |
| * between native libraries and class loaders where the native library |
| * is uniquely identified by its absolute path in the file system. This |
| * constraint creates a problem when a bundle is refreshed, because it |
| * gets a new class loader. Using the refresh counter to generate the name |
| * of the bundle JAR directory resolves this problem because each time |
| * bundle is refresh, the native library will have a unique name. |
| * As a result of the unique name, the JVM will then reload the |
| * native library without a problem. |
| **/ |
| private long getRefreshCount() |
| throws Exception |
| { |
| // If we have already read the update counter file, |
| // then just return the result. |
| if (m_refreshCount >= 0) |
| { |
| return m_refreshCount; |
| } |
| |
| // Get update counter file. |
| File counterFile = new File(m_dir, REFRESH_COUNTER_FILE); |
| |
| // If the update counter file doesn't exist, then |
| // assume the counter is at zero. |
| if (!counterFile.exists()) |
| { |
| return 0; |
| } |
| |
| // Read the bundle update counter. |
| FileReader fr = null; |
| BufferedReader br = null; |
| try |
| { |
| fr = new FileReader(counterFile); |
| br = new BufferedReader(fr); |
| long counter = Long.parseLong(br.readLine()); |
| return counter; |
| } |
| finally |
| { |
| if (br != null) br.close(); |
| if (fr != null) fr.close(); |
| } |
| } |
| |
| /** |
| * This utility method is used to retrieve the current refresh |
| * counter value for the bundle. This value is used when generating |
| * the bundle JAR directory name where native libraries are extracted. |
| * This is necessary because Sun's JVM requires a one-to-one mapping |
| * between native libraries and class loaders where the native library |
| * is uniquely identified by its absolute path in the file system. This |
| * constraint creates a problem when a bundle is refreshed, because it |
| * gets a new class loader. Using the refresh counter to generate the name |
| * of the bundle JAR directory resolves this problem because each time |
| * bundle is refresh, the native library will have a unique name. |
| * As a result of the unique name, the JVM will then reload the |
| * native library without a problem. |
| **/ |
| private void setRefreshCount(long counter) |
| throws Exception |
| { |
| // Get update counter file. |
| File counterFile = new File(m_dir, REFRESH_COUNTER_FILE); |
| |
| // Write the update counter. |
| FileWriter fw = null; |
| BufferedWriter bw = null; |
| try |
| { |
| fw = new FileWriter(counterFile); |
| bw = new BufferedWriter(fw); |
| String s = Long.toString(counter); |
| bw.write(s, 0, s.length()); |
| m_refreshCount = counter; |
| } |
| catch (IOException ex) |
| { |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "DefaultBundleArchive: Unable to write counter: " + ex); |
| throw ex; |
| } |
| finally |
| { |
| if (bw != null) bw.close(); |
| if (fw != null) fw.close(); |
| } |
| } |
| |
| // |
| // File-oriented utility methods. |
| // |
| |
| protected static boolean deleteDirectoryTree(File target) |
| { |
| if (!target.exists()) |
| { |
| return true; |
| } |
| |
| if (target.isDirectory()) |
| { |
| File[] files = target.listFiles(); |
| for (int i = 0; i < files.length; i++) |
| { |
| deleteDirectoryTree(files[i]); |
| } |
| } |
| |
| return target.delete(); |
| } |
| |
| /** |
| * This method copies an input stream to the specified file. |
| * <p> |
| * Security: This method must be called from within a <tt>doPrivileged()</tt> |
| * block since it accesses the disk. |
| * @param is the input stream to copy. |
| * @param outputFile the file to which the input stream should be copied. |
| **/ |
| private void copy(InputStream is, File outputFile) |
| throws IOException |
| { |
| OutputStream os = null; |
| |
| try |
| { |
| os = new BufferedOutputStream( |
| new FileOutputStream(outputFile), DefaultBundleCache.BUFSIZE); |
| byte[] b = new byte[DefaultBundleCache.BUFSIZE]; |
| int len = 0; |
| while ((len = is.read(b)) != -1) |
| os.write(b, 0, len); |
| } |
| finally |
| { |
| if (is != null) is.close(); |
| if (os != null) os.close(); |
| } |
| } |
| |
| /** |
| * This method pre-processes a bundle JAR file making it ready |
| * for use. This entails extracting all embedded JAR files and |
| * all native libraries. |
| * @throws java.lang.Exception if any error occurs while processing JAR file. |
| **/ |
| private void preprocessBundleJar(int revision, File revisionDir) |
| throws Exception |
| { |
| // |
| // Create special directories so that we can avoid checking |
| // for their existence all the time. |
| // |
| |
| File embedDir = new File(revisionDir, EMBEDDED_DIRECTORY); |
| if (!embedDir.exists()) |
| { |
| if (!embedDir.mkdir()) |
| { |
| throw new IOException("Could not create embedded JAR directory."); |
| } |
| } |
| |
| File libDir = new File(revisionDir, LIBRARY_DIRECTORY); |
| if (!libDir.exists()) |
| { |
| if (!libDir.mkdir()) |
| { |
| throw new IOException("Unable to create native library directory."); |
| } |
| } |
| |
| // |
| // This block extracts all embedded JAR files. |
| // |
| |
| try |
| { |
| // Get the bundle's manifest header. |
| Map map = getManifestHeader(revision); |
| if (map == null) |
| { |
| map = new HashMap(); |
| } |
| |
| // 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(); |
| } |
| } |
| |
| // Parse the class path into strings. |
| String[] classPathStrings = Util.parseDelimitedString( |
| classPath, FelixConstants.CLASS_PATH_SEPARATOR); |
| |
| if (classPathStrings == null) |
| { |
| classPathStrings = new String[0]; |
| } |
| |
| for (int i = 0; i < classPathStrings.length; i++) |
| { |
| if (!classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT)) |
| { |
| extractEmbeddedJar(revisionDir, classPathStrings[i]); |
| } |
| } |
| |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| |
| /** |
| * This method extracts an embedded JAR file from the bundle's |
| * JAR file. |
| * <p> |
| * Security: This method must be called from within a <tt>doPrivileged()</tt> |
| * block since it accesses the disk. |
| * @param id the identifier of the bundle that owns the embedded JAR file. |
| * @param jarPath the path to the embedded JAR file inside the bundle JAR file. |
| **/ |
| private void extractEmbeddedJar(File revisionDir, String jarPath) |
| throws Exception |
| { |
| // Remove leading slash if present. |
| jarPath = (jarPath.charAt(0) == '/') ? jarPath.substring(1) : jarPath; |
| // Get only the JAR file name. |
| String jarName = (jarPath.lastIndexOf('/') >= 0) |
| ? jarPath.substring(jarPath.lastIndexOf('/') + 1) : jarPath; |
| |
| // If JAR is already extracted, then don't |
| // re-extract it... |
| File embedFile = new File( |
| revisionDir, EMBEDDED_DIRECTORY + File.separatorChar + jarName); |
| if (!embedFile.exists()) |
| { |
| JarFile jarFile = null; |
| InputStream is = null; |
| |
| try |
| { |
| jarFile = openBundleJarUnchecked(revisionDir); |
| ZipEntry ze = jarFile.getEntry(jarPath); |
| if (ze == null) |
| { |
| throw new IOException("No JAR entry: " + jarPath); |
| } |
| is = new BufferedInputStream(jarFile.getInputStream(ze), DefaultBundleCache.BUFSIZE); |
| if (is == null) |
| { |
| throw new IOException("No input stream: " + jarPath); |
| } |
| |
| // Create the file. |
| copy(is, embedFile); |
| |
| } |
| finally |
| { |
| if (jarFile != null) jarFile.close(); |
| if (is != null) is.close(); |
| } |
| } |
| } |
| |
| // INCREASES THE REVISION COUNT. |
| protected void update(InputStream is) throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.UPDATE_ACTION, this, is)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| updateUnchecked(is); |
| } |
| } |
| |
| // INCREASES THE REVISION COUNT. |
| private void updateUnchecked(InputStream is) throws Exception |
| { |
| File revisionDir = null; |
| |
| try |
| { |
| // Create the new revision directory. |
| int revision = getRevisionCountUnchecked(); |
| revisionDir = new File( |
| m_dir, REVISION_DIRECTORY |
| + getRefreshCount() + "." + revision); |
| if (!revisionDir.mkdir()) |
| { |
| throw new IOException("Unable to create revision directory."); |
| } |
| |
| // Save the new revision bundle jar file. |
| File file = new File(revisionDir, BUNDLE_JAR_FILE); |
| copy(is, file); |
| |
| preprocessBundleJar(revision, revisionDir); |
| } |
| catch (Exception ex) |
| { |
| if ((revisionDir != null) && revisionDir.exists()) |
| { |
| try |
| { |
| deleteDirectoryTree(revisionDir); |
| } |
| catch (Exception ex2) |
| { |
| // There is very little we can do here. |
| m_logger.log( |
| LogWrapper.LOG_ERROR, |
| "Unable to remove partial revision directory.", ex2); |
| } |
| } |
| throw ex; |
| } |
| |
| // If everything was successful, then update |
| // the revision count. |
| m_revisionCount++; |
| // Clear the cached revision header, since it is |
| // no longer the current revision. |
| m_currentHeader = null; |
| } |
| |
| // DECREASES THE REVISION COUNT. |
| protected void purge() throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.PURGE_ACTION, this)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| purgeUnchecked(); |
| } |
| } |
| |
| // DECREASES THE REVISION COUNT. |
| private void purgeUnchecked() throws Exception |
| { |
| // Get the current update count. |
| long update = getRefreshCount(); |
| // Get the current revision count. |
| int count = getRevisionCountUnchecked(); |
| |
| File revisionDir = null; |
| for (int i = 0; i < count - 1; i++) |
| { |
| revisionDir = new File(m_dir, REVISION_DIRECTORY + update + "." + i); |
| if (revisionDir.exists()) |
| { |
| deleteDirectoryTree(revisionDir); |
| } |
| } |
| // Increment the update count. |
| setRefreshCount(update + 1); |
| |
| // Rename the current revision to be the current update. |
| File currentDir = new File(m_dir, REVISION_DIRECTORY + (update + 1) + ".0"); |
| revisionDir = new File(m_dir, REVISION_DIRECTORY + update + "." + (count - 1)); |
| revisionDir.renameTo(currentDir); |
| |
| // If everything is successful, then set the revision |
| // count to one. |
| m_revisionCount = 1; |
| // Although the cached current header should stay the same |
| // here, clear it for consistency. |
| m_currentHeader = null; |
| } |
| |
| protected void remove() throws Exception |
| { |
| if (System.getSecurityManager() != null) |
| { |
| try |
| { |
| AccessController.doPrivileged( |
| new PrivilegedAction( |
| PrivilegedAction.REMOVE_ACTION, this)); |
| } |
| catch (PrivilegedActionException ex) |
| { |
| throw ((PrivilegedActionException) ex).getException(); |
| } |
| } |
| else |
| { |
| removeUnchecked(); |
| } |
| } |
| |
| private void removeUnchecked() throws Exception |
| { |
| deleteDirectoryTree(m_dir); |
| } |
| |
| // |
| // Utility class for performing privileged actions. |
| // |
| |
| private static class PrivilegedAction implements PrivilegedExceptionAction |
| { |
| private static final int INITIALIZE_ACTION = 0; |
| private static final int UPDATE_ACTION = 1; |
| private static final int PURGE_ACTION = 2; |
| private static final int REMOVE_ACTION = 3; |
| private static final int GET_REVISION_COUNT_ACTION = 4; |
| private static final int GET_LOCATION_ACTION = 5; |
| private static final int GET_PERSISTENT_STATE_ACTION = 6; |
| private static final int SET_PERSISTENT_STATE_ACTION = 7; |
| private static final int GET_START_LEVEL_ACTION = 8; |
| private static final int SET_START_LEVEL_ACTION = 9; |
| private static final int OPEN_BUNDLE_JAR_ACTION = 10; |
| private static final int CREATE_DATA_DIR_ACTION = 11; |
| private static final int GET_CLASS_PATH_ACTION = 12; |
| private static final int GET_ACTIVATOR_ACTION = 13; |
| private static final int SET_ACTIVATOR_ACTION = 14; |
| |
| private int m_action = 0; |
| private DefaultBundleArchive m_archive = null; |
| private InputStream m_isArg = null; |
| private int m_intArg = 0; |
| private File m_fileArg = null; |
| private ClassLoader m_loaderArg = null; |
| private Object m_objArg = null; |
| |
| public PrivilegedAction(int action, DefaultBundleArchive archive) |
| { |
| m_action = action; |
| m_archive = archive; |
| } |
| |
| public PrivilegedAction(int action, DefaultBundleArchive archive, InputStream isArg) |
| { |
| m_action = action; |
| m_archive = archive; |
| m_isArg = isArg; |
| } |
| |
| public PrivilegedAction(int action, DefaultBundleArchive archive, int intArg) |
| { |
| m_action = action; |
| m_archive = archive; |
| m_intArg = intArg; |
| } |
| |
| public PrivilegedAction(int action, DefaultBundleArchive archive, File fileArg) |
| { |
| m_action = action; |
| m_archive = archive; |
| m_fileArg = fileArg; |
| } |
| |
| public PrivilegedAction(int action, DefaultBundleArchive archive, ClassLoader loaderArg) |
| { |
| m_action = action; |
| m_archive = archive; |
| m_loaderArg = loaderArg; |
| } |
| |
| public PrivilegedAction(int action, DefaultBundleArchive archive, Object objArg) |
| { |
| m_action = action; |
| m_archive = archive; |
| m_objArg = objArg; |
| } |
| |
| public Object run() throws Exception |
| { |
| switch (m_action) |
| { |
| case INITIALIZE_ACTION: |
| m_archive.initializeUnchecked(m_isArg); |
| return null; |
| case UPDATE_ACTION: |
| m_archive.updateUnchecked(m_isArg); |
| return null; |
| case PURGE_ACTION: |
| m_archive.purgeUnchecked(); |
| return null; |
| case REMOVE_ACTION: |
| m_archive.removeUnchecked(); |
| return null; |
| case GET_REVISION_COUNT_ACTION: |
| return new Integer(m_archive.getRevisionCountUnchecked()); |
| case GET_LOCATION_ACTION: |
| return m_archive.getLocationUnchecked(); |
| case GET_PERSISTENT_STATE_ACTION: |
| return new Integer(m_archive.getPersistentStateUnchecked()); |
| case SET_PERSISTENT_STATE_ACTION: |
| m_archive.setPersistentStateUnchecked(m_intArg); |
| return null; |
| case GET_START_LEVEL_ACTION: |
| return new Integer(m_archive.getStartLevelUnchecked()); |
| case SET_START_LEVEL_ACTION: |
| m_archive.setStartLevelUnchecked(m_intArg); |
| return null; |
| case OPEN_BUNDLE_JAR_ACTION: |
| return m_archive.openBundleJarUnchecked(m_fileArg); |
| case CREATE_DATA_DIR_ACTION: |
| m_archive.createDataDirectoryUnchecked(m_fileArg); |
| return null; |
| case GET_CLASS_PATH_ACTION: |
| return m_archive.getClassPathUnchecked(m_intArg); |
| case GET_ACTIVATOR_ACTION: |
| return m_archive.getActivatorUnchecked(m_loaderArg); |
| case SET_ACTIVATOR_ACTION: |
| m_archive.setActivatorUnchecked(m_objArg); |
| return null; |
| } |
| |
| throw new IllegalArgumentException("Invalid action specified."); |
| } |
| } |
| } |