From 5131597f77f28f1bc65df79ea05c53160deb8e0a Mon Sep 17 00:00:00 2001 From: Markus Alexander Kuppe Date: Thu, 5 Dec 2013 09:23:10 +0100 Subject: Bug 423212: Allow to inject OSGi BundleContext Change-Id: Id4c340d88ba55c40e6936c18fa6d1970657ac33a Signed-off-by: Markus Alexander Kuppe --- .../META-INF/MANIFEST.MF | 3 +- .../OSGI-INF/bundleContext.xml | 8 ++ .../e4/core/di/extensions/BundleContext.java | 28 ++++++ .../extensions/BundleContextObjectSupplier.java | 84 +++++++++++++++++ .../di/extensions/InjectionBundleContextTest.java | 103 +++++++++++++++++++++ .../org/eclipse/e4/core/tests/CoreTestSuite.java | 2 + 6 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml create mode 100644 bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/extensions/BundleContext.java create mode 100644 bundles/org.eclipse.e4.core.di.extensions/src/org/eclipse/e4/core/di/internal/extensions/BundleContextObjectSupplier.java create mode 100644 tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionBundleContextTest.java 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 3eb44a564..d2a904b37 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-ActivationPolicy: lazy 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 000000000..a03d46c19 --- /dev/null +++ b/bundles/org.eclipse.e4.core.di.extensions/OSGI-INF/bundleContext.xml @@ -0,0 +1,8 @@ + + + + + + + + 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 000000000..bf3586572 --- /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 000000000..f2547e422 --- /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 requestor2listener = new HashMap(); + + 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 000000000..9a09a212c --- /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 dcf7f06c2..bfd00bd67 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.InjectionOrderTest; 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 @@ public class CoreTestSuite extends TestSuite { addTestSuite(InjectionPreferencesTest.class); addTestSuite(InjectionMixedSuppliersTest.class); addTestSuite(InjectionEventTest.class); + addTestSuite(InjectionBundleContextTest.class); // DI addTestSuite(InjectionOrderTest.class); -- cgit v1.2.3