| /******************************************************************************* |
| * Copyright (c) 2005, 2013 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Rob Harrop - SpringSource Inc. (bug 253942) |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.baseadaptor.bundlefile; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| import org.eclipse.osgi.baseadaptor.BaseData; |
| import org.eclipse.osgi.framework.debug.Debug; |
| import org.eclipse.osgi.internal.baseadaptor.*; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.FrameworkEvent; |
| |
| /** |
| * A BundleFile that uses a ZipFile as it base file. |
| * @since 3.2 |
| */ |
| public class ZipBundleFile extends BundleFile { |
| |
| private final MRUBundleFileList mruList; |
| /** |
| * The bundle data |
| */ |
| protected BaseData bundledata; |
| /** |
| * The zip file |
| */ |
| protected volatile ZipFile zipFile; |
| /** |
| * The closed flag |
| */ |
| protected volatile boolean closed = true; |
| |
| private int referenceCount = 0; |
| |
| /** |
| * Constructs a ZipBundle File |
| * @param basefile the base file |
| * @param bundledata the bundle data |
| * @throws IOException |
| */ |
| public ZipBundleFile(File basefile, BaseData bundledata) throws IOException { |
| this(basefile, bundledata, null); |
| } |
| |
| public ZipBundleFile(File basefile, BaseData bundledata, MRUBundleFileList mruList) throws IOException { |
| super(basefile); |
| if (!BundleFile.secureAction.exists(basefile)) |
| throw new IOException(NLS.bind(AdaptorMsg.ADAPTER_FILEEXIST_EXCEPTION, basefile)); |
| this.bundledata = bundledata; |
| this.closed = true; |
| this.mruList = mruList; |
| } |
| |
| /** |
| * Checks if the zip file is open |
| * @return true if the zip file is open |
| */ |
| protected boolean checkedOpen() { |
| try { |
| return getZipFile() != null; |
| } catch (IOException e) { |
| if (bundledata != null) |
| bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundledata.getBundle(), e); |
| return false; |
| } |
| } |
| |
| /** |
| * Opens the ZipFile for this bundle file |
| * @return an open ZipFile for this bundle file |
| * @throws IOException |
| */ |
| protected ZipFile basicOpen() throws IOException { |
| return BundleFile.secureAction.getZipFile(this.basefile); |
| } |
| |
| /** |
| * Returns an open ZipFile for this bundle file. If an open |
| * ZipFile does not exist then a new one is created and |
| * returned. |
| * @return an open ZipFile for this bundle |
| * @throws IOException |
| */ |
| protected synchronized ZipFile getZipFile() throws IOException { |
| if (closed) { |
| mruListAdd(); |
| zipFile = basicOpen(); |
| closed = false; |
| } else |
| mruListUse(); |
| return zipFile; |
| } |
| |
| /** |
| * Returns a ZipEntry for the bundle file. Must be called while synchronizing on this object. |
| * This method does not ensure that the ZipFile is opened. Callers may need to call getZipfile() prior to calling this |
| * method. |
| * @param path the path to an entry |
| * @return a ZipEntry or null if the entry does not exist |
| */ |
| protected ZipEntry getZipEntry(String path) { |
| if (path.length() > 0 && path.charAt(0) == '/') |
| path = path.substring(1); |
| ZipEntry entry = zipFile.getEntry(path); |
| if (entry != null && entry.getSize() == 0 && !entry.isDirectory()) { |
| // work around the directory bug see bug 83542 |
| ZipEntry dirEntry = zipFile.getEntry(path + '/'); |
| if (dirEntry != null) |
| entry = dirEntry; |
| } |
| return entry; |
| } |
| |
| /** |
| * Extracts a directory and all sub content to disk |
| * @param dirName the directory name to extract |
| * @return the File used to extract the content to. A value |
| * of <code>null</code> is returned if the directory to extract does |
| * not exist or if content extraction is not supported. |
| */ |
| protected synchronized File extractDirectory(String dirName) { |
| if (!checkedOpen()) |
| return null; |
| Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
| while (entries.hasMoreElements()) { |
| String entryPath = entries.nextElement().getName(); |
| if (entryPath.startsWith(dirName) && !entryPath.endsWith("/")) //$NON-NLS-1$ |
| getFile(entryPath, false); |
| } |
| return getExtractFile(dirName); |
| } |
| |
| protected File getExtractFile(String entryName) { |
| if (bundledata == null) |
| return null; |
| String path = ".cp"; /* put all these entries in this subdir *///$NON-NLS-1$ |
| String name = entryName.replace('/', File.separatorChar); |
| if ((name.length() > 1) && (name.charAt(0) == File.separatorChar)) /* if name has a leading slash */ |
| path = path.concat(name); |
| else |
| path = path + File.separator + name; |
| return bundledata.getExtractFile(path); |
| } |
| |
| public synchronized File getFile(String entry, boolean nativeCode) { |
| if (!checkedOpen()) |
| return null; |
| ZipEntry zipEntry = getZipEntry(entry); |
| if (zipEntry == null) |
| return null; |
| |
| try { |
| File nested = getExtractFile(zipEntry.getName()); |
| if (nested != null) { |
| if (nested.exists()) { |
| /* the entry is already cached */ |
| if (Debug.DEBUG_GENERAL) |
| Debug.println("File already present: " + nested.getPath()); //$NON-NLS-1$ |
| if (nested.isDirectory()) |
| // must ensure the complete directory is extracted (bug 182585) |
| extractDirectory(zipEntry.getName()); |
| } else { |
| if (zipEntry.getName().endsWith("/")) { //$NON-NLS-1$ |
| if (!nested.mkdirs()) { |
| if (Debug.DEBUG_GENERAL) |
| Debug.println("Unable to create directory: " + nested.getPath()); //$NON-NLS-1$ |
| throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, nested.getAbsolutePath())); |
| } |
| extractDirectory(zipEntry.getName()); |
| } else { |
| InputStream in = zipFile.getInputStream(zipEntry); |
| if (in == null) |
| return null; |
| /* the entry has not been cached */ |
| if (Debug.DEBUG_GENERAL) |
| Debug.println("Creating file: " + nested.getPath()); //$NON-NLS-1$ |
| /* create the necessary directories */ |
| File dir = new File(nested.getParent()); |
| if (!dir.exists() && !dir.mkdirs()) { |
| if (Debug.DEBUG_GENERAL) |
| Debug.println("Unable to create directory: " + dir.getPath()); //$NON-NLS-1$ |
| throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, dir.getAbsolutePath())); |
| } |
| /* copy the entry to the cache */ |
| AdaptorUtil.readFile(in, nested); |
| if (nativeCode) |
| setPermissions(nested); |
| } |
| } |
| |
| return nested; |
| } |
| } catch (IOException e) { |
| if (Debug.DEBUG_GENERAL) |
| Debug.printStackTrace(e); |
| } |
| return null; |
| } |
| |
| public synchronized boolean containsDir(String dir) { |
| if (!checkedOpen()) |
| return false; |
| if (dir == null) |
| return false; |
| |
| if (dir.length() == 0) |
| return true; |
| |
| if (dir.charAt(0) == '/') { |
| if (dir.length() == 1) |
| return true; |
| dir = dir.substring(1); |
| } |
| |
| if (dir.length() > 0 && dir.charAt(dir.length() - 1) != '/') |
| dir = dir + '/'; |
| |
| Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
| ZipEntry zipEntry; |
| String entryPath; |
| while (entries.hasMoreElements()) { |
| zipEntry = entries.nextElement(); |
| entryPath = zipEntry.getName(); |
| if (entryPath.startsWith(dir)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public synchronized BundleEntry getEntry(String path) { |
| if (!checkedOpen()) |
| return null; |
| ZipEntry zipEntry = getZipEntry(path); |
| if (zipEntry == null) { |
| if (path.length() == 0 || path.charAt(path.length() - 1) == '/') { |
| // this is a directory request lets see if any entries exist in this directory |
| if (containsDir(path)) |
| return new DirZipBundleEntry(this, path); |
| } |
| return null; |
| } |
| |
| return new ZipBundleEntry(zipEntry, this); |
| |
| } |
| |
| public synchronized Enumeration<String> getEntryPaths(String path) { |
| // Get entry paths. Recurse or not based on caller's thread local |
| // request. |
| Enumeration<String> result = getEntryPaths(path, ListEntryPathsThreadLocal.isRecursive()); |
| // Always set the thread local back to its default value. If the caller |
| // requested recursion, this will indicate that recursion was done. |
| // Otherwise, no harm is done. |
| ListEntryPathsThreadLocal.setRecursive(false); |
| return result; |
| } |
| |
| // Optimized method allowing this zip bundle file to recursively return |
| // entry paths when requested. |
| public synchronized Enumeration<String> getEntryPaths(String path, boolean doRecurse) { |
| if (path == null) |
| throw new NullPointerException(); |
| // Is the zip file already open or, if not, can it be opened? |
| if (!checkedOpen()) |
| return null; |
| |
| // Strip any leading '/' off of path. |
| if (path.length() > 0 && path.charAt(0) == '/') |
| path = path.substring(1); |
| // Append a '/', if not already there, to path if not an empty string. |
| if (path.length() > 0 && path.charAt(path.length() - 1) != '/') |
| path = new StringBuilder(path).append("/").toString(); //$NON-NLS-1$ |
| |
| LinkedHashSet<String> result = new LinkedHashSet<String>(); |
| // Get all zip file entries and add the ones of interest. |
| Enumeration<? extends ZipEntry> entries = zipFile.entries(); |
| while (entries.hasMoreElements()) { |
| ZipEntry zipEntry = entries.nextElement(); |
| String entryPath = zipEntry.getName(); |
| // Is the entry of possible interest? Note that |
| // string.startsWith("") == true. |
| if (entryPath.startsWith(path)) { |
| // If we get here, we know that the entry is either (1) equal to |
| // path, (2) a file under path, or (3) a subdirectory of path. |
| if (path.length() < entryPath.length()) { |
| // If we get here, we know that entry is not equal to path. |
| getEntryPaths(path, entryPath.substring(path.length()), doRecurse, result); |
| } |
| } |
| } |
| return result.size() == 0 ? null : Collections.enumeration(result); |
| } |
| |
| /** |
| * Process the given entry by appending it to path and adding the full path |
| * to entries. If recursive, sub-paths of entry will also be processed. |
| * |
| * For example, given the following parameters: |
| * |
| * path = com/ |
| * entry = foo/bar/X.class |
| * doRecurse = false |
| * |
| * com/foo/ will be added to entries and the method will return. |
| * |
| * If, instead, doRecurse equals true, the following will be added to |
| * entries before returning: |
| * |
| * com/foo/ |
| * com/foo/bar/ |
| * com/foo/bar/X.class |
| * |
| * @param path - The requested or already processed path. On the first call |
| * to this method, this will be the path requested by the |
| * caller of {@link #getEntryPaths(String, boolean)}. On |
| * subsequent, recursive calls, this will be the portion of |
| * the path already processed. |
| * @param entry - The entry underneath path to process. |
| * @param doRecurse - If true, process all path segments under entry |
| * recursively. If false, process only the first path |
| * segment in entry. |
| * @param entries - The set of processed entries. |
| */ |
| private void getEntryPaths(String path, String entry, boolean doRecurse, LinkedHashSet<String> entries) { |
| if (entry.length() == 0) // Terminating condition. |
| // The previous entry was a directory with no files. |
| return; |
| int slash = entry.indexOf('/'); |
| if (slash == -1) // Terminating condition. |
| // The entry is a file so nothing follows. Add its full path and |
| // return. |
| entries.add(path + entry); |
| else { |
| // Append the entry to the path to track the full path for recursion. |
| path = path + entry.substring(0, slash + 1); |
| // Add the full entry path. |
| entries.add(path); |
| if (doRecurse) |
| // Recurse with the updated path plus the next path segment of |
| // entry. |
| getEntryPaths(path, entry.substring(slash + 1), true, entries); |
| } |
| } |
| |
| public synchronized void close() throws IOException { |
| if (!closed) { |
| if (referenceCount > 0 && isMruListClosing()) { |
| // there are some opened streams to this BundleFile still; |
| // wait for them all to close because this is being closed by the MRUBundleFileList |
| try { |
| wait(1000); // timeout after 1 second |
| } catch (InterruptedException e) { |
| // do nothing for now ... |
| } |
| if (referenceCount != 0 || closed) |
| // either another thread closed the bundle file or we timed waiting for all the reference inputstreams to close |
| // If the referenceCount did not reach zero then this bundle file will remain open until the |
| // bundle file is closed explicitly (i.e. bundle is updated/uninstalled or framework is shutdown) |
| return; |
| |
| } |
| closed = true; |
| zipFile.close(); |
| mruListRemove(); |
| } |
| } |
| |
| private boolean isMruListClosing() { |
| return this.mruList != null && this.mruList.isClosing(this); |
| } |
| |
| boolean isMruEnabled() { |
| return this.mruList != null && this.mruList.isEnabled(); |
| } |
| |
| private void mruListRemove() { |
| if (this.mruList != null) { |
| this.mruList.remove(this); |
| } |
| } |
| |
| private void mruListUse() { |
| if (this.mruList != null) { |
| mruList.use(this); |
| } |
| } |
| |
| private void mruListAdd() { |
| if (this.mruList != null) { |
| mruList.add(this); |
| } |
| } |
| |
| public void open() { |
| //do nothing |
| } |
| |
| synchronized void incrementReference() { |
| referenceCount += 1; |
| } |
| |
| synchronized void decrementReference() { |
| referenceCount = Math.max(0, referenceCount - 1); |
| // only notify if the referenceCount is zero. |
| if (referenceCount == 0) |
| notify(); |
| } |
| } |