Make a really dynamic Eclipse extension point consumer

Even if Eclipse broke man of OSGI modularity paradigm, you can always make a step into coding coding your extension point the good way.
It’s not more expensive, and can help you when debugging your product (no need to restart your platform for example).

So let start doing it!

Sample description

The first thing to keep in mind when you want a make a dynamic extension point is that you can’t listen for only one implementation (I guess you understand why).
Extension

Reacting to extension point appearance

So know, we’re going to react from extension point appearance/disapearance. In order to do this, we must implement some classes.

contribution-point-sequence

We’ve got to implement a Descriptor class that will describe our extension (e.g. the interface an extension must implement).
An extension point Listener will be started by our plugin’s Activator and will add newly added (and remove removed) descriptors to a Registry.
As an addition and because our extended plugin isn’t the first that have started, the start() method of our Activator will ask the registry to add already started extensions.

Generic extended plugin descriptor API

Let’s design a generic API for describing EP.
Starting from the descriptor (EP description).

public class DefaultExtensionDescriptor {
	/**
	 * Configuration element of this descriptor.
	 */
	 final IConfigurationElement _element;
	/**
	 * the extension name
	 */
	String _extensionName;
	/**
	 * Qualified class name of the extension. This will be used as an id to
	 * remove contributions.
	 */
        @Getter
	final String _id;
	/**
	 * CTor
	 * @param configuration_p extension configuration,
	 * @param id_p extension ID
	 */
	public DefaultExtensionDescriptor(IConfigurationElement configuration_p, String id_p) {
		super();
		this._extensionName = id_p;
		_element = configuration_p;
		_id = configuration_p.getAttribute(id_p);
	}	
}

And a subclass that will describe an extension made from an SPI (a Java Interface).

@Slf4j
public class APIExtensionDescriptor<T> extends DefaultExtensionDescriptor{
	/**
	 * The {@link ElementCustomizer}.
	 */
	private T _provider;
	/**
	 * Constructor.
	 * 
	 * @param configuration_p
	 *            configuration element from which to create this descriptor.
	 * @param extensionName_p name of the extension
	 */
	public APIExtensionDescriptor(
			final IConfigurationElement configuration_p, String extensionName_p) {
		super(configuration_p, extensionName_p);
 
	}
 
	/**
	 * Creates an instance of this descriptor's
	 * {@link ExtensionDescriptor}.
	 * 
	 * @return a new instance of this descriptor's
	 *         {@link ExtensionDescriptor}.
	 */
	public T getExtension() {
		if (_provider == null) {
			try {
				_provider = (T) _element
						.createExecutableExtension(_extensionName);
			} catch (CoreException e) {
				LOG.error("Problem while loading extensions state", e);
			}
		}
		return _provider;
	}

You can put this code below in a dedicated helper plugin and reuse and abuse of it.

Finally, here’s a descriptor implmentation:

public class PopulatorExtensionDescriptor extends APIExtensionDescriptor<SelectionResolver>{
	/**
	 * Name of the extension point's tag "populator" attribute.
	 */
	public static final String POPULATOR_EXTENSION = "resolverClass"; //$NON-NLS-1$
 
	/**
	 * Constructor.
	 * 
	 * @param configuration_p
	 *            configuration element from which to create this descriptor.
	 */
	public PopulatorExtensionDescriptor(IConfigurationElement configuration_p) {
		super(configuration_p, POPULATOR_EXTENSION);
	}
}

EP Registry

We’re now implementating the registry: the one which will contain all our extensions descriptors to query.
Let’s start again with a little bit of generic code (that you can also put in an helper plugin)

public class DefaultExtensionRegistry <T extends DefaultExtensionDescriptor>{//The registry is implemented for a kind of descriptor
	/**
	 * List of extensions created from the extension point contributions.
	 */
	private final List<T> _extensions = new ArrayList<T>();
 
	/**
	 * Adds an extension to the registry.
	 * 
	 * @param extension_p
	 *            The extension that is to be added to the registry.
	 */
	public void addExtension(
			T extension_p) {
		_extensions.add(extension_p);
	}
 
	/**
	 * Removes all extensions from the registry. This will be called at plugin
	 * stopping.
	 */
	public void clearRegistry() {
		_extensions.clear();
	}
 
