Book Image

Eclipse Plug-in Development Beginner's Guide - Second Edition

By : Alex Blewitt
Book Image

Eclipse Plug-in Development Beginner's Guide - Second Edition

By: Alex Blewitt

Overview of this book

Eclipse is used by everyone from indie devs to NASA engineers. Its popularity is underpinned by its impressive plug-in ecosystem, which allows it to be extended to meet the needs of whoever is using it. This book shows you how to take full advantage of the Eclipse IDE by building your own useful plug-ins from start to finish. Taking you through the complete process of plug-in development, from packaging to automated testing and deployment, this book is a direct route to quicker, cleaner Java development. It may be for beginners, but we're confident that you'll develop new skills quickly. Pretty soon you'll feel like an expert, in complete control of your IDE. Don't let Eclipse define you - extend it with the plug-ins you need today for smarter, happier, and more effective development.
Table of Contents (24 chapters)
Eclipse Plug-in Development Beginner's Guide Second Edition
Credits
Foreword
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

Registering a service declaratively


Registering services imperatively in the start method of an Activator method is one way of installing services in an OSGi framework. However it requires that the bundle be started, which requires that either the bundle is started automatically or has classes (such as API classes) accessed by default. Both approaches will mean that additional code has to run to bring the system into the desired state.

An alternative is to use one of the declarative service approaches, which represent the service definition in an external file. These are processed using an extender pattern, which looks out for bundles with a given file or files and then instantiates the service from this definition. It combines the declarative nature of the extension registry with the flexibility of OSGi services.

There are two providers of declarative service support, which both achieve a similar result but use slightly different configuration files and approaches. They are Declarative Services and Blueprint. Since the most common use case for Eclipse plug-ins is to use Declarative Services, they will be covered here (Blueprint is covered in Mastering Eclipse Plug-in Development by the same author).

Declarative Services

Declarative Services, or simply DS, was the original declarative implementation for instantiating services in a declarative fashion in an OSGi runtime. Both Equinox and Felix have DS modules, and it is a required part of the Eclipse 4 runtime, so can be trivially expected to be present. In the OSGi specification, it is referred to as the Services Component Runtime (SCR), which is why the associated package names use org.osgi.service.component.

The DS bundle needs to be started before it can process bundles; as a result, it is typically started early on. It listens to bundles being installed and then looks for a specific header in the META-INF/MANIFEST.MF:

