Bug 540446 - Should issue a warning if an invalidated class loader is
used

Change-Id: Ia6585a0910a019d774fc071a4c83a774187544ec
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java
index 4140078..f53778a 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java
@@ -34,7 +34,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import junit.framework.AssertionFailedError;
@@ -2369,7 +2371,7 @@
 		exporter.start();
 		Bundle importer = getContext().installBundle(getName() + "-importer", new FileInputStream(importerBundleFile));
 		importer.start();
-		Bundle requirer = getContext().installBundle(getName() + "-requirer", new FileInputStream(requirerBundleFile));
+		final Bundle requirer = getContext().installBundle(getName() + "-requirer", new FileInputStream(requirerBundleFile));
 		requirer.start();
 
 		BundleWiring importerWiring = importer.adapt(BundleWiring.class);
@@ -2400,6 +2402,17 @@
 		// invalid wires by refreshing the exporter
 		refreshBundles(Collections.singleton(exporter));
 
+		// add a framework event listener to find error message about invalud class loaders
+		final BlockingQueue<FrameworkEvent> events = new LinkedBlockingQueue<>();
+		getContext().addFrameworkListener(new FrameworkListener() {
+			@Override
+			public void frameworkEvent(FrameworkEvent event) {
+				if (event.getBundle() == requirer) {
+					events.add(event);
+				}
+			}
+		});
+
 		try {
 			importerCL.loadClass("export2.SomeClass");
 			fail("Expecting LinkageError.");
@@ -2424,6 +2437,17 @@
 
 		export4Resource = requirerCL.getResource("export4/resource.txt");
 		assertNull("Found resource from invalid wire.", export4Resource);
+
+		// find the expected event
+		FrameworkEvent event = events.poll(5, TimeUnit.SECONDS);
+		assertNotNull("No FrameworkEvent found.", event);
+		assertEquals("Wrong bundle for event.", requirer, event.getBundle());
+		assertEquals("Wrong event type.", FrameworkEvent.ERROR, event.getType());
+		assertTrue("Wrong exception: " + event.getThrowable(), event.getThrowable() instanceof RuntimeException);
+		assertTrue("Wrong message: " + event.getThrowable().getMessage(), event.getThrowable().getMessage().startsWith("Invalid class loader"));
+
+		// make sure there are no others
+		assertNull("Found more events.", events.poll(1, TimeUnit.SECONDS));
 	}
 
 	void refreshBundles(Collection<Bundle> bundles) throws InterruptedException {
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
index 3db1696..461dc31 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2017 IBM Corporation and others.
+ * Copyright (c) 2004, 2018 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -59,6 +59,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
 import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.namespace.HostNamespace;
 import org.osgi.framework.namespace.PackageNamespace;
@@ -127,6 +128,7 @@
 	private volatile ModuleClassLoader classloader;
 	private final ClassLoader parent;
 	private final AtomicBoolean triggerClassLoaded = new AtomicBoolean(false);
+	private final AtomicBoolean firstUseOfInvalidLoader = new AtomicBoolean(false);
 
 	/**
 	 * Returns the package name from the specified class name.
@@ -964,7 +966,15 @@
 
 	private BundleLoader getProviderLoader(ModuleWire wire) {
 		ModuleWiring provider = wire.getProviderWiring();
-		return provider == null ? null : (BundleLoader) provider.getModuleLoader();
+		if (provider == null) {
+			if (firstUseOfInvalidLoader.getAndSet(true)) {
+				// publish a framework event once per loader, include an exception to show the stack
+				String message = "Invalid class loader from a refreshed bundle is being used: " + toString(); //$NON-NLS-1$
+				container.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, wiring.getBundle(), new IllegalStateException(message));
+			}
+			return null;
+		}
+		return (BundleLoader) provider.getModuleLoader();
 	}
 
 	final void addProvidedPackageNames(String packageName, List<String> result, boolean subPackages, Collection<BundleLoader> visited) {