	/**
	 * Returns a copy of the registered extensions list.
	 * 
	 * @return A copy of the registered extensions list.
	 */
	public List<T> getRegisteredExtensions() {
		return new ArrayList<T>(_extensions);
	}
 
	/**
	 * Removes a phantom from the registry.
	 * 
	 * @param extensionClassName_p
	 *            Qualified class name of the sync element which corresponding
	 *            phantom is to be removed from the registry.
	 */
	public void removeExtension(String extensionClassName_p) {
		for (T extension : getRegisteredExtensions()) {
			if (extension.getId().equals(extensionClassName_p)) {
				_extensions.remove(extension);
			}
		}
	}
}

And let’s do a specialization for registries that can query an API to get a method call result:

/**
 * @param <T> the extension descriptor
 * @param <Y> the API
 */
public class APIExtensionRegistry<T extends APIExtensionDescriptor<Y>, Y> extends DefaultExtensionRegistry<T> {
	/**
	 * All api's extensions
	 * @return all api extensions
	 */
	public Collection<Y> getExtensions() {
		Collection<Y> ret = new HashSet<Y>();
		for (T extension : getRegisteredExtensions()) {
			ret.add(extension.getExtension());
 
		}
		return ret;
	}
}

Finally, here’s a simple final implementation (as a singleton) where we can query a method to have the result of all our extension:

public class PopulatorExtensionRegistry extends APIExtensionRegistry<PopulatorExtensionDescriptor, SelectionResolver>{
	/**
	 * Singleton
	 */
	private static PopulatorExtensionRegistry _instance = null;
	/**
	 * Constructor.
	 */
	private PopulatorExtensionRegistry() {
	}
	/**
	 * The singleton
	 * @return the singleton
	 */
	public static PopulatorExtensionRegistry getInstance() {
		if (_instance == null) {
			_instance = new PopulatorExtensionRegistry();
		}
		return _instance;
	}
	/**
	 * Resolves the selection of given elements to a collection of eobjects
	 * 
	 * @param selection_p
	 *            the selection
	 * @return the selection
	 */
	public Collection<EObject> asSemanticElements(Object selection_p) {
		Collection<EObject> ret = new ArrayList<EObject>();
		for (PopulatorExtensionDescriptor extension : getRegisteredExtensions()) {
			ret.addAll(extension.getExtension().asSemanticElements(
					selection_p));//Call to the extension API
		}
		return ret;
	}
}

Extension point listener

The listener will listen for newly added extensions on the service registry.
As usual, here’s the abstract code leading to tiny little final listener implementation.

public abstract class DefaultRegistryEventListener<X extends DefaultExtensionDescriptor> implements
		IRegistryEventListener {
 
	/** The _extension point. */
	private final String _extensionPoint;
 
	/** The extension tag. */
	 protected final String _tag;
 
	/** The descriptor extension. */
	private final String _descriptorExtension;
 
	/**
	 * Instantiates a new default registry event listener.
	 *
	 * @param extensionPoint_p the extension point
	 * @param tag_p the tag
	 * @param descriptorExtension_p id of the descriptor to remove it while remove from registry
	 */
	public DefaultRegistryEventListener(String extensionPoint_p, String tag_p, String descriptorExtension_p) {
		this._extensionPoint = extensionPoint_p;
		this._tag = tag_p;
		this._descriptorExtension = descriptorExtension_p;
	}
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.core.runtime.IRegistryEventListener#added(org.eclipse.core
	 * .runtime.IExtensionPoint[])
	 */
	@Override
	public final void added(IExtensionPoint[] extensionPoints) {
		// do nothing, handled by extension
 
	}
 
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.core.runtime.IRegistryEventListener#removed(org.eclipse.core
	 * .runtime.IExtensionPoint[])
	 */
	@Override
	public final void removed(IExtensionPoint[] extensionPoints) {
		// do nothing, handled by extension
 
	}
	/**
	 * {@inheritDoc}
	 * 
	 * @see org.eclipse.core.runtime.IRegistryEventListener#added(org.eclipse.core.runtime.IExtension[])
	 */
	@Override
	public void added(IExtension[] extensions) {
		for (IExtension extension : extensions) {
			parseExtension(extension);
		}
 
	}
 
 
	/**
	 * Though this listener reacts to the extension point changes, there could
	 * have been contributions before it's been registered. This will parse
	 * these initial contributions.
	 */
	public void parseInitialContributions() {
		final IExtensionRegistry registry = Platform.getExtensionRegistry();
 
		for (IExtension extension : registry.getExtensionPoint(
				_extensionPoint).getExtensions()) {
			parseExtension(extension);
		}
	}
 
 	/**
 	 * Gets the registry instance.
 	 *
 	 * @param <T> the generic type
 	 * @return the registry instance
 	 */
 	protected abstract <T extends DefaultExtensionRegistry> T getRegistryInstance();
 
	/**
	 * Parses the extension.
	 *
	 * @param extension_p the extension_p
	 */
	protected abstract void parseExtension(IExtension extension_p);
 
	/**
	 * {@inheritDoc}
	 * 
	 * @see org.eclipse.core.runtime.IRegistryEventListener#removed(org.eclipse.core.runtime.IExtension[])
	 */
	@Override
	public void removed(IExtension[] extensions_p) {
		for (IExtension extension : extensions_p) {
			final IConfigurationElement[] configElements = extension
					.getConfigurationElements();
			for (IConfigurationElement elem : configElements) {
				if (_tag.equals(elem.getName())) {
					final String extensionClassName = elem
							.getAttribute(_descriptorExtension);
					getRegistryInstance()
							.removeExtension(extensionClassName);
				}
			}
		}
	}
}