Service-Component: OSGI-INF/*.xml

If the DS bundle finds this header, it looks for files contained in the bundle itself matching the file pattern specified. This is a comma-separated list, and can use a single wildcard * character (which will match file names but not directories).

The service document is then loaded and parsed, and used to instantiate and register services with the OSGi runtime environment. The XML document uses namespaces to represent the component, using http://www.osgi.org/xmlns/scr/v1.2.0. Different versions of SCR use different endings; v1.0.0 is defined as the first version, with v1.1.0 the second. The current version (as of the writing of this book) uses the suffix v1.3.0.

Each service document defines a single service, which has an implementation class as well as an identifier. The service can be registered under one or more interfaces, as well as optional priorities.

This can be used to remove the custom code in the TimeZonesActivator created previously, by deleting the class and removing the Bundle-Activator from the manifest.

If the application is run now, the service won't be registered. To register these as OSGi services declaratively, create a file called OSGI-INF/timezones.xml:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
 name="TimeZonesProvider">
 <implementation
  class="com.packtpub.e4.timezones.internal.TimeZonesProvider"/>
  <service>
   <provide
    interface="com.packtpub.e4.timezones.TimeZonesService"/>
  </service>
</scr:component>

Tip

Don't forget to tell Eclipse to consider this part of the build by adding OSGI-INF/ to the build.properties file in the bin.includes property.

As long as a declarative services provider is installed in the application and started, the service will be created on demand.

Note

Client bundles should be able to express a dependency on a Declarative Services provider by using the following requirement in the manifest:

Require-Capability:
 osgi.extender;osgi.extender="osgi.component"

However, this is not yet implemented in Eclipse.

The launch configuration can detect whether or not declarative services are installed with the Validate Bundles button, but at the moment the Add Required Bundles button does not resolve the problem. At present the org.eclipse.equinox.ds bundle must be resolved manually to fix this problem. It will also require org.eclipse.equinox.util and org.eclipse.osgi.services to be added to the launch configuration.

Properties and Declarative Services

Declarative Services can also be used to register properties with the service when it is registered. These properties can be sourced either from the services XML file or an external properties file.

To add the service.ranking property to the registered service, add the following into the services document:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
 name="TimeZonesProvider">
  ...
  <property name="service.ranking" type="Integer" value="2"/>
</scr:component>

When the application is restarted, the services console command will show that the service.ranking property is associated with the TimeZonesService:

osgi> services | grep timezones
{com.packtpub.e4.timezones.TimeZonesService}=
 {service.ranking=2,
  component.name=TimeZonesProvider,
  component.id=0,
  service.id=48,
  service.bundleid=11,
  service.scope=bundle}

Tip

If the property isn't listed, add a -clean argument to the Eclipse runtime console; sometimes the files are cached and PDE doesn't always notice when files are changed.

The property types can be one of:

  • String (default)

  • Long

  • Double

  • Float

  • Integer

  • Byte

  • Character

  • Boolean

  • Short

Additionally, arrays of elements can be specified by placing them in the body of the element instead of as an attribute:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
 name="TimeZonesProvider">
  ...
  <property name="compass.point" type="String">
    NORTH
    EAST
    SOUTH
    WEST
  </property>
</scr:component>

Service references in Declarative Services

As well as hard-coded values, it is also possible to set up references to services in DS. The service implementation can have bind and unbind methods, which are called when a service becomes available or goes away.

These can be mandatory or optional; if the dependency is mandatory then the service is not presented until its dependencies are available. If they are optional, the service can come up and be assigned later. They can also be single-valued or multi-valued. These are encoded in the relationships cardinality:

  • 0..1: This service is optional, either zero or one instance needed

  • 1..1: This service is mandatory, exactly one instance needed (default)

  • 0..n: This service is optional, may have zero or more instances

  • 1..n: This service is mandatory, may have one or more instances

This can be used to inject a LogService (from the org.osgi.service.log package; it may be necessary to add this as an imported package to the bundle) into the component. Modify the TimeZonesProvider to accept an instance of the LogService by adding a setLog and unsetLog method:

private LogService logService;
public void setLog(LogService logService) {
  this.logService = logService;
}
public void unsetLog(LogService logService) {
  this.logService = null;
}

This can be used to report on how many time zones are loaded in the getTimeZones method of the TimeZonesProvider class:

if (logService != null) {
  logService.log(LogService.LOG_INFO,
   "Time zones loaded with " + timeZones.size());
}

To configure DS to provide a log service, the following must be added to the timezones.xml file:

<scr:component name="TimeZonesProvider"
 xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
 ...
 <reference interface="org.osgi.service.log.LogService"
  cardinality="0..1" name="log" 
  bind="setLog" unbind="unsetLog"/>
</scr:component>

This tells DS that the log service is optional (so it will bring the service up before a LogService is available), and that setLog(log) will be called when it is available. DS also provides an unbind method which can be used to remove the service if it goes away. The instance is provided for both the setLog and unsetLog method, which may look strange—but when setting multiple elements the methods are typically called addThing and removeThing, which may be more appropriate.

Multiple components and debugging Declarative Services

Although this seems to imply that an XML file can only contain one component, in fact an XML parent element can be defined with multiple scr namespaced children. Since all elements outside the scr namespace are ignored, it is possible to embed an XHTML document with an scr namespaced element inside, and still have it picked up by Declarative Services:

<xhtml>
  <h1>Example HTML file with SCR elements</h1>
  <h2>Component One</h2>
  <scr:component name="One" xmlns:scr="http://...">
    …
  </scr:component>
  <h2>Component Two</h2>
  <scr:component name="Two" xmlns:scr="http://...">
    …
  </scr:component>
</xhtml>

Note that many developers will use a one-to-one mapping between service components and corresponding XML files; it is rare to see a single XML file with multiple service components. It is recommended to only put one component per XML file for ease of maintenance.

Tip

When using DS inside Equinox, using -Dequinox.ds.print=true gives additional diagnostic information on the state of the declarative services, including highlighting what services are waiting. For Felix, specifying -Dds.showtrace=true can increase logging, as can -Dds.loglevel=4.

Dynamic Service annotations

Although XML allows for flexibility, it has fallen out of fashion in the Java community in favor of Java annotations. The 1.2 version of the OSGi DS specification provides annotations that can be used to mark the code such that a build time processor can create the service component XML files automatically.

Tip

Note that the standard OSGi annotations are not read at runtime by the service but only build-time tools such as maven-scr-plugin. As a result they should be optionally imported, since they aren't needed at runtime, or with compile scope if using a Maven-based build.

To use the annotations, add the following as an Import-Package for the bundle in the MANIFEST.MF:

Import-Package:
 org.osgi.service.component.annotations;
 version="1.2.0";
 resolution:=optional

The @Component annotation can now be added to the individual classes that should be represented as services. Add this to the TimeZonesProvider:

import org.osgi.service.component.annotations.Component;
@Component(name="TimeZonesProvider2",
 service={TimeZonesService.class},
 property={"service.ranking:Integer=1"})
public class TimeZonesProvider implements TimeZonesService {
  ...
}

Processing annotations at Maven build time

If using Maven Tycho to build bundles, it is possible to add a Maven plug-in to generate the service xml files from components. Maven Tycho is covered in more detail in Chapter 12, Automated Builds with Tycho.

To configure the maven-scr-plugin to a build, first add the following dependency to the pom.xml file:

<dependencies>
  <dependency>
    <groupId>org.apache.felix</groupId>
    <artifactId>org.apache.felix.scr.ds-annotations</artifactId>
    <version>1.2.8</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

This both provides the org.osgi.service.component.annotations classes as well as the processing engine necessary to generate the components. Note that even if other dependencies are given (say, osgi.enterprise or equinox.ds) this isn't sufficient on its own to generate the service.xml files.

Next the plugin needs to be added to the pom.xml file:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-scr-plugin</artifactId>
      <version>1.22.0</version>
      <configuration>...</configuration>
      <executions>...</executions>
    </plugin>
  </plugins>
  <sourceDirectory>src</sourceDirectory>
</build>

The source directory needs to be specified to match the value of the source attribute of the build.properties file (which is used by eclipse-plugin instead of sourceDirectory), as otherwise the maven-scr-plugin cannot find the source files.

The plug-in needs to be configured specifically for eclipse-plugin projects. Firstly, the supported projects default to jar and bundle for the maven-scr-plugin, so it needs to be given additional configuration to permit processing eclipse-plugin projects.

Secondly the service files are written to target/classes/ by default. Although this will work, it makes for more difficult debugging in Eclipse. Instead, the maven-scr-plugin can be configured to write it to the project root, which will place the service files under OSGI-INF. This permits the code to be tested in Eclipse as well as exported using the standard build tools:

<configuration>
  <supportedProjectTypes>
    <supportedProjectType>eclipse-plugin</supportedProjectType>
  </supportedProjectTypes>
  <outputDirectory>${basedir}</outputDirectory>
</configuration>

Finally, to hook it in with the standard build process, add the following to the build:

<executions>
  <execution>
    <id>generate-scr</id>
    <goals>
      <goal>scr</goal>
    </goals>
  </execution>
</executions>

Now when the package is built, the service descriptor xml file will be automatically be regenerated based on the annotations. The file name is derived from the class name.