Bug 549895 - Ignore classes provided by the Java platform

Should not ask the factories if they can handle classes provided by the
JRE because they cannot and it causes recursion to occur in cases where
a classloader loading the embedded framework itself creates URLs from
URI objects.

Change-Id: I1ca9b0a8690a4eb295b624ded73698b2dc7afd07
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi.tests/.classpath b/bundles/org.eclipse.osgi.tests/.classpath
index 71ce997..2650588 100644
--- a/bundles/org.eclipse.osgi.tests/.classpath
+++ b/bundles/org.eclipse.osgi.tests/.classpath
@@ -124,6 +124,7 @@
 	<classpathentry kind="src" output="bundle_tests/test.bug449484" path="bundles_src/test.bug449484"/>
 	<classpathentry kind="src" output="bundle_tests/wrapper.hooks.a" path="bundles_src/wrapper.hooks.a"/>
 	<classpathentry kind="src" output="bundle_tests/test.protocol.handler" path="bundles_src/test.protocol.handler"/>
+	<classpathentry kind="src" output="bundle_tests/test.protocol.handler.user" path="bundles_src/test.protocol.handler.user"/>
 	<classpathentry kind="src" output="bundle_tests/test.bug471551" path="bundles_src/test.bug471551"/>
 	<classpathentry kind="src" output="bundle_tests/test.dynamicimport" path="bundles_src/test.dynamicimport"/>
 	<classpathentry kind="src" output="bundle_tests/test.bug490902.a" path="bundles_src/test.bug490902.a"/>
diff --git a/bundles/org.eclipse.osgi.tests/build.properties b/bundles/org.eclipse.osgi.tests/build.properties
index ad43e4e..b5f9ec7 100644
--- a/bundles/org.eclipse.osgi.tests/build.properties
+++ b/bundles/org.eclipse.osgi.tests/build.properties
@@ -263,6 +263,8 @@
 manifest.bundle_tests/wrapper.hooks.a.jar = META-INF/MANIFEST.MF
 source.bundle_tests/test.protocol.handler.jar = bundles_src/test.protocol.handler/
 manifest.bundle_tests/test.protocol.handler.jar = META-INF/MANIFEST.MF
+source.bundle_tests/test.protocol.handler.user.jar = bundles_src/test.protocol.handler.user/
+manifest.bundle_tests/test.protocol.handler.user.jar = META-INF/MANIFEST.MF
 source.bundle_tests/test.bug471551.jar = bundles_src/test.bug471551/
 manifest.bundle_tests/test.bug471551.jar = META-INF/MANIFEST.MF
 source.bundle_tests/test.dynamicimport.jar = bundles_src/test.dynamicimport/
@@ -403,6 +405,7 @@
                      bundle_tests/test.bug449484.jar,\
                      bundle_tests/wrapper.hooks.a.jar,\
                      bundle_tests/test.protocol.handler.jar,\
+                     bundle_tests/test.protocol.handler.user.jar,\
                      bundle_tests/test.bug471551.jar,\
                      bundle_tests/test.dynamicimport.jar,\
                      bundle_tests/test.bug490902.b.jar,\
diff --git a/bundles/org.eclipse.osgi.tests/bundles_src/test.protocol.handler.user/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi.tests/bundles_src/test.protocol.handler.user/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..2e05daf
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/bundles_src/test.protocol.handler.user/META-INF/MANIFEST.MF
@@ -0,0 +1,5 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: test.protocol.handler.user
+Bundle-Activator: test.protocol.handler.user.Activator
+Import-Package: org.osgi.framework
diff --git a/bundles/org.eclipse.osgi.tests/bundles_src/test.protocol.handler.user/test/protocol/handler/user/Activator.java b/bundles/org.eclipse.osgi.tests/bundles_src/test.protocol.handler.user/test/protocol/handler/user/Activator.java
new file mode 100644
index 0000000..fef905e
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/bundles_src/test.protocol.handler.user/test/protocol/handler/user/Activator.java
@@ -0,0 +1,21 @@
+package test.protocol.handler.user;
+
+import java.net.URL;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+	@Override
+	public void start(BundleContext context) throws Exception {
+		URL testURL = new URL("testing1://test");
+		testURL.openConnection();
+		System.out.println(testURL);
+	}
+
+	@Override
+	public void stop(BundleContext context) throws Exception {
+		// nothing
+	}
+
+}
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AbstractFrameworkHookTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AbstractFrameworkHookTests.java
index cdbaa3f..f7726f3 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AbstractFrameworkHookTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AbstractFrameworkHookTests.java
@@ -14,6 +14,9 @@
 package org.eclipse.osgi.tests.hooks.framework;
 
 import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Enumeration;
