Bug 528422 - [osgi R7] Support for Multi-Release jars

Add check to prevent versioning of files under META-INF

Change-Id: Ibb7afa65e0da75c012ec024cd0d79fa774cf9f51
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestService.java b/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestService.java
new file mode 100644
index 0000000..e945202
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestService.java
@@ -0,0 +1,15 @@
+/*******************************************************************************
+ * Copyright (c) 2017 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 multi.release.test;
+
+public interface TestService {
+	// Just a marker
+}
diff --git a/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestService9.java b/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestService9.java
new file mode 100644
index 0000000..af5512f
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestService9.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2017 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 multi.release.test;
+
+public class TestService9 implements TestService {
+	public String toString() {
+		return "SERVICE_9";
+	}
+}
diff --git a/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestServiceBase.java b/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestServiceBase.java
new file mode 100644
index 0000000..bd3dced
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/bundles_src/mrBundleInputBase/multi/release/test/TestServiceBase.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2017 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 multi.release.test;
+
+public class TestServiceBase implements TestService {
+	public String toString() {
+		return "SERVICE_BASE";
+	}
+}
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/MultiReleaseJarTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/MultiReleaseJarTests.java
index 4f774b0..0b075ae 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/MultiReleaseJarTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/MultiReleaseJarTests.java
@@ -25,6 +25,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
@@ -99,12 +100,18 @@
 		bundleEntries.put("multi/release/test/TestClass9.class", getBytes("multi/release/test/TestClass9.class", base));
 		bundleEntries.put("multi/release/test/TestClass10.class", getBytes("multi/release/test/TestClass10.class", base));
 		bundleEntries.put("multi/release/test/TestClass11.class", getBytes("multi/release/test/TestClass11.class", base));
+		bundleEntries.put("multi/release/test/TestService.class", getBytes("multi/release/test/TestService.class", base));
+		bundleEntries.put("multi/release/test/TestService9.class", getBytes("multi/release/test/TestService9.class", base));
+		bundleEntries.put("multi/release/test/TestServiceBase.class", getBytes("multi/release/test/TestServiceBase.class", base));
+
 		bundleEntries.put("multi/release/test/testResourceBase.txt", getBytes("multi/release/test/testResourceBase.txt", base));
 		bundleEntries.put("multi/release/test/testResource8.txt", getBytes("multi/release/test/testResource8.txt", base));
 		bundleEntries.put("multi/release/test/testResource9.txt", getBytes("multi/release/test/testResource9.txt", base));
 		bundleEntries.put("multi/release/test/testResource10.txt", getBytes("multi/release/test/testResource10.txt", base));
 		bundleEntries.put("multi/release/test/testResource11.txt", getBytes("multi/release/test/testResource11.txt", base));
 
+		bundleEntries.put("META-INF/services/", null);
+		bundleEntries.put("META-INF/services/multi.release.test.TestService", "multi.release.test.TestServiceBase".getBytes("UTF-8"));
 		bundleEntries.put("META-INF/versions/", null);
 		bundleEntries.put("META-INF/versions/8/", null);
 		bundleEntries.put("META-INF/versions/8/multi/", null);
@@ -115,6 +122,12 @@
 		bundleEntries.put("META-INF/versions/8/multi/release/test/testResource8.txt", getBytes("multi/release/test/testResource8.txt", base, new byte[] {'0', '8'}));
 		bundleEntries.put("META-INF/versions/8/multi/release/test/testResourceAdd8.txt", getBytes("multi/release/test/testResourceAdd8.txt", base));
 		bundleEntries.put("META-INF/versions/9/", null);
+		bundleEntries.put("META-INF/versions/9/META-INF/", null);
+		bundleEntries.put("META-INF/versions/9/META-INF/addedFor9.txt", "added for 9".getBytes("UTF-8"));
+		bundleEntries.put("META-INF/versions/9/META-INF/addedDirFor9/", null);
+		bundleEntries.put("META-INF/versions/9/META-INF/addedDirFor9/addedFor9.txt", "added for 9".getBytes("UTF-8"));
+		bundleEntries.put("META-INF/versions/9/META-INF/services/", null);
+		bundleEntries.put("META-INF/versions/9/META-INF/services/multi.release.test.TestService", "multi.release.test.TestService9".getBytes("UTF-8"));
 		bundleEntries.put("META-INF/versions/9/multi/", null);
 		bundleEntries.put("META-INF/versions/9/multi/release/", null);
 		bundleEntries.put("META-INF/versions/9/multi/release/test/", null);
@@ -862,4 +875,76 @@
 		}
 	}
 
+	public void testMultiReleasePreventMetaInfServiceVersions() throws Exception {
+		System.setProperty("java.specification.version", "9");
+
+		File config = OSGiTestsActivator.getContext().getDataFile(getName()); //$NON-NLS-1$
+		Map<String, String> configMap = Collections.singletonMap(Constants.FRAMEWORK_STORAGE, config.getAbsolutePath());
+		Equinox equinox = new Equinox(configMap);
+
+		try {
+			equinox.start();
+			BundleContext systemContext = equinox.getBundleContext();
+			Bundle mrBundle = systemContext.installBundle("reference:" + mrJarBundle.toURI().toString());
+			mrBundle.start();
+
+			Class<?> testServiceClass = mrBundle.loadClass("multi.release.test.TestService");
+			ServiceLoader<?> loader = ServiceLoader.load(testServiceClass, mrBundle.adapt(BundleWiring.class).getClassLoader());
+			Object testService = loader.iterator().next();
+			assertEquals("Wrong service found.", "SERVICE_BASE", testService.toString());
+		} finally {
+			equinox.stop();
+			equinox.waitForStop(1000);
+		}
+	}
+
+	public void testMultiReleasePreventMetaInfResourceURLs() throws Exception {
+		System.setProperty("java.specification.version", "9");
+
+		File config = OSGiTestsActivator.getContext().getDataFile(getName()); //$NON-NLS-1$
+		Map<String, String> configMap = Collections.singletonMap(Constants.FRAMEWORK_STORAGE, config.getAbsolutePath());
+		Equinox equinox = new Equinox(configMap);
+
+		try {
+			equinox.start();
+			BundleContext systemContext = equinox.getBundleContext();
+			Bundle mrBundle = systemContext.installBundle("reference:" + mrJarBundle.toURI().toString());
+			mrBundle.start();
+
+			URL existingResource = mrBundle.getResource("multi/release/test/testResourceAdd9.txt");
+			assertNotNull("Did not find Java 9 added resource.", existingResource);
+			URL metaInfResource = new URL(existingResource, "/META-INF/addedFor9.txt");
+			try {
+				metaInfResource.openStream().close();
+				fail("Expected error opening versioned META-INF resource.");
+			} catch (IOException e) {
+				// expected
+			}
+
+		} finally {
+			equinox.stop();
+			equinox.waitForStop(1000);
+		}
+	}
+
+	public void testMultiReleasePreventMetaInfVersionListing() throws Exception {
+		System.setProperty("java.specification.version", "9");
+
+		File config = OSGiTestsActivator.getContext().getDataFile(getName()); //$NON-NLS-1$
+		Map<String, String> configMap = Collections.singletonMap(Constants.FRAMEWORK_STORAGE, config.getAbsolutePath());
+		Equinox equinox = new Equinox(configMap);
+
+		try {
+			equinox.start();
+			BundleContext systemContext = equinox.getBundleContext();
+			Bundle mrBundle = systemContext.installBundle("reference:" + mrJarBundle.toURI().toString());
+			mrBundle.start();
+
+			Collection<String> list = mrBundle.adapt(BundleWiring.class).listResources("/META-INF/", "*.txt", 0);
+			assertTrue("Found versioned META-INF resources: " + list, list.isEmpty());
+		} finally {
+			equinox.stop();
+			equinox.waitForStop(1000);
+		}
+	}
 }
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathEntry.java
index 09ceba1..d627a49 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathEntry.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathEntry.java
@@ -102,10 +102,10 @@
 		}
 		List<BundleFile> mrBundleFiles = new ArrayList<>();
 		for (int i = storage.getRuntimeVersion().getMajor(); i > 8; i--) {
-			String versionPath = "META-INF/versions/" + i + '/'; //$NON-NLS-1$
+			String versionPath = BundleInfo.MULTI_RELEASE_VERSIONS + i + '/';
 			BundleEntry versionEntry = bundlefile.getEntry(versionPath);
 			if (versionEntry != null) {
-				mrBundleFiles.add(storage.createNestedBundleFile(versionPath, bundlefile, generation));
+				mrBundleFiles.add(storage.createNestedBundleFile(versionPath, bundlefile, generation, BundleInfo.MULTI_RELEASE_FILTER_PREFIXES));
 			}
 		}
 		return Collections.unmodifiableList(mrBundleFiles);
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java
index 286bcb7..e4ebfd8 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/BundleInfo.java
@@ -53,6 +53,8 @@
 public final class BundleInfo {
 	public static final String OSGI_BUNDLE_MANIFEST = "META-INF/MANIFEST.MF"; //$NON-NLS-1$
 	public static final String MULTI_RELEASE_HEADER = "Multi-Release"; //$NON-NLS-1$
+	public static final String MULTI_RELEASE_VERSIONS = "META-INF/versions/"; //$NON-NLS-1$
+	public static final Collection<String> MULTI_RELEASE_FILTER_PREFIXES = Collections.singleton("META-INF/"); //$NON-NLS-1$
 
 	public final class Generation {
 		private final long generationId;
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
index 4a1156a..5e759f0 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
@@ -1045,8 +1045,12 @@
 	}
 
 	public BundleFile createNestedBundleFile(String nestedDir, BundleFile bundleFile, Generation generation) {
+		return createNestedBundleFile(nestedDir, bundleFile, generation, Collections.<String> emptyList());
+	}
+
+	public BundleFile createNestedBundleFile(String nestedDir, BundleFile bundleFile, Generation generation, Collection<String> filterPrefixes) {
 		// here we assume the content is a path offset into the base bundle file;  create a NestedDirBundleFile
-		return wrapBundleFile(new NestedDirBundleFile(bundleFile, nestedDir), generation, false);
+		return wrapBundleFile(new NestedDirBundleFile(bundleFile, nestedDir, filterPrefixes), generation, false);
 	}
 
 	public BundleFile wrapBundleFile(BundleFile bundleFile, Generation generation, boolean isBase) {
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/NestedDirBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/NestedDirBundleFile.java
index 7184414..8422952 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/NestedDirBundleFile.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/NestedDirBundleFile.java
@@ -13,6 +13,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Enumeration;
 
 /**
@@ -28,46 +30,87 @@
  */
 public class NestedDirBundleFile extends BundleFile {
 	private final BundleFile baseBundleFile;
-	private final String cp;
+	private final String nestedDirName;
+	private final Collection<String> filterPrefixes;
 
 	/**
 	 * Constructs a NestedDirBundleFile
 	 * @param baseBundlefile the base bundle file
-	 * @param cp
+	 * @param nestedDirName
 	 */
-	public NestedDirBundleFile(BundleFile baseBundlefile, String cp) {
+	public NestedDirBundleFile(BundleFile baseBundlefile, String nestedDirName) {
+		this(baseBundlefile, nestedDirName, Collections.<String> emptyList());
+	}
+
+	/**
+	 * Constructs a NestedDirBundleFile
+	 * @param baseBundlefile the base bundle file
+	 * @param nestedDirName
+	 * @param filterPrefixes the prefixes to filter out for the bundle file
+	 */
+	public NestedDirBundleFile(BundleFile baseBundlefile, String nestedDirName, Collection<String> filterPrefixes) {
 		super(baseBundlefile.getBaseFile());
 		this.baseBundleFile = baseBundlefile;
-		if (cp.charAt(cp.length() - 1) != '/') {
-			cp = cp + '/';
+		if (nestedDirName.charAt(nestedDirName.length() - 1) != '/') {
+			nestedDirName = nestedDirName + '/';
 		}
-		this.cp = cp;
+		this.nestedDirName = nestedDirName;
+		this.filterPrefixes = filterPrefixes;
 	}
 
 	public void close() {
 		// do nothing.
 	}
 
+	private boolean filterPath(String path) {
+		if (path.length() > 0 && path.charAt(0) == '/')
+			path = path.substring(1);
+		for (String prefix : filterPrefixes) {
+			if (path.startsWith(prefix)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private boolean filterDir(String path) {
+		if (filterPrefixes.isEmpty()) {
+			return false;
+		}
+		if (path.length() > 0 && path.charAt(path.length() - 1) != '/') {
+			path = path + '/';
+		}
+		return filterPath(path);
+	}
+
 	public BundleEntry getEntry(String path) {
+		if (filterPath(path)) {
+			return null;
+		}
 		return baseBundleFile.getEntry(prependNestedDir(path));
 	}
 
 	public boolean containsDir(String dir) {
 		if (dir == null)
 			return false;
-
+		if (filterPath(dir)) {
+			return false;
+		}
 		return baseBundleFile.containsDir(prependNestedDir(dir));
 	}
 
 	private String prependNestedDir(String path) {
 		if (path.length() > 0 && path.charAt(0) == '/')
 			path = path.substring(1);
-		return new StringBuffer(cp).append(path).toString();
+		return new StringBuffer(nestedDirName).append(path).toString();
 	}
 
 	public Enumeration<String> getEntryPaths(String path, boolean recurse) {
+		if (filterDir(path)) {
+			return null;
+		}
 		final Enumeration<String> basePaths = baseBundleFile.getEntryPaths(prependNestedDir(path), recurse);
-		final int cpLength = cp.length();
+		final int cpLength = nestedDirName.length();
 		if (basePaths == null)
 			return null;
 		return new Enumeration<String>() {