Bug 423212: Allow to inject OSGi BundleContext 

Change-Id: Id4c340d88ba55c40e6936c18fa6d1970657ac33a
Signed-off-by: Markus Alexander Kuppe <bugs.eclipse.org@lemmster.de>
diff --git a/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF
index 3eb44a5..d2a904b 100644
--- a/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF
@@ -13,7 +13,8 @@
 Bundle-RequiredExecutionEnvironment: J2SE-1.5
 Import-Package: javax.annotation;version="1.0.0",
  javax.inject;version="1.0.0"
-Service-Component: OSGI-INF/preferences.xml, OSGI-INF/events.xml
+Service-Component: OSGI-INF/preferences.xml, OSGI-INF/events.xml,
+ OSGI-INF/bundleContext.xml
 Export-Package: org.eclipse.e4.core.di.extensions;x-internal:=true,
  org.eclipse.e4.core.di.internal.extensions;x-friends:="org.eclipse.e4.ui.di"
 Bundle-Localization: fragment
diff --git a/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml b/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml
new file mode 100644
index 0000000..a03d46c
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.eclipse.e4.core.di.extensions.bundleContext">
+   <implementation class="org.eclipse.e4.core.di.internal.extensions.BundleContextObjectSupplier"/>
+   <property name="dependency.injection.annotation" type="String" value="org.eclipse.e4.core.di.extensions.BundleContext"/>
+   <service>
+      <provide interface="org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier"/>
+   </service>
+</scr:component>
diff --git a/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/BundleContext.java b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/BundleContext.java
new file mode 100644
index 0000000..bf35865
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/BundleContext.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Markus Alexander Kuppe 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:
+ *   Markus Alexander Kuppe - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.di.extensions;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.inject.Qualifier;
+
+/**
+ * https://bugs.eclipse.org/423212
+ */
+@Qualifier
+@Documented
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BundleContext {
+	// Nop
+}
diff --git a/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/BundleContextObjectSupplier.java b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/BundleContextObjectSupplier.java
new file mode 100644
index 0000000..f2547e4
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/BundleContextObjectSupplier.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Markus Alexander Kuppe 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:
+ *   Markus Alexander Kuppe - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.di.internal.extensions;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.e4.core.di.InjectionException;
+import org.eclipse.e4.core.di.annotations.Optional;
+import org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier;
+import org.eclipse.e4.core.di.suppliers.IObjectDescriptor;
+import org.eclipse.e4.core.di.suppliers.IRequestor;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.SynchronousBundleListener;
+
+public class BundleContextObjectSupplier extends ExtendedObjectSupplier {
+
+	/**
+	 * A Map of Requestor to BundleListener. Each Requestor will only ever request its own bundle and thus there is a 1:1 relationship between R and BL.
+	 */
+	private final Map<IRequestor, BundleListener> requestor2listener = new HashMap<IRequestor, BundleListener>();
+
+	private final BundleContext localBundleContext = FrameworkUtil.getBundle(BundleContextObjectSupplier.class).getBundleContext();
+
+	@Override
+	public Object get(IObjectDescriptor descriptor, IRequestor requestor, boolean track, boolean group) {
+		final Class<?> requestingObjectClass = requestor.getRequestingObjectClass();
+		final Bundle bundle = FrameworkUtil.getBundle(requestingObjectClass);
+
+		// Cannot use BundleListener as a BL can only be registered with a BC (which might be null)
+		if (track) {
+			if (!requestor2listener.containsKey(requestor)) {
+				track(bundle, requestor);
+			}
+		} else {
+			untrack(requestor);
+		}
+
+		final BundleContext bundleContext = bundle.getBundleContext();
+		if (bundleContext != null) {
+			return bundleContext;
+		} else if (descriptor.getQualifier(Optional.class) != null) {
+			// Do not have a bundle context but requestor has marked the parameter/field optional
+			return null;
+		}
+		throw new InjectionException("Unable to inject BundleContext: " + bundle.getSymbolicName() + " bundle is not active or starting/stopping"); //$NON-NLS-1$  //$NON-NLS-2$
+	}
+
+	private void untrack(final IRequestor requestor) {
+		synchronized (requestor2listener) {
+			BundleListener l = requestor2listener.remove(requestor);
+			localBundleContext.removeBundleListener(l);
+		}
+	}
+
+	private void track(final Bundle bundle, final IRequestor requestor) {
+		// A _synchronous_ BundleListener asserts that the BC is un-injected,
+		// _before_ it becomes invalid (state-wise). 
+		BundleListener listener = new SynchronousBundleListener() {
+			public void bundleChanged(BundleEvent event) {
+				if (event.getBundle().equals(bundle)) {
+					if (requestor.isValid()) {
+						requestor.resolveArguments(false);
+						requestor.execute();
+					}
+				}
+			}
+		};
+		synchronized (requestor2listener) {
+			localBundleContext.addBundleListener(listener);
+			requestor2listener.put(requestor, listener);
+		}
+	}
+}
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionBundleContextTest.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionBundleContextTest.java
new file mode 100644
index 0000000..9a09a21
--- /dev/null
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionBundleContextTest.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Markus Alexander Kuppe 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:
+ *   Markus Alexander Kuppe - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.internal.tests.di.extensions;
+
+import javax.inject.Inject;
+
+import junit.framework.TestCase;
+
+import org.eclipse.e4.core.contexts.ContextInjectionFactory;
+import org.eclipse.e4.core.contexts.EclipseContextFactory;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.core.di.annotations.Optional;
+import org.eclipse.e4.core.di.extensions.BundleContext;
+import org.eclipse.e4.core.internal.tests.CoreTestsActivator;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+
+public class InjectionBundleContextTest extends TestCase {
+
+	// classed used as a user of the @BundleContext annotation
+	static class InjectionTarget {
+
+		private org.osgi.framework.BundleContext ctx;
+
+		@Inject
+		public void setBundleContext(
+				@BundleContext @Optional org.osgi.framework.BundleContext ctx) {
+			this.ctx = ctx;
+		}
+
+		public boolean hasContext() {
+			return this.ctx != null;
+		}
+
+		public org.osgi.framework.BundleContext getContext() {
+			return this.ctx;
+		}
+	}
+
+	private InjectionTarget target;
+	private Bundle bundle;
+
+	@Override
+	protected void tearDown() throws Exception {
+		bundle.start();
+
+		final org.osgi.framework.BundleContext bundleContext = CoreTestsActivator
+				.getDefault().getBundleContext();
+		final IEclipseContext localContext = EclipseContextFactory
+				.getServiceContext(bundleContext);
+
+		ContextInjectionFactory.uninject(target, localContext);
+
+		super.tearDown();
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		final org.osgi.framework.BundleContext bundleContext = CoreTestsActivator
+				.getDefault().getBundleContext();
+		bundle = bundleContext.getBundle();
+
+		final IEclipseContext localContext = EclipseContextFactory
+				.getServiceContext(bundleContext);
+
+		target = ContextInjectionFactory.make(InjectionTarget.class,
+				localContext);
+	}
+
+	public void testInject() {
+		assertTrue(target.hasContext());
+	}
+
+	public void testUnInject() throws BundleException, InterruptedException {
+		// inject
+		assertTrue(target.hasContext());
+
+		// Check also that the BundleContext instance has indeed changed
+		final org.osgi.framework.BundleContext firstContext = target
+				.getContext();
+
+		// uninject
+		bundle.stop();
+		assertFalse(target.hasContext());
+
+		// re-inject
+		bundle.start();
+		assertTrue(target.hasContext());
+
+		final org.osgi.framework.BundleContext secondContext = target
+				.getContext();
+		assertNotSame(firstContext, secondContext);
+	}
+}
diff --git a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java
index dcf7f06..bfd00bd 100644
--- a/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java
+++ b/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/tests/CoreTestSuite.java
@@ -48,6 +48,7 @@
 import org.eclipse.e4.core.internal.tests.di.InjectionResultLeakTest;
 import org.eclipse.e4.core.internal.tests.di.InvokeTest;
 import org.eclipse.e4.core.internal.tests.di.RecursiveObjectCreationTest;
+import org.eclipse.e4.core.internal.tests.di.extensions.InjectionBundleContextTest;
 import org.eclipse.e4.core.internal.tests.di.extensions.InjectionEventTest;
 import org.eclipse.e4.core.internal.tests.di.extensions.InjectionMixedSuppliersTest;
 import org.eclipse.e4.core.internal.tests.di.extensions.InjectionPreferencesTest;
@@ -62,6 +63,7 @@
 		addTestSuite(InjectionPreferencesTest.class);
 		addTestSuite(InjectionMixedSuppliersTest.class);
 		addTestSuite(InjectionEventTest.class);
+		addTestSuite(InjectionBundleContextTest.class);
 
 		// DI
 		addTestSuite(InjectionOrderTest.class);