@@ -31,8 +34,11 @@
 
 public abstract class AbstractFrameworkHookTests extends CoreTest {
 	protected static class BasicURLClassLoader extends URLClassLoader {
-		public BasicURLClassLoader(URL[] urls, ClassLoader parent) {
+		private volatile String testURL;
+
+		public BasicURLClassLoader(URL[] urls, ClassLoader parent, String testURL) {
 			super(urls, parent);
+			this.testURL = testURL;
 		}
 
 		@Override
@@ -56,6 +62,16 @@
 
 		@Override
 		protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+			if (testURL != null) {
+				try {
+					URL fileUrl = new URI(testURL).toURL();
+					fileUrl.toString();
+				} catch (MalformedURLException | URISyntaxException e) {
+					// stop doing the URI creating
+					testURL = null;
+					throw new RuntimeException(e);
+				}
+			}
 			if (name.startsWith("org.eclipse") || name.startsWith("org.osgi.framework.FrameworkUtil")) {
 				Class<?> result = findLoadedClass(name);
 				if (result == null)
@@ -73,6 +89,7 @@
 	protected static final String BUNDLES_ROOT = "bundle_tests";
 
 	protected BasicURLClassLoader classLoader;
+	protected String testURL = null;
 	protected BundleInstaller bundleInstaller;
 
 	public BundleContext getContext() {
@@ -154,6 +171,6 @@
 			urls = new URL[] {new URL(osgiFramework), new URL(osgiFramework + "bin/")};
 		else
 			urls = new URL[] {new URL(osgiFramework)};
-		classLoader = new BasicURLClassLoader(urls, getClass().getClassLoader());
+		classLoader = new BasicURLClassLoader(urls, getClass().getClassLoader(), testURL);
 	}
 }
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AllFrameworkHookTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AllFrameworkHookTests.java
index 53880a5..d86f3ef 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AllFrameworkHookTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/AllFrameworkHookTests.java
@@ -24,6 +24,7 @@
 		suite.addTest(new TestSuite(BundleFileWrapperFactoryHookTests.class));
 		suite.addTest(new TestSuite(ContextFinderTests.class));
 		suite.addTest(new TestSuite(DevClassPathWithExtensionTests.class));
+		suite.addTest(new TestSuite(EmbeddedEquinoxWithURLInClassLoadTests.class));
 		return suite;
 	}
 }
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/EmbeddedEquinoxWithURLInClassLoadTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/EmbeddedEquinoxWithURLInClassLoadTests.java
new file mode 100644
index 0000000..c7c1c8c
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/hooks/framework/EmbeddedEquinoxWithURLInClassLoadTests.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2013, 2018 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.tests.hooks.framework;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.osgi.tests.OSGiTestsActivator;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+
+public class EmbeddedEquinoxWithURLInClassLoadTests extends AbstractFrameworkHookTests {
+
+	private Framework framework;
+
+	@Override
+	protected void setUp() throws Exception {
+		URL myManifest = getClass().getResource("/META-INF/MANIFEST.MF");
+		testURL = myManifest.toExternalForm();
+		super.setUp();
+		File file = OSGiTestsActivator.getContext().getDataFile(getName());
+		Map<String, String> configuration = new HashMap<String, String>();
+		configuration.put(Constants.FRAMEWORK_STORAGE, file.getAbsolutePath());
+		framework = createFramework(configuration);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		stopQuietly(framework);
+		super.tearDown();
+	}
+
+	private void initAndStartFramework() throws Exception {
+		initAndStart(framework);
+	}
+
+	public void testFrameworkClassLoaderWithNewURI() throws Exception {
+		initAndStartFramework();
+	}
+
+	public void testEmbeddedURLHandler() throws Exception {
+		initAndStart(framework);
+		Bundle testHandler = framework.getBundleContext().installBundle(bundleInstaller.getBundleLocation("test.protocol.handler"));
+		testHandler.start();
+		Bundle testHandlerUser = framework.getBundleContext().installBundle(bundleInstaller.getBundleLocation("test.protocol.handler.user"));
+		testHandlerUser.start();
+		try {
+			URL testingURL = new URL("testing1://test");
+			fail("Should not find testing1 protocol: " + testingURL);
+		} catch (MalformedURLException e) {
+			// expected
+		}
+	}
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java
index 0d4e0b2..5d9ced4 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/MultiplexingFactory.java
@@ -11,14 +11,24 @@
  *******************************************************************************/
 package org.eclipse.osgi.internal.url;
 
-import java.lang.reflect.*;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import java.net.URL;
-import java.util.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.internal.framework.EquinoxBundle;
 import org.eclipse.osgi.internal.framework.EquinoxContainer;
 import org.eclipse.osgi.storage.StorageUtil;
-import org.osgi.framework.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 
 /*
  * An abstract class for handler factory impls (Stream and Content) that can 
@@ -40,6 +50,7 @@
 	 * it the ability to call setAccessible(true) on other types from the java.base module
 	 */
 	static final Collection<AccessibleObject> setAccessible;
+	static final Collection<ClassLoader> systemLoaders;
 	static {
 		Collection<AccessibleObject> result = null;
 		try {
@@ -64,6 +75,18 @@
 			// ingore as if there is no Unsafe
 		}
 		setAccessible = result;
+
+		Collection<ClassLoader> loaders = new ArrayList<>();
+		try {
+			ClassLoader cl = ClassLoader.getSystemClassLoader();
+			while (cl != null) {
+				loaders.add(cl);
+				cl = cl.getParent();
+			}
+		} catch (Throwable t) {
+			// ignore as if no loaders
+		}
+		systemLoaders = Collections.unmodifiableCollection(loaders);
 	}
 	protected EquinoxContainer container;
 	protected BundleContext context;
@@ -101,7 +124,8 @@
 			setParentFactory.invoke(factory, new Object[] {getParentFactory()});
 		} catch (Exception e) {
 			container.getLogServices().log(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, "register", e); //$NON-NLS-1$
-			throw new RuntimeException(e.getMessage(), e);
+			// just return and not have it registered
+			return;
 		}
 		addFactory(factory);
 	}
