Bndtools aims to be a thin wrapper over bnd. This means that it is comparatively rare that bndtools should be creating errors or warnings. In most cases bnd is responsible for generating errors at build time, it's then bndtools job to display them nicely to the user.
Make sure that bnd is generating an appropriate error or warning using bnd's reporting API. By default all build errors are displayed as markers on the bnd.bnd file. This isn't usually the right file, and even when it is there isn't enough information to pick the correct line. To fix this you need to add information to the Location object associated with the error/warning. Importantly you must add a custom details object to the location. This should be your own object type, and not reused by any other error reporting.
e.g.
SetLocation location = analyzer.error("A clever error message");
location.line(1234)
.method("doStuff")
.details(new org.bndtools.example.MyCustomizedLocationObject());
Once bnd is generating extra error information then bndtools can use it to generate appropriate markers. This is achieved through the use of the org.bndtools.build.api.BuildErrorDetailsHandler (it's normally best to extend org.bndtools.build.api.AbstractBuildErrorDetailsHandler). The method responsible for adding markers is generateMarkerData(), which returns a list of MarkerData objects. Each MarkerData will be used to create a Marker in a file. Normally an error should map to a single marker, although sometimes more than one is appropriate (e.g. if the error can be fixed by changing one of several files).
AbstractBuildErrorDetailsHandler provides convenience methods for adding markers to Java source files. Markers must be created with a message (which is typically the message written by bnd). Markers should also have a line number, or a start/end location. The convenience methods set the start/end location for you, but you have to set the message yourself.
e.g.
MyCustomizedLocationObject errorInfo = (MyCustomizedLocationObject) location.details;
IJavaProject javaProject = JavaCore.create(project);
Map<String,Object> attribs = new HashMap<String,Object>();
attribs.put(IMarker.MESSAGE, location.message.trim());
MarkerData md = null;
if (errorInfo.isTypeLevel) {
md = createTypeMarkerData(javaProject, errorInfo.className, attribs, false);
} else if (errorInfo.isMethodLevel) {
md = createMethodMarkerData(javaProject, errorInfo.className, errorInfo.methodName,
errorInfo.methodSignature, attribs, false);
}
if (md == null) {
// No other marker could be created, so add a marker to the bnd file
result.add(new MarkerData(getDefaultResource(project), attribs, false));
}
result.add(md);
return result;
Bndtools uses the Eclipse plugin registry to discover BuildErrorDetailsHandler instances. To hook in to this you need to add the following to your plugin.xml
<extension point="bndtools.core.buildErrorDetailsHandlers">
<handler typeMatch="org.bndtools.example.MyCustomizedLocationObject"
class="org.bndtools.example.handler.MyCustomBuildErrorDetailsHandler" />
</extension>
Error markers will now appear in the right places. More work can be done to add quick fixes, but it's much harder than adding the markers.
org.bndtools.facade.ExtensionFacade
was developed by the core Bndtools
team as part of the bndtools.core
project. It acts as a bridge between the
Eclipse extension registry and OSGi Declarative Services components. Thus it
allows you to deploy Eclipse extensions in your plugins that are implemented as
OSGi Declarative Services components.
Eclipse extensions tend to be quite static. Although in theory the ExtensionRegistry was meant to allow dynamic extensions, in practice the assumption that the extension lifecycle is equal to the workbench lifecycle has been accidentally baked in to many Eclipse plugins (including core Eclipse plugins). This means that in practice you can't restart a plugin in a running Eclipse instance if it registers extensions.
The main purpose of ExtensionFacade
is to allow extension implementations
to be decoupled a bit from the registry, allowing the ExtensionRegistry to be
shielded from the dynamic nature of the underlying implementation. This allows
you to develop Eclipse plugins with extension implementations that can be
dynamically restarted without requiring a restart of the entire Workbench.
This feature will most of the time not be noticed by the average Eclipse user. However, it is extremely useful to the Eclipse plugin developer who uses Bndtools as their plugin development platform, as it enables you to make use of Bndtools' live coding/testing feature. This significantly improves the rate at which you can develop/deploy/test changes to your plugin.
There are two ways to use the ExtensionFacade
:
- Factory mode - in this case, the
ExtensionFacade
simply acts as a factory object, directly instantiating and returning the extension implementation objects. If the backing component service is not available, it will throw a runtime exception at instantiation time. - Full proxy mode - in this case, the
ExtensionFacade
returns a dynamic proxy implementation of the extension interface. If the backing component is not available, then the proxy implementation will throw a runtime exception any time one of its methods is invoked.
A comparison of the advantages of each mode follows.
The full proxy mode presents the same extension object to the rest of the system for the life of the Workbench. This makes it more suitable when you have components in the system that are hanging on to references to the extension objects for extended periods, as it will allow the backing component object instances to be cleaned up. For example, if the extension client caches the reference to the extension object in a static class variable, there is no easy way to clear that reference when your service restarts. Using the proxy mode, the extension client's cache keeps the reference to the proxy, while the backing service is allowed to come-and-go as its bundle is stopped/started.
However, there are times when the full proxy mode won't work:
- When the clients of the extension make assumptions about the concrete type
of the extension object. An example is in the source lookup code, which at one
point specifically looks for a subclass of
AbstractSourceLookupDirector
- even if your backing service extendsAbstractSourceLookupDirector
, the returned proxy object does not. - The proxy mode makes use of Java's dynamic proxies, and these can only be used when the base extension type is an interface. Some of the extension objects in Eclipse (eg, builders) have a class or an abstract class as their base class.
The full proxy mode also involves a theoretical performance overhead for requests (though this is not likely to be of practical significance).
The factory mode directly returns a reference to the backing component service
every time create()
is called. This means that the returned object it will
be the exact same type as the actual extension object. This means it doesn't
have the limitations that the proxy mode has - it can be used with extensions
that have a class as their base type, and it will not cause code to break if it
is expecting a particular concrete subtype.
The biggest problem with the factory mode is that it loses control over the lifecycle of the extension object. It is mostly useful where the lifecycle can be controlled in some other manner, otherwise you may not be able to release references to stale components when their implementation bundle is stopped or be able to instantiate the new implementation after the freshly-installed bundle is started.
Generally speaking, you will most likely get smoother results using the proxy mode when you can, and use the factory mode as a fallback if you run into one of the proxy mode's limitations.
There also exists the possibility of extensions where neither approach will work
properly - eg, a client of an extension that has a class as its base type, but
the client also hangs on to references in a static variable. Unfortunately in
this case you'll have to abandon the use of the ExtensionFacade
altogether.
- Register your extension in the
plugin.xml
of a bundle that won't be restarted. For Bndtools, the best choice is probablybndtools.core
.- The
id
attribute of the extension point element must match thecomponent.name
property of the backing component. - In the attribute where you normally put the fqn of your implementation class,
instead put the fqn of the facade, ie
org.bndtools.facade.ExtensionFacade
. This name of this attribute depends on the extension point, that you're implementing, but it's usuallyclass
ordelegate
. - If you are using the proxy mode, add a ':' and then the fqn of the
extension base type to the
ExtensionFacade
attribute.
- The
<launchConfigurationType
id="bndtools.launch.OSGiRunLaunchDelegate"
delegate="org.bndtools.facade.ExtensionFacade:org.eclipse.debug.core.model.ILaunchConfigurationDelegate2"
...
- Implement your extension in your implementation bundle. The implementation
bundle should not register any extensions in its
plugin.xml
otherwise you will not be able to restart it.- Create a Declarative Services component that implements/extends the base type of the extension that you are implementing.
- Ensure that the
component.name
property is the same as theid
field in theplugin.xml
of the plugin that registers the extension (set in step 1.i). By default, this is the fqn of the implementation class. (Note: there are some cases where you might need theid
to be different from thecomponent.name
- in such cases, see the section "Overriding thecomponent.name
" below.) - Set the correct component scope. Usually the component will need to be prototype scope, otherwise the implementation instances will interfere with each other - however, if your component implementation is stateless, then you can use default scope and all of the extension objects will share the same backend implementation.
That's all that is needed. With this setup, you can restart your implementation bundle dynamically without breaking your running Eclipse workbench.
Sometimes in your Eclipse plugin code you will want to directly instantiate a component implementation programmatically. However, this creates a hard dependency from your plugin on implementation bundle, so that you can't restart the implementation bundle unless your plugin also restarts. If your plugin is designed to be able to restart dynamically then this will not be a problem, but if it registers its own Eclipse plugins then it will prevent you from restarting your implementation bundle.
A way around this is to use the ExtensionFacade
programmatically to
reference the extension object. An example is in
bndtools.editor.pages.RunAction
. As a nice additional feature, you
can then programmatically register callbacks on the facade to be notified when
a new backing component arrives or is closed. RunAction
uses this feature
to enable/disable itself depending on whether or not the backing component is
present.
By default, the ExtensionFacade
will use the extension element's id
attribute to
look for a DS component with a component.name
that matches. However, sometimes you
want two extensions that point to the same DS component. Because having extensions with
duplicate ids is not a good idea, the ExtensionFacade
provides an alternative method
to specify the component.name
to match: by adding :<component.name>
to the end
of the class.
If you need to combine the factory mode with this feature, leave the name of the class
blank (eg, class="org.bndtools.facade.ExtensionFacade::my.component.name"
).
The prototype example of how to use the ExtensionFacade
is in the
org.bndtools.launch
bundle, which contains the launch-related code
for Bndtools. This bundle was used as a proving ground for the
ExtensionFacade
's initial development. The extensions are registered in
bndtools.core
and bndtools.m2e
's plugin.xml
files, using the
ExtensionFacade
configured as described above.