Xtreme Eclipse4

A tutorial on advanced usages of the Eclipse4 platform

Customizing dependency injection behavior with custom annotations

Paul Webster / @paulweb515
Lars Vogel / @vogella
Sopot Çela / @smcela

Introduction

Pic of Paul Paul Webster
Pic of Paul Sopot Çela
Pic of Paul Lars Vogel

Overview

  • Customizing dependency injection behavior with custom annotations
  • Exploiting Eclipse4 advanced rendering
  • Framework services replacement and model extensions

Eclipse Core DI

  1. Retrieves its information from the IEclipseContext.
  2. Can access both data and services.
  3. Keeps the fields and methods up to date as the data changes.
  4. DI is applied to POJOs instantiated by the system (Parts, Processors, Addons).
  5. ContextInjectionFactory uses data in IEclipseContext to instantiate an object.

Annotations

Annotation Description
@Inject Identifies injectable constructors, methods, and fields.
@Named Specifies a String used to look up the injected value.
@PostConstruct The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
@PreDestroy The PreDestroy annotation is used on methods as a callback notification to signal that the instance is in the process of being removed by the container.
@Singleton Identifies a type that the injector only instantiates once.

Annotations - Eclipse Specific

Annotation Description
@Optional This annotation can be applied to methods, fields, and parameters to mark them as optional for the dependency injection.
@Creatable Specifies that the target class can be created by an injector as needed.
@Active This annotation can be added to injectable fields ands methods to indicate that the injected value should come from the active context.

Eclipse Contexts

The source for our DI data and services.

  1. Look up the local value for that key.
  2. If it's a regular result, return the result.
  3. If it's an IContextFunction evaluate and return the result.
  4. Ask the parent IEclipseContext.

ContextInjectionFactory

  1. Check for a Provider<T>
  2. Check for an ExtendedObjectSupplier
  3. Check the static IEclipseContext
  4. Check the primary IEclipseContext
  5. Check for any registered IBindings
  6. Check to see if the type is @Createable

Annotations - Eclipse Extensions

Annotation Description
@Preference Access a preference value from IEclipsePreferences.
@OSGiBundle Access the Bundle or BundleContext of the injected class.

Extended Object Supplier

  1. You need to register an OSGi service to provide org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier
  2. Your service needs to identify your annotation in a property dependency.injection.annotation
  3. Provide an implementation that extends org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier

@Preference


@Qualifier
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Preference {
	String value() default ""; // key in the node
	String nodePath() default "";
}

@Preference - Usage


public class EclipseSplashHandler {
	@Inject
	@Preference("SHOW_BUILDID_ON_STARTUP")
	boolean showBuildId;
	
	@Inject
	@Preference(nodePath="org.eclipse.ui", value="SHOW_PROGRESS_ON_STARTUP" )
	boolean showProgress;
}

@Preference - Contributing


<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
      name="org.eclipse.e4.core.services.preferences">
   <implementation 
      class="org.eclipse.e4.core.di.internal.extensions.PreferencesObjectSupplier"/>
   <service>
      <provide interface="org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier"/>
   </service>
   <property name="dependency.injection.annotation" type="String" 
      value="org.eclipse.e4.core.di.extensions.Preference"/>
</scr:component>

@Preference - Implementation

Argument Description
descriptor descriptor of the object requested by the requestor.
requestor the originator of this request.
track If the object supplier should notify the requestor of a change to the returned object.
group If the change notification can be grouped.

@Preference - Implementation II


public Object get(IObjectDescriptor descriptor, IRequestor requestor, 
        boolean track, boolean group) {
    Class<?> descriptorsClass = getDesiredClass(descriptor.getDesiredType());
    String nodePath = getNodePath(descriptor, requestor.getRequestingObjectClass());
    String key = getKey(descriptor);
    return getPreferencesService().getString(nodePath, key, null, null);
}

Enhance an RCP application

  1. This is an Eclipse4 application.
  2. It dynamically loads and unloads plugins.
  3. It provides an extension point so that plugins can state their author, company.
  4. We want to consume the contributed extensions using DI.

Enhance an RCP application - 1.1

  1. https://github.com/scela/EclipseCon2014
  2. org.eclipse.e4.examples.di.product - the RCP application
  3. org.eclipse.e4.examples.di.extensions - adds support for @Extension
  4. Open /org.eclipse.e4.examples.di.product/org.eclipse.e4.examples.di.product.product and choose "Launch an Eclipse Application"

Exercise - 1.1

  1. We're replacing OldExtensionReader, getting rid of the boilerplate code.
  2. Replacing the calls to process(), added(final IExtension[]), and removed(final IExtension[]).

@Inject
@Optional
public void setExtensions(
    final @Extension(SamplePart.EXTENSION_POINT) List<IConfigurationElement> elements) {
    //... stuff
}

Exercise - 1.2

  1. Provide the @Extension annotation for the object supplier.
  2. Provide an implementation class that extends ExtendedObjectSupplier.
  3. Register the OSGi service using Declarative Services.

Exercise - 1.3

  1. Make sure we're calling the @Extension object supplier.
  2. Have SamplePart use DI to instantiate the ExtensionReader.

Exercise - 1.4

  1. Return the valid configuration elements for @Extension.
  2. Get the IExtensionRegistry.
  3. Get the annotation information.
  4. Return the requested information.

Exercise - 1.5

  1. Add a registry listener to respond to changes if the request should be tracked.
  2. Re-evaluate the requestor by resolving the arguments and then executing it again.
  3. Check for requestor validity.

Exercise - 1.6

  1. Added bonus: remove all listeners from a requestor.
  2. In some scenarios, a single requestor can generate multiple listeners.
  3. Store them in a set and remove them if a requestor becomes invalid.

THE END

BY Paul Webster