@@ -116,7 +140,7 @@
 			closeTracker.invoke(factory, (Object[]) null);
 		} catch (Exception e) {
 			container.getLogServices().log(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, "unregister", e); //$NON-NLS-1$
-			throw new RuntimeException(e.getMessage(), e);
+			// just return without blowing up here
 		}
 	}
 
@@ -155,7 +179,7 @@
 		List<Object> current = getFactories();
 		Class<?>[] classStack = internalSecurityManager.getClassContext();
 		for (Class<?> clazz : classStack) {
-			if (clazz == InternalSecurityManager.class || clazz == MultiplexingFactory.class || ignoredClasses.contains(clazz))
+			if (clazz == InternalSecurityManager.class || clazz == MultiplexingFactory.class || ignoredClasses.contains(clazz) || isSystemClass(clazz))
 				continue;
 			if (hasAuthority(clazz))
 				return this;
@@ -169,7 +193,7 @@
 					}
 				} catch (Exception e) {
 					container.getLogServices().log(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, "findAuthorizedURLStreamHandler-loop", e); //$NON-NLS-1$
-					throw new RuntimeException(e.getMessage(), e);
+					// we continue to the next factory here instead of failing
 				}
 			}
 		}
@@ -179,6 +203,17 @@
 		return this;
 	}
 
+	private boolean isSystemClass(final Class<?> clazz) {
+		// we want to ignore classes from the system
+		ClassLoader cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+			@Override
+			public ClassLoader run() {
+				return clazz.getClassLoader();
+			}
+		});
+		return cl == null || systemLoaders.contains(cl);
+	}
+
 	public boolean hasAuthority(Class<?> clazz) {
 		Bundle b = FrameworkUtil.getBundle(clazz);
 		if (!(b instanceof EquinoxBundle)) {