Konstantin Komissarchik (Oracle)
Last Updated on 2009-10-28
An amazing variety of ways of packaging and referencing Java libraries have developed over the years. This creates some interesting challenges for tooling writers as it is often impossible for the tooling author to account for all the conceivable options. Some of the common patterns include the following:
Faced with this complexity, it is tempting to throw up one's hands and leave library configuration completely up to the tooling user. This approach, of course, is doing users a disservice as it creates unnecessary barriers for novice users and fails to make life easier for advanced users.
The middle ground between leaving library configuration completely in users hands and locking it down to one particular approach requires a system that allows a third party to supply library configuration logic for tooling written by someone else. Since the type of work that needs to happen to configure libraries varies widely, the API needs to allow for hand-off from tooling to the library provider. No assumptions can be made about what work the library provider will need to do. You cannot even assume that project classpath will be modified. Many library providers also need to be able to interact with users to gather required configuration and to provide validation feedback. This implies letting the library provider supply a UI panel that should be seamlessly integrated into the rest of the configuration UI for the technology.
This article describes the Library Provider Framework, which is a new extension for Galileo to the Faceted Project Framework. The association of library providers with facets is natural as library providers have to be associated with a particular technology in order to be most useful and facets provide an increasingly standard way for enabling various technologies on project-by-project basis. While both frameworks are currently part of the Eclipse Web Tools Project, they are free of dependencies on other WTP plugins and are even packaged into a separate distribution for ease of consumption.
Table of Contents
If you want to try out the library provider framework or run the examples presented in this article, you will need to get the appropriate Eclipse packages. This article is written for Galileo SR1 release. Here are two of the most common options:
Throughout this article, the class names are frequently not qualified for brevity. All of the referenced classes will be found in one of these plugins:
A single example scenario that is setup around a fictional MyTech library is used throughout this article. You can download all of the example source code or just follow links from code links to view the relevant parts.
The Library Provider Framework includes various API to make it as easy as possible for facets to integrate with it. Two key API classes are LibraryInstallDelegate and LibraryUninstallDelegate. These delegates both hold the configuration state and can be executed. The library delegates need to get hand-off in all facet actions. The facet must create and initialize library delegates in it's action config objects. It is then responsible for calling execute on the library delegate when the facet action is executed.
The LibraryInstallDelegate may create and execute a LibraryUninstallDelegate if it detects presence of prior library configuration for the facet. This makes the LibraryInstallDelegate quite flexible. It is used in facet install action, facet version change action and even in facet property page.
The easiest approach when implement facet actions is to subclass LibraryFacetInstallConfig for facet's INSTALL and VERSION_CHANGE actions and to subclass LibraryFacetUninstallConfig for facet's UNINSTALL action. This will ensure that the library delegates have been properly created and initialized. Both of these classes can also be used directly if the facet action has no further configuration besides library configuration.
This examples shows the facet install delegate for the mytech facet. Notice the hand-off to the library install delegate's execute method. If a facet action has more work to do besides library configuration that can happen before or after this call. The uninstall and version change delegates look very similar.
public final class MyTechFacetInstallDelegate implements IDelegate
{
@Override
public void execute( final IProject project, final IProjectFacetVersion fv,
final Object config, final IProgressMonitor monitor )
throws CoreException
{
monitor.beginTask( "", 1 );
try
{
final LibraryFacetInstallConfig cfg = (LibraryFacetInstallConfig) config;
cfg.getLibraryInstallDelegate().execute( new SubProgressMonitor( monitor, 1 ) );
}
finally
{
monitor.done();
}
}
}The next step is to embed the library provider UI into the facet's install and version change action pages. Similarly to the action config classes, the easiest way of doing this is to subclass LibraryFacetInstallPage class. If the action page needs to display other controls besides the library provider UI, the createPageContents and performValidation methods can be overridden.
This example shows a very simple facet install page class. This is all that is necessary to display the library provider UI. The version change page class looks very similar.
public final class MyTechFacetInstallPage extends LibraryFacetInstallPage
{
public MyTechFacetInstallPage()
{
super( "mytech-install" );
setTitle( "MyTech" );
setDescription( "Enables the use of MyTech framework in the project." );
}
}The final step is to create a property page for the facet where library configuration can be changed outside of facet lifecycle events. Once again the framework makes this task easier by providing LibraryFacetPropertyPage class to subclass. If the property page needs to display other controls besides the library provider UI, the createPageContents, performValidation, performApply and performDefaults methods can be overridden.
This example shows a very simple facet property page class. This is all that is necessary to display the library provider UI.
public final class MyTechFacetPropertyPage extends LibraryFacetPropertyPage
{
@Override
public IProjectFacetVersion getProjectFacetVersion()
{
final IFacetedProject fproj = getFacetedProject();
return fproj.getInstalledVersion( ProjectFacetsManager.getProjectFacet( "mytech" ) );
}
}
<extension point="org.eclipse.ui.propertyPages">
<page
adaptable="true"
class="mytech.tools.facet.MyTechFacetPropertyPage"
id="mytech.tools.MyTechFacetPropertyPage"
name="MyTech"
objectClass="org.eclipse.core.resources.IProject">
<enabledWhen>
<adapt type="org.eclipse.core.resources.IProject">
<test
forcePluginActivation="true"
property="org.eclipse.wst.common.project.facet.core.projectFacet"
value="mytech"/>
</adapt>
</enabledWhen>
</page>
</extension>| ID: | user-library-provider |
| Priority: | 500 |
| Label: | User Library |
| Use: | Abstract. Must be extended to be used. |
One of the most basic library configuration patterns revolves around the user library feature of JDT. A user library is simply a named collection of JARs present somewhere on the local system whose definition is stored in the workspace. The project references a user library by name thus avoiding references to machine-specific paths in project metadata.
This framework includes a library provider that leverages the user library feature. When the user selects this library provider, the list of user libraries defined in the workspace is presented for selection. The user can select one or more of these libraries to fill the facet's library requirement. The user can also create a new user library by pointing at JARs already present on the local system or by using the handy download wizard to pull the JARs down from places known to host them. More on this later.
Correct library configuration is assured by supplying a validator in the library provider definition. A typical condition to validate for is presence of a certain key class, but custom validation rules can be written as well. If your scenario fits the pattern, you can avoid writing your own validator by referencing KeyClassesValidator supplied with the framework. Parameters to this validator are full names of classes to check for. A typical pattern is to select one or two that will give you reasonable certainty that the user has the correct library. Specifying more key classes is certainly possible, but there is a performance tradeoff.
| Parameter | Purpose |
|---|---|
| validator | Associates a validator with the library provider. The value of this parameter is the name of a class that extends the UserLibraryValidator class. |
| validator.param.x | Specifies parameters that should be provided to the validator. The x in the parameter name is a counter that starts with zero. |
This example shows a definition of a user library provider for a fictitious mytech facet. Notice the use of the KeyClassesValidator to verify the selected libraries.
<extension point="org.eclipse.jst.common.project.facet.core.libraryProviders">
<provider id="mytech-user-library-provider" extends="user-library-provider">
<param name="validator" value="org.eclipse.jst.common.project.facet.core.libprov.user.KeyClassesValidator"/>
<param name="validator.param.0" value="mytech.framework.MyTechFramework"/>
<param name="validator.param.1" value="mytech.extensions.ExtensionsManager"/>
<enablement>
<with variable="requestingProjectFacet">
<test property="org.eclipse.wst.common.project.facet.core.projectFacet" value="mytech" forcePluginActivation="true"/>
</with>
</enablement>
</provider>
</extension>One of the ways that you can make the user library provider even more useful is by registering known locations where the required library can be downloaded. That will allow the user a very simple way to download the library and to configure a user library around it, all from the included wizard without leaving your facet's configuration screen.
Registering downloadable libraries is not hard. You first create a catalog (XML Schema) that describes the libraries by specifying their locations on the web, any licenses associated with their use and various other information about how to present them to the user and what to do with them once they are downloaded. The second step is to decide where the catalog is going to be located. There are two alternatives. You can place the catalog in the plugin or you can host it on the web. The second alternative is more flexible as it is possible to update the catalog without updating the tooling plugins. The final step is to use the downloadableLibraries extension point to register the catalog with an enablement condition. The enablement condition controls when the entries from the catalog are visible in the download library wizard. The most typical enablement condition is to check for a particular facet.
This example shows registration of two library catalogs corresponding to the two versions of the mytech facet. Splitting the libraries into two catalogs ensures that when the user is trying configure mytech 1.5, the 1.0.x versions of the mytech library are not presented in the download wizard.
<catalog>
<library>
<name>MyTech 1.0</name>
<download-provider>Eclipse Foundation</download-provider>
<download-url>http://www.eclipse.org/fproj/articles/libprov/1/mytech/lib/mytech-1.0.0.zip</download-url>
<license-url>http://www.eclipse.org/legal/epl-v10.html</license-url>
</library>
<library>
<name>MyTech 1.0.1</name>
<download-provider>Eclipse Foundation</download-provider>
<download-url>http://www.eclipse.org/fproj/articles/libprov/1/mytech/lib/mytech-1.0.1.zip</download-url>
<license-url>http://www.eclipse.org/legal/epl-v10.html</license-url>
</library>
</catalog>
<catalog>
<library>
<name>MyTech 1.5</name>
<download-provider>Eclipse Foundation</download-provider>
<download-url>http://www.eclipse.org/fproj/articles/libprov/1/mytech/lib/mytech-1.5.0.zip</download-url
<license-url>http://www.eclipse.org/legal/epl-v10.html</license-url>
</library>
</catalog>
<extension point="org.eclipse.jst.common.project.facet.core.downloadableLibraries">
<import-definitions url="http://www.eclipse.org/fproj/articles/libprov/1/mytech/lib/catalog-1.0.xml">
<enablement>
<with variable="requestingProjectFacet">
<test property="org.eclipse.wst.common.project.facet.core.projectFacet" value="mytech:1.0" forcePluginActivation="true"/>
</with>
</enablement>
</import-definitions>
<import-definitions url="http://www.eclipse.org/fproj/articles/libprov/1/mytech/lib/catalog-1.5.xml">
<enablement>
<with variable="requestingProjectFacet">
<test property="org.eclipse.wst.common.project.facet.core.projectFacet" value="mytech:1.5" forcePluginActivation="true"/>
</with>
</enablement>
</import-definitions>
</extension>| ID: | osgi-bundles-library-provider |
| Priority: | 600 |
| Label: | none |
| Use: | Abstract. Must be extended to be used. |
Sometimes it is convenient to distribute libraries that are required for user applications alongside the tooling as OSGi bundles. This approach is particularly useful when tooling needs to access those same libraries as well. One of the convenient properties of OSGi bundles is that they can also be used as simple JAR files as long as the code inside the bundles doesn't reference any OSGi facilities. This is often the case when libraries get packaged as OSGi bundles.
The library provider framework includes a provider that can be configured to look for specified OSGi bundles in the running configuration of user's Eclipse instance and to add these bundles to project classpath. The classpath entry that is added by this provider dynamically resolves the bundles, which maintains project portability. One additional feature of this library provider is that it will only become enabled for selection (visible to the user) when the specified bundles are available. This is useful in cases where tooling may or may not include the library bundles depending on the package that user selects.
| Parameter | Purpose |
|---|---|
| bundle.x | Specifies the bundles to be referenced. The x in the parameter name is a counter that starts with zero. This allows multiple bundles to be specified. The value is either just a bundle id or [bundle-id]:[version-range]. |
| container.label | The label to use for the classpath entry that is added by this library provider. This label will be visible in Project Explorer and other places where classpath entries are displayed. The default value is OSGi Bundles (facet). |
This example shows registration of an OSGi bundles library provider for mytech library. Note the use of facet version in requestingProjectFacet contraint and the corresponding version ranges on the bundle references. This approach will make sure that only the bundles of appropriate version will be resolved in this context.
<extension point="org.eclipse.jst.common.project.facet.core.libraryProviders">
<provider id="mytech-osgi-bundles-library-provider" extends="osgi-bundles-library-provider">
<label>MyTech Library from Eclipse</label>
<enablement>
<with variable="requestingProjectFacet">
<test property="org.eclipse.wst.common.project.facet.core.projectFacet" value="mytech:1.5" forcePluginActivation="true"/>
</with>
</enablement>
<param name="bundle.0" value="mytech.framework:[1.5,2.0)"/>
<param name="bundle.1" value="mytech.extensions:[1.5,2.0)"/>
</provider>
</extension>| ID: | no-op-library-provider |
| Priority: | -1000 |
| Label: | Disable Library Configuration |
| Use: | Abstract. Must be extended to be used. |
In certain cases it is desirable to give users an option to opt-out from library configuration. Some advanced users have come up with quite elaborate constructs for managing libraries and it is frequently not cost effective or feasible to support all of those cases with custom library providers. The no-op library provider is a way to disable library configuration for a facet. To discourage novice users from going down this path and getting into trouble, an optional warning can be displayed.
| Parameter | Purpose |
|---|---|
| message | The explanatory message that should be displayed in library provider's configuration panel. Must be specified. |
| warning | An optional warning message to display when the library provider is selected. |
<extension point="org.eclipse.jst.common.project.facet.core.libraryProviders">
<provider id="mytech-no-op-library-provider" extends="no-op-library-provider">
<param
name="message"
value="This facet requires MyTech library to be present on project classpath. By disabling library
configuration, user takes on responsibility of configuring classpath appropriately via alternate means."/>
<param
name="warning"
value="Library configuration is disabled. Manual classpath changes may be required."/>
<enablement>
<with variable="requestingProjectFacet">
<test property="org.eclipse.wst.common.project.facet.core.projectFacet" value="mytech" forcePluginActivation="true"/>
</with>
</enablement>
</provider>
</extension>Backwards compatibility is very important for facets that adopt Library Provider Framework after shipping one or more releases using a different approach. There are two aspects to the backwards compatibility story. The first is detecting and dealing with legacy library configurations. This takes work on the part of the facet author and typically should be done at the time that the framework is adopted. The second is integrating with the IClasspathProvider API, a deprecated method for allowing the execution environment to supply libraries to the facet.
| ID: | legacy-library-provider |
| Priority: | n/a |
| Label: | Legacy Library Configuration |
| Use: | Abstract. Must be extended to be used. |
When a facet integrates with the Library Provider Framework after shipping at least one release using a different approach, it is important to provide a transition path to users with existing projects. This is accomplished by writing what's called a legacy library provider detector. A detector identifies project metadata state that is associated with legacy library configurations and constructs the appropriate legacy library provider. A legacy library provider is not something that the user is able to select normally. It remains hidden until constructed by a detector. By their nature, legacy library providers do not include installation logic. Their only responsibility is to enable transition to other library providers by implementing the uninstall action.
| Parameter | Purpose |
|---|---|
| message | Allows the explanatory message that's displayed to the user to be overridden. The default message is this: This project is currently configured to use a legacy method for configuring libraries. Switch to one of the other available library types when it is no longer necessary to open this project in previous versions of Eclipse. |
This example shows the pieces necessary for handling the legacy library configuration for the mytech facet. The first step is to write the detector and to register it with legacyLibraryProviderDetectors extension point.
<extension point="org.eclipse.jst.common.project.facet.core.legacyLibraryProviderDetectors"> <detector class="mytech.tools.facet.libprov.legacy.MyTechLegacyLibraryProviderDetector"/> </extension>
The final step is to define the legacy library provider itself. For brevity, the code listing for the uninstall delegate is not shown. Note the use of the hidden attribute in the library provider definition. This assures that the library provider will not be available for selection under regular circumstances.
<extension point="org.eclipse.jst.common.project.facet.core.libraryProviders">
<provider id="mytech-legacy-library-provider" extends="legacy-library-provider" hidden="true">
<action type="UNINSTALL">
<operation class="mytech.tools.facet.libprov.legacy.MyTechLegacyLibraryProviderUninstallOperation"/>
</action>
</provider>
</extension>| ID: | runtime-library-provider |
| Priority: | 600 |
| Label: | Library Provided by Target Runtime |
| Use: | Concrete. Globaly available. |
Before the advent of the Library Provider Framework, another less flexible API was used to let facets integrate with libraries that are included in the application's execution environment (a.k.a. target runtime). An adapter for IClasspathProvider interface could be registered for a runtime component of a given type. A facet that choose to integrate with IClasspathProvider would call into this adapter asking if the runtime is capable of supplying requisite classpath entries for it. The IClasspathProvider facility is deprecated and should not be used for new code. A similar result can be achieved with more flexibility by writing a library provider.
The Execution Environment Library Provider is the integration point with the legacy IClasspathProvider facility. It is automatically enabled (becomes available for selection) if the execution environment indicates that it is capable of supplying classpath entries for the facet in question. No work is required on the part of the facet author to take advantage of this library provider. It is globably available.
So far in this article you have seen several examples for how to create a library provider by deriving from one of the existing abstract providers. In this section you will learn how to build a library provider from scratch.
Here is the basic schema of the libraryProviders extension point:
<provider id="..." extends="..." abstract="..." hidden="..."> (1 or more)
<label>...</label> (optional)
<priority>...</priority> (optional)
<enablement>...</enablement> (optional)
<param name="..." value="..."/> (0 or more)
<action type="INSTALL|UNINSTALL"> (0 or more)
<config class="..."/> (optional)
<operation class="..."/>
</action>
</provider>
Each library provider is required to have a unique identifier. This identifier must not change after the library provider implementation is shipped as it is referenced in user project metadata.
A library provider can extend another library provider and override or augment its definition. The value of this attribute is the id of the library provider to extend.
Just like with abstract classes in Java, abstract library providers can be used to facilitate code re-use. Typically, abstract library providers are written to be made concrete by supplying configuration parameters and an enablement condition. Abstract library providers are never made available to the user. They simply serve as building blocks for writing library providers that are made available.
Somewhat similar to abstract library providers is the concept of hidden library providers. This feature allows a concrete library provider to be be made unnavailable for normal use. Why is this useful? It allows the library provider to be activated externally under special conditions, as is necessary for handling legacy library configurations.
The localizable name for the library provider that will be presented to the user.
Controls the importance of a library provider relative to other providers around it. Higher priority gives the the library provider a spot closer to the start of the list of available providers that are presented to the user. The provider with the highest priority in a given context is given the default selection.
One of the most important parts of a library provider declaration is the enablement condition. This condition controls when the library provider is available for selection. If the enablement condition is not specified, the library provider will be always available for all facets, which is unlikely to be the intended behavior. The most typical enablement condition is to restrict the library provider to a particular requesting facet, but other more complex conditions are possible. The enablement condition uses Eclipse Expressions Language. The variables that are available in expression evaluation context are documented in the following table.
| Variable | Description |
|---|---|
| requestingProjectFacet | Holds an IProjectFacetVersion instance of the facet making the request for available library providers. This is the default variable. Use the org.eclipse.wst.common.project.facet.core.projectFacet property tester to check for particular values. |
| projectFacets | Holds a Set<IProjectFacetVersion> containing all facets currently selected on the project. Use the loop constructs of the expressions language to work with this variable. |
| targetedRuntimes | Holds a Set<IRuntime containing all runtimes currently targeted by the project. Use the loop constructs of the expression language to work with this variable. |
| provider | Holds an ILibraryProvider instance whose enablement condition is being evaluated. |
| context | Holds an EnablementExpressionContext instance which contains all of the information available in the expression evaluation context. This variable is particularly useful when writing a custom property tester to check for complex conditions. |
Used for attaching arbitrary name-value pairs to the library provider definitions. The attached parameters are visible to the actions. The parameters is a particularly powerful feature in conjunction with abstract/extends facility. One can create a single implementation of actions and then parameterize those actions appropriately in the derived library providers.
A library provider has two major lifecycle events: INSTALL and UNINSTALL. Other lifecycle events are handled by using combinations of these events. For instance, an "update" of library provider configuration is accomplished by issuing an uninstall event followed by an install event with updated configuration. The library providers tap into the lifecycle events by implementing actions. Every concrete (non-abstract) library provider must have an install and an uninstall action (definited directly by the provider or inherited).
An action is composed of two parts. The operation class extends LibraryProviderOperation and contains the logic for actually performing the action. The operation config class extends LibraryProviderOperationConfig and contains the state of action configuration before it is executed. The operation config object serves as the bridge between UI and the operation. It also holds the validation logic. An operation config class does not need to be specified if the operation is not parameterizable. This is the case with all uninstall actions and even with install actions for some simple library providers. If an operation config is not specified in the action definition, the operation is going to be called with a generic LibraryProviderOperationConfig instance that holds basic context information.
Here is the basic schema of the libraryProviderActionPanels extension point. Note that there is no way to provide the action type. This is because the framework only supports UI panels for install actions. The uninstall actions are expected to run silent and without user configuration.
<panel provider="..." class="..."/> (1 or more)
The ID of the library provider that the UI panel should attach to.
The UI panel implementation class, which must extend from LibraryProviderOperationPanel class.
This example shows a custom library provider implementation that is designed to give user easy access to mytech libraries that may already be available at a pre-defined location on the local system. There is too much code to list it all in this article, but you can follow the links in the partial listing to find the rest of the code.
<extension point="org.eclipse.jst.common.project.facet.core.libraryProviders">
<provider id="mytech-system-library-provider">
<label>MyTech Library from Local System</label>
<priority>1000</priority>
<enablement>
<and>
<with variable="requestingProjectFacet">
<test property="org.eclipse.wst.common.project.facet.core.projectFacet" value="mytech:1.5" forcePluginActivation="true"/>
</with>
<with variable="context">
<test property="mytech.systemLibraryPresent" value="true" forcePluginActivation="true"/>
</with>
</and>
</enablement>
<action type="INSTALL">
<config class="mytech.tools.facet.libprov.system.MyTechSystemLibraryProviderInstallOperationConfig"/>
<operation class="mytech.tools.facet.libprov.system.MyTechSystemLibraryProviderInstallOperation"/>
</action>
<action type="UNINSTALL">
<operation class="mytech.tools.facet.libprov.system.MyTechSystemLibraryProviderUninstallOperation"/>
</action>
</provider>
</extension>
<extension point="org.eclipse.jst.common.project.facet.ui.libraryProviderActionPanels">
<panel
provider="mytech-system-library-provider"
class="mytech.tools.facet.libprov.system.MyTechSystemLibraryProviderInstallPanel"/>
</extension>
<extension point="org.eclipse.core.expressions.propertyTesters">
<propertyTester
id="mytech.systemLibraryPresent"
type="org.eclipse.jst.common.project.facet.core.libprov.EnablementExpressionContext"
namespace="mytech"
properties="systemLibraryPresent"
class="mytech.tools.facet.libprov.system.MyTechSystemLibraryPropertyTester"
</propertyTester>
</extension>
<extension point="org.eclipse.jdt.core.classpathContainerInitializer">
<classpathContainerInitializer
class="mytech.tools.facet.libprov.system.MyTechSystemLibraryContainerImpl$Initializer"
id="mytech.system.library"/>
</extension>This article has shown you how to take advantage of the Library Provider Framework whether you are the facet author or someone tasked with implementing a custom library provider for an existing facet.
An effort is currently underway to migrate this framework together with rest of the Faceted Project Framework to an independent project (technology.fproj). Stay tuned for further developments by monitoring the project's forum.
Please direct any questions and comments to project's forum.