The final listener implementation:

public class PopulatorRegistryListener extends DefaultRegistryEventListener<PopulatorExtensionDescriptor> {
 
	/**
	 * Name of the extension point to parse for extensions.
	 */
	public static final String POPULATOR_EXTENSION_POINT = "net.osgiliath.common.selections.resolver"; //$NON-NLS-1$
 
	/**
	 * Name of the extension point's "selectionResolver" tag.
	 */
	private static final String POPULATOR_TAG_EXTENSION = "selectionResolver"; //$NON-NLS-1$
	/**
	 * Ctor
	 */
	public PopulatorRegistryListener() {
		super(POPULATOR_EXTENSION_POINT, POPULATOR_TAG_EXTENSION, PopulatorExtensionDescriptor.POPULATOR_EXTENSION);
	}
	/**
	 * Parses a single extension contribution.
	 * 
	 * @param extension_p
	 *            Parses the given extension and adds its contribution to the
	 *            registry.
	 */
	protected void parseExtension(IExtension extension_p) {
		final IConfigurationElement[] configElements = extension_p
				.getConfigurationElements();
		for (IConfigurationElement elem : configElements) {
			if (POPULATOR_TAG_EXTENSION.equals(elem.getName())) {
 
				getRegistryInstance().addExtension(new PopulatorExtensionDescriptor(elem));
			}
		}
	}
 
	@Override
	protected <T extends DefaultExtensionRegistry> T getRegistryInstance() {
 
		return (T) PopulatorExtensionRegistry.getInstance();
	}
}

Activator listener activation and registry initialisation

Finally we just have to regsister our listener and initialize the registry.

public class SelectionActivator extends AbstractUIPlugin {
/**
	 * The registry listener that will be used to listen to extension changes.
	 */
	private PopulatorRegistryListener _registryListener = new PopulatorRegistryListener();
public void start(BundleContext context) throws Exception {
		super.start(context);
		_plugin = this;
		final IExtensionRegistry registry = Platform.getExtensionRegistry();
		registry.addListener(_registryListener,
				PopulatorRegistryListener.POPULATOR_EXTENSION_POINT);
		_registryListener.parseInitialContributions();
 
	}
/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext
	 * )
	 */
	public void stop(BundleContext context) throws Exception {
		_plugin = null;
		super.stop(context);
		final IExtensionRegistry registry = Platform.getExtensionRegistry();
		registry.removeListener(_registryListener);
		PopulatorExtensionRegistry.getInstance().clearRegistry();
 
	}
}

And we’re done, our extended plugin will be at least reacting to extensions appearance/deappearance (and now I’m thinking about blueprint/scr, their OSGI counterparts 🙂 ).

Conclusion

The common implementation code taken appart, there is not to much code to implement to get this functionality up and running.
With this implementation, you can now play with the OSGI console while debugging, starting and stopping extensions when they’re no more synchronized.
This will lead to a much dynamic Eclipse ecosystem (please, also use import-package instead of Require-Bundle directive in your Manifest), for example, avoiding to restart the workbench every time you install a new feature…

Share Button

Leave a Reply

Your email address will not be published. Required fields are marked *