From aa6a7a8049cfe5089b7307c3a815e16b15d5f0b2 Mon Sep 17 00:00:00 2001 From: John Ross Date: Mon, 23 Sep 2013 15:26:38 -0500 Subject: Bug 411877 - BundleWiring.listResources does not work properly in dev mode --- .../osgi/internal/hookregistry/HookRegistry.java | 8 +- .../osgi/internal/hooks/DevBundleFileWrapper.java | 212 +++++++++++++++++++++ .../hooks/DevBundleFileWrapperFactoryHook.java | 39 ++++ .../osgi/internal/hooks/DevClassLoadingHook.java | 26 ++- .../loader/classpath/ClasspathManager.java | 10 +- .../osgi/storage/bundlefile/BundleFileWrapper.java | 5 - 6 files changed, 286 insertions(+), 14 deletions(-) create mode 100755 bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapper.java create mode 100755 bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapperFactoryHook.java diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java index 19354f7a1..4b0bb17b3 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java @@ -18,8 +18,7 @@ import java.util.*; import org.eclipse.osgi.framework.log.FrameworkLogEntry; import org.eclipse.osgi.internal.framework.EquinoxConfiguration; import org.eclipse.osgi.internal.framework.EquinoxContainer; -import org.eclipse.osgi.internal.hooks.DevClassLoadingHook; -import org.eclipse.osgi.internal.hooks.EclipseLazyStarter; +import org.eclipse.osgi.internal.hooks.*; import org.eclipse.osgi.internal.signedcontent.SignedBundleHook; import org.eclipse.osgi.internal.weaving.WeavingHookConfigurator; import org.eclipse.osgi.util.ManifestElement; @@ -99,11 +98,14 @@ public final class HookRegistry { mergeFileHookConfigurators(configurators, errors); mergePropertyHookConfigurators(configurators); synchronized (this) { - addClassLoaderHook(new DevClassLoadingHook(container.getConfiguration())); + EquinoxConfiguration configuration = container.getConfiguration(); + addClassLoaderHook(new DevClassLoadingHook(configuration)); addClassLoaderHook(new EclipseLazyStarter(container)); addClassLoaderHook(new WeavingHookConfigurator(container)); configurators.add(SignedBundleHook.class.getName()); loadConfigurators(configurators, errors); + if (configuration.inDevelopmentMode()) + addBundleFileWrapperFactoryHook(new DevBundleFileWrapperFactoryHook(configuration)); // set to read-only initialized = true; } diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapper.java new file mode 100755 index 000000000..2bb763d71 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapper.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ +package org.eclipse.osgi.internal.hooks; + +import java.io.File; +import java.util.*; +import org.eclipse.osgi.internal.framework.EquinoxConfiguration; +import org.eclipse.osgi.internal.loader.classpath.ClasspathManager; +import org.eclipse.osgi.storage.BundleInfo.Generation; +import org.eclipse.osgi.storage.bundlefile.*; +import org.osgi.framework.wiring.BundleRevision; + +/* + * BundleFileWrapper.getFile(String, boolean) is not overridden because only + * the base bundle file should be called for this method, which the superclass + * already does. + */ +public class DevBundleFileWrapper extends BundleFileWrapper { + private final EquinoxConfiguration configuration; + private final Generation generation; + + private volatile Collection devClasspath; + private volatile Collection nested; + + public DevBundleFileWrapper(BundleFile bundleFile, Generation generation, EquinoxConfiguration configuration) { + super(bundleFile); + if (generation == null || configuration == null) + throw new NullPointerException(); + this.generation = generation; + this.configuration = configuration; + + } + + @Override + public boolean containsDir(String dir) { + if (!isRootOnBundleClasspath()) + return getBundleFile().containsDir(dir); + if (getBundleFile().containsDir(dir)) + return true; + for (NestedDirBundleFile bundleFile : getNestedDirBundleFiles()) + if (bundleFile.containsDir(dir)) + return true; + return false; + } + + @Override + public BundleEntry getEntry(String path) { + if (!isRootOnBundleClasspath()) { + if (isPathOnDevelopmentClasspath(path)) + return null; + return getBundleFile().getEntry(path); + } + if (isPathOnDevelopmentClasspath(path)) + return getEntryFromNestedDirBundleFiles(path); + return getEntryFromBundleFile(path); + } + + @Override + public Enumeration getEntryPaths(String path, boolean recurse) { + Collection result = new LinkedHashSet(); + if (!isRootOnBundleClasspath()) { + if (!isPathOnDevelopmentClasspath(path)) + addEntryPathsFromBundleFile(path, recurse, result); + } else if (isPathOnDevelopmentClasspath(path)) { + addEntryPathsFromNestedDirBundleFiles(path, recurse, result); + } else { + addEntryPathsFromBundleFile(path, recurse, result); + addEntryPathsFromNestedDirBundleFiles(path, recurse, result); + } + return result.isEmpty() ? null : Collections.enumeration(result); + } + + @Override + public File getFile(String path, boolean nativeCode) { + return getBundleFile().getFile(path, nativeCode); + } + + private Collection getNestedDirBundleFiles() { + Collection result = nested; + if (result == null) { + synchronized (this) { + if (result == null) { + Collection ss = getDevClasspath(); + if (ss == null) + return Collections.emptyList(); + result = nested = new HashSet(ss.size()); + for (String s : ss) + // Don't pass 'this' to NestedDirBundleFile or an + // infinite loop results. + result.add(new NestedDirBundleFile(getBundleFile(), s)); + } + } + } + return result; + } + + private Collection getDevClasspath() { + Collection result = devClasspath; + if (result == null) { + synchronized (this) { + if (result == null) { + BundleRevision revision = generation.getRevision(); + if (revision == null) + return null; + String[] ss = configuration.getDevClassPath(revision.getSymbolicName()); + if (ss == null || ss.length == 0) + result = devClasspath = Collections.emptyList(); + else { + result = devClasspath = new LinkedHashSet(ss.length); + for (String s : ss) + if (!s.contains("..")) //$NON-NLS-1$ + result.add(s); + } + } + } + } + return result; + } + + private BundleEntry getEntryFromBundleFile(String path) { + return getBundleFile().getEntry(path); + } + + private BundleEntry getEntryFromNestedDirBundleFiles(String path) { + for (NestedDirBundleFile f : getNestedDirBundleFiles()) { + BundleEntry entry = f.getEntry(path); + if (entry != null) + return entry; + } + return null; + } + + private void addEntryPathsFromBundleFile(String path, boolean recurse, Collection entryPaths) { + Enumeration e = getBundleFile().getEntryPaths(path, recurse); + if (e == null) + return; + while (e.hasMoreElements()) { + String s = strip(e.nextElement()); + if (s.length() > 0) + entryPaths.add(s); + } + } + + private void addEntryPathsFromNestedDirBundleFiles(String path, boolean recurse, Collection entryPaths) { + for (NestedDirBundleFile f : getNestedDirBundleFiles()) { + Enumeration e = f.getEntryPaths(path, recurse); + if (e != null) + entryPaths.addAll(Collections.list(e)); + } + } + + private String strip(String path) { + for (String s : getDevClasspath()) + if (path.startsWith(s)) + return path.substring(s.length() + 1); + return path; + } + + private boolean isRootOnBundleClasspath() { + BundleRevision revision = generation.getRevision(); + if (revision == null) + return true; + return Arrays.asList(ClasspathManager.getClassPath(revision)).contains("."); //$NON-NLS-1$ + } + + private String findMatchingPathOnDevelopmentClasspath(String pathToMatch) { + return findMatchingPathOnDevelopmentClasspath(pathToMatch, getDevClasspath(), getBundleFile()); + } + + private boolean isPathOnDevelopmentClasspath(String path) { + return findMatchingPathOnDevelopmentClasspath(path) != null; + } + + static boolean isRoot(String path) { + return "/".equals(path) || ".".equals(path); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private static String findMatchingPathOnDevelopmentClasspath(String pathToMatch, Collection devClasspath, BundleFile bundleFile) { + return findMatchingPathOnDevelopmentClasspath(pathToMatch, devClasspath == null ? new String[0] : devClasspath.toArray(new String[devClasspath.size()]), bundleFile); + } + + static String findMatchingPathOnDevelopmentClasspath(String pathToMatch, String[] devClasspath, BundleFile bundleFile) { + if (isRoot(pathToMatch)) + return null; + // For each element on the development classpath... + for (String path : devClasspath) { + // ...see if the bundle file has a matching entry... + BundleEntry entry = bundleFile.getEntry(path); + if (entry == null) + // ...if not, then the element is of no interest. + continue; + // ...if so, then see if the entry contains the given path... + String name = entry.getName(); + if (pathToMatch.startsWith(name) || name.startsWith(pathToMatch)) + // ...if it does, we have our match. + return name; + // ...if prepending the path to match with the entry name find an + // entry, then we have our match. + if (bundleFile.getEntry(name + pathToMatch) != null) + return name; + } + return null; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapperFactoryHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapperFactoryHook.java new file mode 100755 index 000000000..3b83561bf --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevBundleFileWrapperFactoryHook.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ +package org.eclipse.osgi.internal.hooks; + +import org.eclipse.osgi.internal.framework.EquinoxConfiguration; +import org.eclipse.osgi.internal.hookregistry.BundleFileWrapperFactoryHook; +import org.eclipse.osgi.storage.BundleInfo.Generation; +import org.eclipse.osgi.storage.bundlefile.BundleFile; +import org.eclipse.osgi.storage.bundlefile.BundleFileWrapper; + +/* + * Returns DevBundleFileWrapper objects for base bundle files that are + * directories when in development mode. Ideally, this hook should not be + * registered if not in development mode. + */ +public class DevBundleFileWrapperFactoryHook implements BundleFileWrapperFactoryHook { + private EquinoxConfiguration configuration; + + public DevBundleFileWrapperFactoryHook(EquinoxConfiguration configuration) { + if (configuration == null) + throw new NullPointerException(); + this.configuration = configuration; + } + + @Override + public BundleFileWrapper wrapBundleFile(BundleFile bundleFile, Generation generation, boolean base) { + if (base && configuration.inDevelopmentMode() && bundleFile.getBaseFile().isDirectory()) + return new DevBundleFileWrapper(bundleFile, generation, configuration); + return null; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevClassLoadingHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevClassLoadingHook.java index d936f4ef6..514278d05 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevClassLoadingHook.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hooks/DevClassLoadingHook.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2012 IBM Corporation and others. + * Copyright (c) 2006, 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 @@ -19,6 +19,7 @@ import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook; import org.eclipse.osgi.internal.loader.classpath.*; import org.eclipse.osgi.storage.BundleInfo.Generation; import org.eclipse.osgi.storage.bundlefile.BundleFile; +import org.osgi.framework.wiring.BundleRevision; public class DevClassLoadingHook extends ClassLoaderHook implements KeyedElement { public static final String KEY = DevClassLoadingHook.class.getName(); @@ -42,6 +43,12 @@ public class DevClassLoadingHook extends ClassLoaderHook implements KeyedElement return false; // this source has already had its dev classpath entries added. boolean result = false; for (int i = 0; i < devClassPath.length; i++) { + BundleFile bundleFile = sourceGeneration.getBundleFile(); + // If the bundle file is a directory and contains the classpath as an + // entry, don't add a classpath entry. The bundle file will do the work + // itself. See bug 411877. + if (!isClasspathEntryRequired(cp, sourceGeneration, devClassPath[i])) + continue; if (hostmanager.addClassPathEntry(cpEntries, devClassPath[i], hostmanager, sourceGeneration)) result = true; else { @@ -49,7 +56,7 @@ public class DevClassLoadingHook extends ClassLoaderHook implements KeyedElement boolean fromFragment = devCP.endsWith(FRAGMENT); if (!fromFragment && devCP.indexOf("..") >= 0) { //$NON-NLS-1$ // if in dev mode, try using cp as a relative path from the base bundle file - File base = sourceGeneration.getBundleFile().getBaseFile(); + File base = bundleFile.getBaseFile(); if (base.isDirectory()) { // this is only supported for directory bundles ClasspathEntry entry = hostmanager.getExternalClassPath(new File(base, devCP).getAbsolutePath(), sourceGeneration); @@ -109,4 +116,19 @@ public class DevClassLoadingHook extends ClassLoaderHook implements KeyedElement public int getKeyHashCode() { return HASHCODE; } + + private boolean isClasspathEntryRequired(String classpath, Generation generation, String devClasspath) { + if (!generation.getBundleFile().getBaseFile().isDirectory()) + return true; + if (devClasspath.contains("..")) //$NON-NLS-1$ + return true; + if (!isPathOnDevelopmentClasspath(classpath, generation) && !DevBundleFileWrapper.isRoot(classpath)) + return true; + return false; + } + + private boolean isPathOnDevelopmentClasspath(String path, Generation generation) { + BundleRevision revision = generation.getRevision(); + return DevBundleFileWrapper.findMatchingPathOnDevelopmentClasspath(path, configuration.getDevClassPath(revision.getSymbolicName()), generation.getBundleFile()) != null; + } } diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java index bb4ea8093..aaa91bf21 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java @@ -16,8 +16,8 @@ import java.net.URL; import java.util.*; import java.util.jar.Attributes; import java.util.jar.Manifest; -import org.eclipse.osgi.container.*; import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent; +import org.eclipse.osgi.container.*; import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace; import org.eclipse.osgi.framework.util.ArrayMap; import org.eclipse.osgi.internal.debug.Debug; @@ -33,6 +33,8 @@ import org.eclipse.osgi.storage.bundlefile.BundleFile; import org.eclipse.osgi.util.NLS; import org.osgi.framework.BundleException; import org.osgi.framework.namespace.HostNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Resource; /** * A helper class for BaseClassLoader implementations. This class will keep track of @@ -84,10 +86,10 @@ public class ClasspathManager { this.entries = buildClasspath(cp, this, this.generation); } - private static String[] getClassPath(ModuleRevision revision) { - List moduleDatas = revision.getModuleCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE); + public static String[] getClassPath(Resource resource) { + List capabilities = resource.getCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE); @SuppressWarnings("unchecked") - List cp = moduleDatas.isEmpty() ? null : (List) moduleDatas.get(0).getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_CLASSPATH); + List cp = capabilities.isEmpty() ? null : (List) capabilities.get(0).getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_CLASSPATH); return cp == null ? DEFAULT_CLASSPATH : cp.toArray(new String[cp.size()]); } diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/BundleFileWrapper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/BundleFileWrapper.java index 33f59380e..50ec4375a 100755 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/BundleFileWrapper.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/BundleFileWrapper.java @@ -50,11 +50,6 @@ public class BundleFileWrapper extends BundleFile { return bundleFile.getEntry(path); } - @Override - public Enumeration getEntryPaths(String path) { - return bundleFile.getEntryPaths(path); - } - @Override public Enumeration getEntryPaths(String path, boolean recurse) { return bundleFile.getEntryPaths(path, recurse); -- cgit v1.2.3