Introducing eTrack

eTrack is an application that manages tasks. This project is meant to be a comprehensive tutorial that demonstrates the use of several Eclipse technologies, and current best practices for architecture, design, development, testing, build, and deployment.  I would like the architecture to be as open as possible to accommodate different implementations of the various components of the application.  There will, however, be a couple of things set in stone.  The application will be based on OSGi, have a RESTful client / server architecture, and will use HTTP to communicate between client and server.

To get started, the initial implementation will use EMF for the domain model, MongoDB for the backing store, Restlet for the RESTful web services, Jetty for the web container, and simple HTML web pages for the client.  It would also be nice, at some point, to have a GWT client, a SproutCore client, and Mylyn integration.

The project is hosted at EclipseLabs and the place to start is:

http://code.google.com/a/eclipselabs.org/p/etrack/

I would like to use Gerrit for source control, and Tycho and Jenkins for build, but I’m currently not aware of any free hosting for these services.  If I can’t find free hosting, I might consider hosting on my home server and mirroring the source on GitHub for public access.  Issue tracking will initially be done on the EclipseLabs Issues page until we can “eat our own cooking”.

Here is a first attempt at a domain model (I’ve taken a little liberty with the UML notation in that solid diamonds denote containment within the same resource while open diamonds denote cross-document containment):

DomainI hope to start working on the implementation at EclipseCon.  If you are interested in participating, please comment below describing what you would like to work on, or find me at EclipseCon and I’ll be happy to discuss this with you.

Mongo EMF

If you have worked with the Eclipse Modeling Framework (EMF), then you already know how easy it is to create domain models.  If you haven’t, then I highly recommend giving it a try.  I develop all of my domain models using EMF.  EMF has built-in support for persisting models to files in XMI, XML, and binary format.  The persistence API is highly extensible, and I’ve recently been working with Ed Merks on some code that persists EMF models to MongoDB.  The project is called mongo-emf and is hosted on EclipseLabs. Version 0.3.1 is being used in a real application and is fairly stable.  This version supports all of the basic CRUD functions along with a basic query language for finding objects.  This blog post will cover the CRUD functions while Ed Merks will have a blog post soon on using the query model.

One feature that I hope you will find attractive is that there are no annotations or XML configuration files required.

If you are attending EclipseCon 2011, I will be giving a talk titled Hardware Developer’s Workbench: a Case Study in which I will discuss how we are using this technology in our application.

Quickstart

Here’s an example on how easy it is to insert an object into MongoDB using EMF.

ResourceSet resourceSet = new ResourceSetImpl();
EList uriHandlers = resourceSet.getURIConverter().getURIHandlers();
uriHandlers.add(0, new MongoDBURIHandlerImpl());

Person user = ModelFactory.eINSTANCE.createMyObject()
user.setName("Test User");
user.setEmail("test@example.org");

Resource resource = resourceSet.createResource(URI.createURI("mongo://localhost/app/users/"));
resource.getContents().add(user);

try
{
  resource.save(null);
}
catch(IOException e)
{
  e.printStackTrace();
}

Let’s look at the code above in detail.  Line 1 is the normal way you create an EMF ResourceSet.  Lines 2 and 3 hook the MongoDB URI handler that interfaces EMF to MongoDB to the ResourceSet. Lines 5 – 7 set up the model instance. Line 9 creates the EMF resource using a URI that has a specific pattern explained below.  Line 10 simply adds the model instance to the EMF Resource.  Line 14 saves and inserts the model instance to MongoDB.

By adding just two additional lines of code and using a mongo URI, you are able to utilize the full power of EMF with your objects persisted in MongoDB.

Mongo EMF URIs

All EMF resources are identified by a URI.  The URI you use when persisting models to MongoDB must have the form:

mongo://<host>/<database>/<collection>/<id>

  • host is the hostname of the server running MongoDB (the port may be specified if not default)
  • database is the name of the MongoDB database to use
  • collection is the name of the MongoDB collection
  • id is the unique identifier of the object in MongoDB (optional on create / insert)

The URI path must have exactly three segments. Anything else will cause an IOException on a load() or save().

Create

When inserting a new object into MongoDB, the id segment of the URI is optional and typically the empty string.  By default, MongoDB assigns a unique ID to an object when it is inserted into the database. In this mode, the URI of the EMF resource is automatically updated to include the MongoDB generated ID value.  Going back to the example in the Quickstart, the resource URI will be similar to mongo://localhost/app/users/4d6dc268b03b0db29961472c after the call to save() on line 14.

It is also possible for the client to generate the ID and persist the object to MongoDB using that ID.  The ID can be any string value that is unique within the MongoDB collection.  The quickstart example above would be modified as follows:

Resource resource = resourceSet.createResource(URI.createURI("mongo://localhost/app/users/1"));
resource.getContents().add(user);

try
{
  resource.save(null);
}
catch(IOException e)
{
  e.printStackTrace();
}

Retrieve

To retrieve an object from MongoDB, you load an EMF Resource using the unique URI for the object.

ResourceSet resourceSet = new ResourceSetImpl();
EList uriHandlers = resourceSet.getURIConverter().getURIHandlers();
uriHandlers.add(0, new MongoDBURIHandlerImpl());

Resource resource = resourceSet.getResource(URI.createURI("mongo://localhost/app/users/4d6dc268b03b0db29961472c"), true);
Person user = (Person) resource.getContents().get(0);

Update

To update an object in MongoDB, you simply save the Resource containing the object.

user.setEmail("mongo-emf@example.org);

try
{
  user.eResource().save(null);
}
catch(IOException e)
{
  e.printStackTrace();
}

Delete

To delete an object in MongoDB, you call delete() on the Resource containing the object.

try
{
  user.eResource().delete(null);
}
catch(IOException e)
{
  e.printStackTrace();
}

Collections

A collection of objects may be stored (contained) within a parent object, or stored as individual objects in a MongoDB collection. For objects that are contained as part of a parent object, when you operate on (load, save) the parent object, you operate on (load, save) the entire collection. When a collection is stored as individual objects in a MongoDB collection, each object is contained its own EMF Resource and must be managed individually by the client. If you allow MongoDB to generate the object ID, you may bulk insert a collection of objects in a single save() call. The resource is modified to contain a single Result result object with a proxy to each of the inserted objects. You may iterate over the proxies to load each object into its own Resource. Here is an example of bulk insert:

ResourceSet resourceSet = new ResourceSetImpl();
EList uriHandlers = resourceSet.getURIConverter().getURIHandlers();
uriHandlers.add(0, new MongoDBURIHandlerImpl());

Resource resource = resourceSet.createResource(URI.createURI("mongo://localhost/app/users/"));

for(int i = 0; i < 10; i++)
{
  Person user = ModelFactory.eINSTANCE.createMyObject()
  user.setName("User " + 1);
  user.setEmail("user" + i + "@example.org");
  resource.getContents().add(user);
}

try
{
  resource.save(null);
}
catch(IOException e)
{
  e.printStackTrace();
}

Result result = resource.getContents().get(0);

for(EObject eObject : result.getValues()
{
  Person user = (Person) eObject;
  System.out.println("Person " + user.getName() + " has URI: " + user.eResource.getURI());
}

EMF References

The way in which EMF references are persisted in MongoDB depends on the settings you specified when you created your EMF Ecore and Generator models. Two Ecore settings that affect persistence are: Containment, and Resolve Proxies. The Generator setting that affects persistence is: Containment Proxies. There are three types of references to consider: non-containment, containment, and bi-directional cross-document containment.

Non-containment References

A non-containment reference is modeled by setting Containment = false in the Ecore model. A non-containment reference can be to any other object in the database, a file, or on some other server. The target object may be contained by the referencing object, or could be in some other resource. Non-containment references are always persisted as a proxy. If the target object is in a separate resource from the referencing object, the target object must be persisted, on creation, before the object with the reference so that the proxy URI contains the ID of the target object.

Containment References

A containment reference is modeled by setting Containment = true in the Ecore model. A containment reference is persisted as a nested object in the same MongoDB document as the referencing object if Resolve Proxies = false in the Ecore model or Containment Proxies = false in the Generator model. If Resolve Proxies = true in the Ecore model and Containment Proxies = true in the Generator model, the reference will be persisted as a proxy if the target object is contained in its own Resource (cross-document containment). If the target object is not contained in its own Resource, it will be persisted as a nested object of the referencing object.

Bi-directional Cross-document Containment References

Bi-directional cross-document containment references need special consideration when it comes to saving the objects. For the proxies to be properly persisted, three calls to save() must be made on creation. One of the two objects must be saved twice. The other object must be saved once between the two saves to the other object. For example, consider a bi-directional reference between a Person and an Address, the code to save the two objects would be as follows:

user.setAddress(address);

try
{
  address.save(null);
  user.save(null);
  address.save(null);
}
catch(IOException e)
{
  e.printStackTrace();
}

Modeling Restrictions

There is one minor restriction that you must follow when creating your EMF Ecore model. The following keys are reserved for internal use and may not be used as attribute or reference names:

  • _id
  • _eId
  • _eClass
  • _timeStamp
  • _eProxyURI

Mongo EMF Project Bundles

The project consists of several core bundles, an example project, a JUnit test bundle, and a JUnit test utility bundle.

  • org.eclipselabs.emf.query – this bundle contains the model query support
  • org.eclipselabs.mongo – this bundle contains an IMongoDB OSGi service that provides basic connectivity to a MongoDB database
  • org.eclipselabs.mongo.emf – this bundle contains the MongoDBURIHandlerImpl that provides EMF persistence to MongoDB
  • org.eclipselabs.mongo.freemarker – this bundle contains support for persisting FreeMarker templates in MongoDB
  • org.eclipselabs.mongo.emf.examples – this bundle contains a simple example using MongoDBURIHandlerImpl
  • org.eclipselabs.mongo.emf.junit – this bundle contains the JUnit tests for the core bundles
  • org.eclipselabs.mongo.junit – this bundle contains utilities for users developing their own JUnit tests

Unit Testing

The org.eclipselabs.mongo.junit bundle contains two useful classes that can make writing your unit tests easier.

MongoDatabase

This class is a JUnit @Rule that will clear your MongoDB database after each test. The rule requires the BundleContext and the name of the database to be passed as parameters to its constructor. Here is an example on how to use that rule:

	@Rule
	public MongoDatabase database = new MongoDatabase(Activator.getInstance().getContext(), "junit");

MongoUtil

This utility class contains functions for creating a ResourceSet, getting one or more objects from the database, getting the ID of an object, registering the IMongoDB service with OSGi, and comparing two EObjects. This class has extensive JavaDoc comments that explain the usage of each function.

Launch Configurations

Your launch configuration must include the following bundles and their dependencies:

  • org.eclipselabs.emf.query
  • org.eclipselabs.mongo
  • org.eclipselabs.mongo.emf
  • org.eclipse.equinox.ds (or equivalent)

The org.eclipselabs.mongo bundle uses OSGi declarative services to register the IMongoDB service. This service is used by MongoDBURIHandlerImpl to connect to MongoDB.

References

  1. Eclipse Modeling Framework
  2. MongoDB
  3. EclipseLabs mongo-emf project

Rational Team Concert 3.0 on OS X

Update 4/19/2011: The server zips are back and you no longer have to install on linux and move to OS X.

Rational Team Concert 3.0 is a nice development tool built on Eclipse that runs just fine on Mac OS X even though it’s not officially supported.  This guide will help you install and configure RTC 3.0 on OS X.  The client is very easy to install.  The server used to be easy to install, but some recent changes in the RTC builds has made it much more difficult.  Basically, you install the server on Linux, and transfer it to your OS X box.

Installing RTC 3.0 Server on Linux

Since the RTC Server is no longer available as a zip download, you must install the server on a supported platform such as Linux.  Once the server is installed, you can transfer the content to OS X and it will just work.

  • Fire up a Linux box, Parallels, or VMWare (I use Parallels with Ubuntu)
  • Head over to https://jazz.net/downloads and select Rational Team Concert
  • Select Linux x86-32/64 for your platform and click Download Rational Team Concert
  • After logging and accepting the the terms, RTC-Web-Installer-Linux-3.0.zip will be downloaded
  • Unzip the archive into a folder and run launchpad.sh
  • Uncheck Install in a shared location for multiple users
  • Click Jazz Team Server and CCM Application under Install the Server
  • The Installation Manager will start – login with your jazz.net id and password
  • Click Next
  • Agree to the license and click Next
  • Keep the default directory locations and click Next
  • Keep the default installation directory and click Next
  • Select the desired translations and click Next
  • Click Next
  • Select Not upgrading from previous product release and click Next
  • Click Install
  • After the server is installed, click Finish

Moving RTC Server to OS X

Transfer the contents of the JazzTeamServer directory to OS X.  Here is the method I used:

  • cd ~/IBM
  • zip -r jazz.zip JazzTeamServer
  • Copy jazz.zip to your OS X box (I use a Parallels shared folder)
  • unzip jazz.zip into the directory of your choice (I use /Library)
  • cd JazzTeamServer/server
  • rm -rf jre

Configuring RTC Server

You configure the RTC 3.0 Server as you would for any other platform.  If you are using OS X Server and want to set up LDAP authentication, skip that configuration during the setup.  The setup wizard will not configure it correctly for OS X.  I have instructions below on how to configure the Tomcat JNDI realm by hand below.

  • ./server.startup
  • Point your web browser to https://localhost:9443/jts/setup
  • Login id: ADMIN password: ADMIN
  • Click Next
  • Set the Public URI Root to https://<hostname&gt;:9443/jts (where <hostname> is the hostname of your system)
  • Click Test Connection and then click Next
  • Keep the default Derby database settings and click Next
  • Set up Email Notification as desired and click Next
  • Set up your account and click Next
  • Click Register Applications and click Next
  • Keep the default URI, click Test Connection and click Next
  • Keep the default Derby configuration and click Next
  • Click Finalize Application Setup and click Next
  • Click Finish
  • (Optional) Set up LDAP for use with OS X Server
    • ./server.shutdown
    • edit tomcat/conf/server.xml
    • Disable the database realm
    • Configure the JNDI realm as shown below
    • ./server.startup
<Realm className="org.apache.catalina.realm.JNDIRealm"
    connectionName=""
    connectionURL="ldap://<hostname>:389"
    roleBase="cn=groups,dc=<host>,dc=<domain>,dc=com"
    roleName="cn"
    roleSearch="(memberUid={1})"
    roleSubtree="true"
    userBase="cn=users,dc=<host>,dc=<domain>,dc=com"
    userSearch="(uid={0})"
    userSubtree="true"/>
  • Install your license as you normally would

You should now have a functional server.  If you are using OS X Server, remember that the LDAP groups are case sensitive, so make sure you create a JazzUsers group and not a jazzusers group.

Starting RTC Server on Boot

If you would like the RTC Server automatically started when OS X boots, put the following in /Library/LaunchDaemons/com.ibm.rtc.plist

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.ibm.rtc</string>
  <key>OnDemand</key>
  <true/>
  <key>Program</key>
  <string>server.startup</string>
  <key>AbandonProcessGroup</key>
  <true/>
  <key>RunAtLoad</key>
  <true/>
  <key>WorkingDirectory</key>
  <string>/Library/JazzTeamServer/server</string>
</dict>
</plist>

At the command prompt: sudo launchctl load com.ibm.rtc.plist

Installing RTC Client on OS X

Installing the client on OS X is super simple using the P2 repository.

  • From the All Downloads tab on jazz.net, scroll down to the section containing the zips and download the P2 Install Repository
  • unzip the downlaod
  • From the Eclipse IDE, choose Help -> Install New Software …
  • Add the directory to the unzipped P2 repository
  • Check Rational Team Concert and install using the wizard

Configuring OSGi Services with Apache Web Console and Metatype

Introduction

OSGi services can be easily configured using the ConfigurationAdmin service.  If you add metadata to your services, they can be configured with a nice user interface such as the Apache Web Console.  This can be a very nice way to configure remote servers.  This tutorial will walk you through the steps to create the metadata, set up the web console, and configure your services.  Here’s an example of the end result where an OSGi service is configured with the Apache Web Console:

Metatype

Administrative tools such as the Apache Web Console can provide a nice form based user interface for configuring your OSGi services only if you provide sufficient metadata for your service properties.  Using this metadata, the Apache Web Console knows how to display, for example, a label for each property, display a text box for simple string data, and display a combo box for choosing a value from a list.  This metadata is defined using the OSGi Metatype Service.

There are two ways to provide metatype data: an XML file, or by registering a MetaTypeProvider as an OSGi service.  Specifying your metatype data in an XML file works well as long as your data is static, and you are not using declarative services.  If you need to specify dynamic metatype data such as the values for options, you must register a provider.  Metatype will not – yet – work with declarative services.  I’ll discuss this in detail in the section below on declarative services.  The metatype service allows you to specify the following datatypes for configuring your services:

  • boolean
  • byte
  • char
  • double
  • float
  • int
  • long
  • short
  • String

You can also specify a cardinality for arrays, default values, and label/value pairs for choices.

Metatype XML File

To specify your metatype data in an XML file, you must create an OSGI-INF/metatype/ directory in your bundle.  In this directory, you may place one or more XML files adhering to the metatype XML schema.  The file contains a number of object class definitions each with a number of attribute definitions.  You then specify a designate to tie an object class definition to a service PID.  Here is a simple example of a metatype XML file that allows a service to be configured with a property called choice with the possible values: normal and priority.

<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.0.0">
	<OCD description="Example Managed Service" name="Managed Service" id="ocd">
		<AD name="Example Choice Attribute" id="choice" required="true" type="String" default="normal">
			<Option label="Normal" value="normal" />
			<Option label="Priority" value="priority" />
		</AD>
	</OCD>
	<Designate pid="org.eclipselabs.metatype.examples.managedService"> <Object ocdref="ocd"/>
	</Designate>
</MetaData>

Metatype Provider

To register a metatype provider, create a class that implements MetaTypeProvider and construct instances of ObjectClassDefinition and AttributeDefinition.  The provider can be registered in your bundle activator, or as a declarative service.  The metatype specification provides interfaces for the datatypes, but not implementations.  You must provide your own implementation of the interfaces, or you may use the EMF implementation I have hosted at Eclipse Labs.  Here is the source code that specifies the same configuration metadata as the XML file above using the EMF implementation.

public class ExampleDeclarativeServiceMetatype implements MetaTypeProvider
{
	@Override
	public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
	{
		ObjectClassDefinitionImpl ocd = MetatypeFactory.eINSTANCE.createObjectClassDefinitionImpl();
		ocd.setID("org.eclipselabs.metatype.examples.service");
		ocd.setName("Declarative Service");
		ocd.setDescription("Example Description");

		AttributeDefinitionImpl ad = MetatypeFactory.eINSTANCE.createAttributeDefinitionImpl();
		ad.setName("Exampel Chioce Attribute");
		ad.setDescription("Attribute Description");
		ad.setID("choice");
		ad.setType(AttributeDefinition.STRING);
		ad.setOptionLabels(new String[] {"Normal", "Priority"});
		ad.setOptionValues(new String[] {"normal", "priority"});
		ad.setDefaultValue(new String[] {"normal"});

		ocd.getAttributeDefinitions().add(ad);
		return ocd;
	}

	@Override
	public String[] getLocales()
	{
		return null;
	}
}

Now that you have seen how easy it is to specify your metatype data, lets write some services and configure them using the web console. We start by setting up our target platform.

Target Platform

Setting up your target platform is really easy with Eclipse and PDE.  Before you configure Eclipse, you need to download the Apache Web Console (Full) bundle since they do not build P2 repositories.  As of this writing, the latest version of the web console is 3.1.0.

  1. Start up Eclipse (I’m using Helios) with a new workspace.
  2. Select Preferences -> Plug-in Development -> TargetPlatform
  3. Click Add…
  4. Select Nothing: Start with an empty target definition
  5. Click Next
  6. Name: Equinox
  7. In the Locations tab, click Add…
  8. Choose Software Site and click Next
  9. Work with: Helios – http://download.eclipse.org/releases/helios
  10. Expand EclipseRT Target Platform Components
  11. Check
    1. EMF – Eclipse Modeling Framework SDK
    2. Equinox Target Components
  12. Click Finish
  13. Click Add…
  14. Choose Software Site and click Next
  15. Work with: http://metatype-emf-model.eclipselabs.org.codespot.com/hg/site/org.eclipselabs.metatype.site/
  16. Check Metatype EMF Model
  17. Click Finish
  18. Click Add…
  19. Choose Directory and click Next
  20. Location: is the path to the directory containing the web console bundle
  21. Click Finish

Your New Target Definition should look like:

Click Finish, check the target definition to make it active, and click OK.  Your target platform is now ready for you to start writing code.

Managed Services

There are two interfaces that can be registered to make your service configurable by ConfigurationAdmin: ManagedService and ManagedServiceFactory.  Use these interfaces for non-factory and factory services respectively.  Let’s create an Eclipse plug-in project with a ManagedService and ManagedServiceFactory.  Here is the code for the ManagedService:

package org.eclipselabs.metatype.examples;

import java.util.Dictionary;
import java.util.Enumeration;

import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;

public class ExampleManagedService implements ManagedService
{
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public void updated(Dictionary properties) throws ConfigurationException
	{
		System.out.println("Updating configuration properties for ExampleManagedService");

		if (properties != null)
		{
			Enumeration<String> keys = properties.keys();

			while (keys.hasMoreElements())
			{
				String key = keys.nextElement();
				System.out.println(key + " : " + properties.get(key));
			}
		}
	}
}

The code for the ManagedServiceFactory is almost identical:

package org.eclipselabs.metatype.examples;

import java.util.Dictionary;
import java.util.Enumeration;

import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;

public class ExampleManagedServiceFactory implements ManagedServiceFactory
{
	@Override
	public String getName()
	{
		return "ManagedServiceFactoryExample";
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void updated(String pid, Dictionary properties) throws ConfigurationException
	{
		System.out.println("Updating configuration properties for ExampleManagedServiceFactory " + pid);

		if (properties != null)
		{
			Enumeration<String> keys = properties.keys();

			while (keys.hasMoreElements())
			{
				String key = keys.nextElement();
				System.out.println(key + " : " + properties.get(key));
			}
		}
	}

	@Override
	public void deleted(String pid)
	{
		System.out.println("ExampleManagedServiceFactory deleted(" + pid + ")");
	}
}

Create a bundle activator to register the two services:

package org.eclipselabs.metatype.examples;

import java.util.Hashtable;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ManagedServiceFactory;

public class Activator implements BundleActivator
{
	private static BundleContext context;

	static BundleContext getContext()
	{
		return context;
	}

	public void start(BundleContext bundleContext) throws Exception
	{
		Activator.context = bundleContext;

		Hashtable<String, String> serviceProperties = new Hashtable<String, String>();
		serviceProperties.put(Constants.SERVICE_PID, "org.eclipselabs.metatype.examples.managedService");
		context.registerService(ManagedService.class.getName(), new ExampleManagedService(), serviceProperties);

		Hashtable<String, String> serviceFactoryProperties = new Hashtable<String, String>();
		serviceFactoryProperties.put(Constants.SERVICE_PID, "org.eclipselabs.metatype.examples.managedServiceFactory");
		context.registerService(ManagedServiceFactory.class.getName(), new ExampleManagedServiceFactory(), serviceFactoryProperties);
	}

	public void stop(BundleContext bundleContext) throws Exception
	{
		Activator.context = null;
	}
}

Now, we need the metatype data for the two services. In your plug-in project, create the directory OSGI-INF/metatype/ and in that directory, create the following two files:

managed_service.xml

<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.0.0">
	<OCD description="Example Managed Service" name="Managed Service" id="ocd">
		<AD name="Example Choice Attribute" id="choice" required="true" type="String" default="normal">
			<Option label="Normal" value="normal" />
			<Option label="Priority" value="priority" />
		</AD>
	</OCD>
	<Designate pid="org.eclipselabs.metatype.examples.managedService"> <Object ocdref="ocd"/>
	</Designate>
</MetaData>

managed_service_factory.xml

<?xml version="1.0" encoding="UTF-8"?>
<MetaData xmlns="http://www.osgi.org/xmlns/metatype/v1.0.0">
	<OCD description="Example Managed Service" name="Managed Service Factory" id="ocd">
		<AD name="Example Choice Attribute" id="choice" required="true" type="String" default="normal">
			<Option label="Normal" value="normal" />
			<Option label="Priority" value="priority" />
		</AD>
	</OCD>
	<Designate factoryPid="org.eclipselabs.metatype.examples.managedServiceFactory" pid="org.eclipselabs.metatype.examples.managedServiceFactory" bundle="*">
		<Object ocdref="ocd"/>
	</Designate>
</MetaData>

Note that for the factory case, you must specify both the pid and factoryPid in the metatype XML.

Run the Examples

Let’s set up a launch configuration and run the examples you’ve created so far.

  1. Select Run -> Run Configurations…
  2. Select OSGi Framework
  3. Click New launch configuration on the toolbar
  4. In the Bundles tab, click Deselect All
  5. Uncheck Include optional dependencies when computing required bundles
  6. Check your examples bundle
  7. Check the following bundles from the Target Platform
    1. org.eclipse.equinox.metatype
    2. org.eclipselabs.metatype
    3. org.eclipse.equinox.cm
    4. org.ecliplse.equinox.http.jetty
    5. org.mortbay.jetty.server
    6. org.mortbay.jetty.util
    7. org.apache.felix.webconsole
  8. Click Add Required Bundles
  9. On the Arguments tab, add the VM arguments: -Dorg.osgi.service.http.port=8080

Your launch configuration should look like:

Click Run and after the application starts, open your web browser on the URL: http://localhost:8080/system/console/configMgr.  When prompted for login, use admin for both the id and password.  You should now see the Apache Felix Web Console with your services listed for configuration.  Your services will probably appear twice, and if you click to edit them you will see different results.  One will display an empty text box, and the other will display a proper form with a combo allowing you to select between Normal and Priority.  Ignore the one with the empty text box – I assume it’s a bug in the web console.  For your managed service, you can click the edit button to change it’s configuration.  For your managed service factory, you can add and remove configurations.

Try changing the the setting on your managed service from Normal to Priority.  You should see the following in the Console view:

Declarative Services

As I mentioned above, the metatype service does not – yet – work with declarative services.  With some help from Tom Watson, I proposed a change to the OSGi specification to allow the metatype service to work with declarative services. Here are the important parts of the proposal:

I would propose changing the MetaType spec to allow for MetaTypeProvider
services to be registered without a ManagedService or ManagedServiceFactory,
and with the following two service properties: metatype.target.pid, and
metatype.factory. The value of metatype.target.pid would be set to the pid of
the declarative service for which the meta type applies, and the
metatype.factory would be set to boolean true if the pid is a factory pid, and
false otherwise.

The meta type service would be changed to include a service tracker for
MetaTypeProvider and the existence of the two properties defined above. The
meta type service would continue to look for ManagedService and
ManagedServiceFactory and simply add any MetaTypeProvider services with those
properties to the list.

Tom suggested that we use metatype.provider.pid and metatype.provider.type = “normal” | “factory” instead of metatype.target.pid and metatype.factory. The net of this is that you can register a metatype provider with the metatype.provider.pid and metatype.provider.type properties set, and you will get metatype data for declarative services.  This proposal appears to be moving forward into the OSGi specification.

I have provided a patch to the Equinox metatype implementation at https://bugs.eclipse.org/bugs/show_bug.cgi?id=311128 with these proposed changes.  With this patch, you can experiment with metatype and declarative services.  Note that this work is not final and still subject to change until it is in the actual OSGi specification.  To try this out, you first need the Equinox metatype source from CVS.

  1. Switch to the CVS Repository Exploring perspective
  2. Click Add CVS Repository in the toolbar
  3. Host: dev.eclipse.org
  4. Repository path: /cvsroot/rt
  5. User: anonymous
  6. Connection type: pserver
  7. Click Finish
  8. In the CVS Repositories view, expand:
    1. /cvsroot/rt
    2. HEAD
    3. org.eclipse.equinox
    4. compendium
    5. bundles
  9. Select org.eclipse.equinox.metatype, right-click and choose Check Out

You should now have the metatype source in your workspace ready to be patched.  To apply my patch:

  1. Switch back to the Plug-in Development perspective
  2. Right-click on the org.eclipse.equinox.metatype project
  3. Choose Team -> Apply Patch…
  4. Choose URL https://bugs.eclipse.org/bugs/attachment.cgi?id=166704
  5. Click Finish

Let’s create a declarative service and try this out.  You need to create a new bundle for your declarative services.  If you add your declarative service metatype provider to a bundle that contains metatype XML files, the registered metatype providers are ignored.  This might be a bug in the metatype implementation.

Here is the code for the declarative service:

package org.eclipselabs.metatype.examples.ds;

import java.util.Dictionary;
import java.util.Enumeration;

import org.osgi.service.component.ComponentContext;

public class ExampleDeclarativeService
{
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected void activate(ComponentContext context)
	{
		System.out.println("Activating ExampleDeclarativeService");

		Dictionary properties = context.getProperties();

		if (properties != null)
		{
			Enumeration<String> keys = properties.keys();

			while (keys.hasMoreElements())
			{
				String key = keys.nextElement();
				System.out.println(key + " : " + properties.get(key));
			}
		}
	}

	protected void deactivate()
	{
		System.out.println("Deactivating ExampleDeclarativeService");
	}
}

Here is the code for the declarative service metatype:

package org.eclipselabs.metatype.examples.ds;

import org.eclipselabs.metatype.AttributeDefinitionImpl;
import org.eclipselabs.metatype.MetatypeFactory;
import org.eclipselabs.metatype.ObjectClassDefinitionImpl;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.ObjectClassDefinition;

public class ExampleDeclarativeServiceMetatype implements MetaTypeProvider
{
	@Override
	public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
	{
		ObjectClassDefinitionImpl ocd = MetatypeFactory.eINSTANCE.createObjectClassDefinitionImpl();
		ocd.setID("org.eclipselabs.metatype.examples.service");
		ocd.setName("Declarative Service");
		ocd.setDescription("Example Description");

		AttributeDefinitionImpl ad = MetatypeFactory.eINSTANCE.createAttributeDefinitionImpl();
		ad.setName("Exampel Chioce Attribute");
		ad.setDescription("Attribute Description");
		ad.setID("choice");
		ad.setType(AttributeDefinition.STRING);
		ad.setOptionLabels(new String[] {"Normal", "Priority"});
		ad.setOptionValues(new String[] {"normal", "priority"});
		ad.setDefaultValue(new String[] {"normal"});

		ocd.getAttributeDefinitions().add(ad);
		return ocd;
	}

	@Override
	public String[] getLocales()
	{
		return null;
	}
}

Here is the code for the declarative service factory metatype:

package org.eclipselabs.metatype.examples.ds;

import java.util.UUID;

import org.eclipselabs.metatype.AttributeDefinitionImpl;
import org.eclipselabs.metatype.MetatypeFactory;
import org.eclipselabs.metatype.ObjectClassDefinitionImpl;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.MetaTypeProvider;
import org.osgi.service.metatype.ObjectClassDefinition;

public class ExampleDeclarativeServiceFactoryMetatype implements MetaTypeProvider
{
	@Override
	public ObjectClassDefinition getObjectClassDefinition(String id, String locale)
	{
		ObjectClassDefinitionImpl ocd = MetatypeFactory.eINSTANCE.createObjectClassDefinitionImpl();
		ocd.setID("org.eclipselabs.metatype.examples.service.factory");
		ocd.setName("Declarative Service Factory");
		ocd.setDescription("Example Description");

		AttributeDefinitionImpl ad = MetatypeFactory.eINSTANCE.createAttributeDefinitionImpl();
		ad.setName("Exampel Chioce Attribute");
		ad.setDescription("Attribute Description");
		ad.setID("choice");
		UUID normal = UUID.randomUUID();
		UUID priority = UUID.randomUUID();
		ad.setType(AttributeDefinition.STRING);
		ad.setOptionLabels(new String[] {"Normal", "Priority"});
		ad.setOptionValues(new String[] {normal.toString(), priority.toString()});
		ad.setDefaultValue(new String[] {normal.toString()});

		ocd.getAttributeDefinitions().add(ad);
		return ocd;
	}

	@Override
	public String[] getLocales()
	{
		return null;
	}
}

The metatype providers can be registered as declarative services. When you register them, you must set the metatype.provider.pid and metatype.provider.type properties. First, register the declarative service as a factory and non-factory configurable service.

Here is the non-factory registration:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.metatype.examples.service">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeService"/>
</scr:component>

Here is the factory configurable service:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="require" immediate="true" name="org.eclipselabs.metatype.examples.service.factory">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeService"/>
</scr:component>

Here is the metatype provider registration:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.metatype.examples.service.metatype">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeServiceMetatype"/>
   <service>
      <provide interface="org.osgi.service.metatype.MetaTypeProvider"/>
   </service>
   <property name="metatype.provider.pid" type="String" value="org.eclipselabs.metatype.examples.service"/>
   <property name="metatype.provider.type" type="String" value="normal"/>
</scr:component>

Here is the metatype factory provider registration:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.metatype.examples.service.factory.metatype">
   <implementation class="org.eclipselabs.metatype.examples.ds.ExampleDeclarativeServiceFactoryMetatype"/>
   <service>
      <provide interface="org.osgi.service.metatype.MetaTypeProvider"/>
   </service>
   <property name="metatype.provider.pid" type="String" value="org.eclipselabs.metatype.examples.service.factory"/>
   <property name="metatype.provider.type" type="String" value="factory"/>
</scr:component>

That’s all the code we need, so let’s modify our launch configuration and try it out.

Run the Examples

Bring up the launch configuration you created to run the first example.

  1. Add your new bundle containing your declarative services
  2. Add the org.eclipse.equinox.metatype bundle from your Workspace
  3. Add the org.eclilpse.equinox.ds bundle
  4. Remove the org.eclipse.equinox.metatype bundle from the Target Platform
  5. Click Add Required Bundles

Your launch configuration should look like:

Click Run and point your web browser to http://localhost:8080/system/console/configMgr.  You should see your declarative services in addition to your managed services.  Try creating a configuration for your factory service and you will see it display the value of the UUID that was dynamically chosen for a configuration value.  This is where the real power of this technology shines – the ability to programatically create values for declarative service configuration and have those values available as choices on an administrative UI.

If you don’t feel like setting up the bundles and typing in the code, you can get the example source code from Eclipse Labs.

References

  1. OSGi Specifications
  2. Metatype API
  3. OSGi Metatype Service
  4. Configuration Admin API
  5. Dynamically configured declarative OSGi services
  6. Configuration Admin Service specification explained by Example
  7. How to use Config Admin
  8. OSGi as a Web Application Server
  9. Eclipse Labs

On overloading Object.equals() and Object.hashCode()

Unless you are new to the Java language, you already know that when you overload Object.equals(), you must also overload Object.hashCode().  You must exercise caution when overloading these two operators.  I recently ran into a problem when a simple refactoring of an object caused the system to break all due to Object.hashCode().  The resulting refactoring gave us a hash function that looked like:

public class MyObject
{
  public boolean equals(Object object)
  {
    // some code removed here for simplicity
    return ((MyObject) object).getId() == id;
  }

  public int hashCode()
  {
    return id;
  }

  private int id;
}

The object in question is persisted in a database table and the id field is mapped in the database as an automatically generated primary key column.  This guarantees the id field is unique. The problem has to do with the persistence framework changing the value of the id after the object is first stored in the database table.

The persistence framework maintains a HashSet of the objects to be inserted into a database table.  Once the object is inserted into a table, it is removed from the HashSet.  When the framework changes the id of the object, it can no longer be removed from the HashSet.  If you try the following, it will fail:

HashSet<MyObject> data = new HashSet<MyObject>();
MyObject object = new MyObject()
data.add(object);
object.setId(1);
data.remove(object);

There were two lessons learned here. First you must be careful when refactoring Object.equals() and Object.hashCode(). Prior to the refactoring, we had used a UUID to determine equality. Second, when you overload Object.equals() and Object.hashCode(), you can only use invariant values in the computation. If you (accidentally) use a value that can change, your hashed data structured will (eventually) fail.

OSGi as a Web Application Server

Did you know that with an OSGi framework and a handful of bundles, you can set up your own light-weight web application server in a matter of minutes?  I will walk you through the process of running the most basic web application server, and then show you how to add serious performance by using the Jetty bundle.

Target Platform

Start by setting up a target platform that contains the Eclipse Equinox OSGi implementation.

  1. Open the Preferences
  2. Select Plug-in Development -> Target Platform
  3. Click Add…
  4. Select Nothing: Start with an empty target definition
  5. Click Next
  6. Name the target
  7. Click Add…
  8. Select Software Site
  9. Click Next
  10. Work with: Galileo or Helios (I’m using Helios)
  11. Expand EclipseRT Target Platform Components
  12. Check Equinox Project SDK
  13. Click Finish

You will probably want to grab a quick drink as it will take a few minutes to provision your target platform.  Once your target platform is provisioned, click Finish in the New Target Definition dialog.  When you are taken back to the Preferences dialog, check the target platform you just created (I named mine Equinox) and click OK.

Hello World Servlet

Now that we have our target platform set up, let’s create a simple “Hello World” servlet.  There’s nothing special about the servlet, it’s simply a class that extends HttpServlet as you would expect.  The difference is in the packaging.  The servlet is packaged in an OSGi bundle and deployed as a bundle instead of the typical WAR file.

  1. Create a new Plug-in Project
  2. Name the project com.example.servlet
  3. Click Next
  4. Uncheck Generate an activator
  5. Uncheck This plug-in will make contributions to the UI
  6. Click Finish
  7. In the manifest editor, switch to the Dependencies tab
  8. Add the following Imported Packages:
    • javax.servlet
    • javax.servlet.http
  9. Add the following Required Plugins:
    • org.eclipse.equinox.http.registry
  10. Create a package called com.example.servlet
  11. Create a new class called HelloWorldServlet
  12. Override the doGet() function
/*******************************************************************************
 * Copyright (c) 2010 Bryan Hunt.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Bryan Hunt - initial API and implementation
 *******************************************************************************/

package com.example.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author bhunt
 *
 */
public class HelloWorldServlet extends HttpServlet
{
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		PrintWriter out = resp.getWriter();
		out.println("Hello Servlet World");
	}

	private static final long serialVersionUID = -7578840142400570555L;
}

Register the Servlet

Since we are not using a WAR file to deploy our servlet, how do you register the servlet with the container?  The easiest way is with an extension point.  In the manifest editor:

  1. Click on the Overview tab
  2. Check This plug-in is a singleton
  3. Click on the Extensions tab
  4. Add the extension point org.eclipse.equinox.http.registry.servlets
  5. Set the class to com.example.servlet.HelloWorldServlet
  6. Set the alias to /hello


Launch Configuration

The last step is launching our web application.  Let’s launch our application from the workbench using an OSGi launch configuration.

  1. From the Run toolbar, select Run Configurations…
  2. Select OSGi Framework
  3. On the toolbar, click New Launch Configuration
  4. Click Deselect All
  5. Check the following bundles
    • com.example.servlet
    • javax.servlet
    • org.eclipse.equinox.common
    • org.eclipse.equinox.http
    • org.eclipse.equinox.http.registry
    • org.eclilpse.equinox.registry
    • org.eclipse.osgi
    • org.eclipse.osgi.services
  6. Click on the Arguments tab
  7. Add the Program argument -console
  8. Add the VM argument -Dorg.osgi.service.http.port=8080
  9. Click Run

Note that Default Auto-Start is set to true.  If you set it to false, you must Auto-Start the following bundles:

  • org.eclipse.equinox.common
  • org.eclipse.equinox.http
  • org.eclipse.equinox.http.registry
  • org.eclipse.osgi

Once the OSGi framework has launched, start up your favorite browser and point it to:  http://localhost:8080/hello and you should see it respond with Hello Servlet World.  If you are running OS X, there’s a simple program called HTTP Client that is nice for playing around with HTTP.

Jetty

The setup I explained above is great for a light-weight, non-performance critical, deployment of an OSGi based web application.  If you are looking for a more robust solution, then I’d recommend switching out the http bundle for the Jetty bundles.  While the http bundle is compatible with Servlet 2.4, there are limitations.  Jetty 7 has full support for Servlet 2.4, and it looks like Jetty 8 will have support for Servlet 3.0.  To switch to Jetty, edit your launch configuration as follows:

  • Remove Bundles
    • org.eclipse.equinox.http
  • Add Bundles
    • org.eclipse.equinox.http.jetty
    • org.eclipse.equinox.http.servlet
    • org.mortbay.jetty.server
    • org.mortbay.jetty.util

Keep the same arguments as in the http launcher above.  You primarily need to set -Dorg.osgi.service.http.port=8080 otherwise Jetty will  default to port 80 which requires you to run your application as root.

Again, Default Auto-Start is set to true. If you set it to false, you must Auto-Start the following bundles:

  • org.eclipse.equinox.common
  • org.eclipse.equinox.http.jetty
  • org.eclipse.equinox.http.registry
  • org.eclipse.osgi

Running OSGi inside a web container

There is another option for running OSGi as a web service.  You can embed OSGi inside a web container like Tomcat using the servlet bridge.  This setup is a bit more complicated and beyond the scope of the blog entry.  If you are interested, please see the following:

Finally, as I was working on this blog post, Peter Friese wrote up a couple of entries on this topic that you may find interesting:

    Updating Ensemble for Restlet 2.0 (license beware)

    I attended a tutorial at EclipseCon 2009 on ReSTful OSGi Web Applications which was based on the Ensemble project at NASA and Restlet 1.x.  I recently started looking at Restlet 2.0 and updated the Ensemble code to work with the new API, cleaned up a possible threading issue, and added an OSGi service for extensibility.  I was hoping to share these changes in this post, but the open source license under which the Ensemble code was released is too prohibitive (private, non-comercial, use only) for my use.  I had assumed that it was released under EPL, and I was wrong.

    The functionality provided by the Ensemble code is rather simple.  It provides an Eclipse extension point for registering a Restlet representation.  It also has the ability to handle the dynamic nature of OSGi bundles allowing extensions to come and go during runtime.  It’s too bad my work was for not.  Fortunately, a colleague has agreed to attempt to reproduce this functionality, and since he has not seen the Ensemble source code, nor discussed it with me, he will be able to release his code under EPL.

    Lesson learned: Always check the license before looking at the source code.

    Using EMF to Model Your Build Process as a Workflow

    There are a lot of tools available for building software, each with it’s own strengths and weaknesses. They all fundamentally do the same job – they convert source code into an application that can be delivered to a customer. Building an application generally involves multiple steps, and it seems natural that those steps could be described as a workflow. I will demonstrate how to model a build process using my EMF workflow model.

    Let’s define a build process with more than one task.  We will compile a Java source file into a class file, jar the class file, and finally zip the jar file.  I started by creating domain specific extensions (Figure 1) to the EMF workflow model.  Remember that the workflow model is a framework and domain specific tasks are developed as extensions to the model.

    Figure 1

    Figure 1

    I start by creating an Empty EMF Project and then a new Ecore Model.  Next, load the EMF workflow model as a resource of the ecore extension model.  Create three new classes: JavaCompiler, JavaJar, and ZipFile each extending WorkflowUnitOfWork. Finally, create non-containment references to WorkflowParameter as follows:

    JavaCompiler

    • classNameParameter
    • classpathParameter
    • workingDirectoryParameter

    JavaJar

    • classNameParameter
    • workingDirectoryParameter
    • outputFileParameter

    ZipFile

    • workingDirectoryParameter
    • inputFileParameter
    • outputFileParameter

    These con-containment references are created for convenience which will be apparent when the workflow is constructed.  Now that the model extensions have been defined, the model and edit code can be generated.  Remember to set Child Creation Extenders and Extensible Provider Factory to true.

    EditProperties

    The last piece of the development is to provide the executable functionality of each of the tasks.  Here are the implementations of each of the run() functions:

    JavaCompiler

    @Override
    public WorkflowState run(WorkflowContext context) throws WorkflowRuntimeException
    {
    	File workingDirectory = new File((String) workingDirectoryParameter.getValue(context));
    	String[] command = {"javac", "-cp", (String) classpathParameter.getValue(context), (String) classNameParameter.getValue(context) + ".java"};
    	int rc = 0;
    	
    	try
    	{
    		logDebug(context, "Executing command: javac -cp " + command[2] + " " + command[3]);
    		Process process = Runtime.getRuntime().exec(command, null, workingDirectory);
    		rc = process.waitFor();
    	}
    	catch (Exception e)
    	{
    		logException(context, e);
    		return StateFactory.eINSTANCE.createWorkflowErrorState();
    	}
    	
    	return rc == 0 ? StateFactory.eINSTANCE.createWorkflowSuccessState() : StateFactory.eINSTANCE.createWorkflowFailedState();
    }
    
    

    JavaJar

    @Override
    public WorkflowState run(WorkflowContext context) throws WorkflowRuntimeException
    {
    	File workingDirectory = new File((String) workingDirectoryParameter.getValue(context));
    	String[] command = {"jar", "-cf", (String) outputFileParameter.getValue(context), (String) classNameParameter.getValue(context) + ".class"};
    	int rc = 0;
    	
    	try
    	{
    		logDebug(context, "Executing command: jar -cf " + command[2] + " " + command[3]);
    		Process process = Runtime.getRuntime().exec(command, null, workingDirectory);
    		rc = process.waitFor();
    	}
    	catch (Exception e)
    	{
    		logException(context, e);
    		return StateFactory.eINSTANCE.createWorkflowErrorState();
    	}
    	
    	return rc == 0 ? StateFactory.eINSTANCE.createWorkflowSuccessState() : StateFactory.eINSTANCE.createWorkflowFailedState();
    }
    

    ZipFile

    @Override
    public WorkflowState run(WorkflowContext context) throws WorkflowRuntimeException
    {
    	File workingDirectory = new File((String) workingDirectoryParameter.getValue(context));
    	String[] command = {"zip", (String) outputFileParameter.getValue(context), (String) inputFileParameter.getValue(context)};
    	int rc = 0;
    	
    	try
    	{
    		logDebug(context, "Executing command: zip " + command[1] + " " + command[2]);
    		Process process = Runtime.getRuntime().exec(command, null, workingDirectory);
    		rc = process.waitFor();
    	}
    	catch (Exception e)
    	{
    		logException(context, e);
    		return StateFactory.eINSTANCE.createWorkflowErrorState();
    	}
    		
    	return rc == 0 ? StateFactory.eINSTANCE.createWorkflowSuccessState() : StateFactory.eINSTANCE.createWorkflowFailedState();
    }
    

    Now that we have domain specific tasks that can do actual work, we can assemble them into a build process / workflow. Launch the runtime workbench, create a new project, and create a new ecore model. Here are the results:

    Build Workflow

    After the build workflow has been created, we can create a runtime model that contains the parameter values for the build.

    Build Runtime

    I’ve set up this example runtime to build Hello.java in the working directory /tmp/build. When you run the workflow, it will generate Hello.class, hello.jar, and finally hello.zip. You may have noticed that not all modeled parameters have values in the runtime model. I created parameter connections in the workflow model for those parameters that share values. For this example, I also created a second runtime to build Bye.java using the same build process. This complete example is available in CVS at dev.eclipse.org /cvsroot/modeling/org.eclipse.emf/org.eclipse.emf.mwe/examples.

    There is a screencast available until I need the disk space.

    EMF Workflow Model

    I have been working on an EMF Workflow Model for some time now, and the documentation on how to use the model has been lacking.  In this post, I will show you how to extend and use the model with a complete example.  The EMF Workflow Model is a framework for modeling workflows.  Developers must extend the model by creating objects that do useful work.  Once the model has been extended, users may then assemble workflows from the components provided.  The core of the model is a composite pattern that allows a workflow to be constructed as an arbitrary tree forming a directed graph.  The framework supports executing nodes of the model in serial, parallel, loops, and conditionally.  Each node can be parameterized, and provides execution state that can be persisted.

    There are two major parts to the workflow model:  a design model, and a runtime model.  The design model specifies which tasks to execute and the order they are executed.  The runtime model specifies the parameter values and state of the running workflow.  Separating the design model from the runtime model allows you to have a single workflow that can be executed with different permutations by choosing a different runtime model.

    Workflow Design Model

    The design model is based on a composite pattern (Figure 1).  The composite pattern allows for the construction of arbitrarily complex workflows.  Workflows can be a simple sequence of steps or a complicated tree of sub-workflows.  The flow of execution, or orchestration, is modeled as a strategy pattern (Figure 2). This allows custom flow control strategies to be plugged into the workflow providing a rich framework for executing workflows.  The framework includes strategies for executing workflow tasks in serial, parallel, conditionally, and in a loop.

    Workflow

    Figure 1

    • WorkflowComponent – the abstract root of the composite
    • CompositeWorkflowComponent – the branches of the tree
    • WorkflowUnitOfWork – the abstract leaves of the tree (you must extend this class)
    • WorkflowParameter – the keys of the runtime parameter map
    • WorkflowParameterConnection – connect an output parameter to one or more input parameters
    Figure 2

    Figure 2

    • WorkflowCompositeOrchestrationStrategy – abstract base class that controls the execution of children of a composite
    • WorkflowParallelOrchestrationStrategy – executes the children of a composite in parallel
    • WorkflowSerialOrchestrationStrategy – executes the children of a composite in serial
    • WorkflowComponentOrchestrationStrategy – base class that controls the execution of a component
    • WorkflowLoopComponentOrchestrationStrategy – executes a component in a loop
    • WorkflowExecutionPredicate – abstract base class for conditional predicates
    • WorkflowPredicateAND – logical AND of the child predicates
    • WorkflowPredicateOR – logical OR of the child predicates
    • WorkflowRerunPredicate – predicate based on the state of the last run
    • WorkflowParameterPredicate – predicate based on a parameter
    • WorkflowLoopCountPredicate – predicate based on a parameter value count

    Workflow Runtime Model

    The runtime model holds the dynamic information used by the workflow during execution.  This includes parameter values, execution state, and logging (Figure 3).  Parameter values are stored as instances of EObject allowing you to pass any modeled object to a workflow task.  Simple values such as String, Integer, etc can be wrapped in an instance of SimpleAnyType.  Since tasks can be executed in parallel, the runtime model makes use of EMF Transaction for thread safe access to the workflow data.  Workflow states are modeled using the state pattern (Figure 4).  You may extend a state if your task needs state granularity not currently provided.  You should not access the data in the WorkflowContext directly while the workflow is running, but instead use the convenience functions in the various design model classes.

    Figure 3

    Figure 3

    • WorkflowContext – the root container for all runtime data
    • WorkflowStateMap – maps a WorkflowComponent to a WorlflowState
    • WorkflowComponentExecutionInfoMap – maps a WorkflowComponent to a WorkflowComponentExecutionInfo
    • WorkflowParameterMap – maps a WorkflowParameter to its value
    • WorkflowLogMap – maps a WorkflowComponent to a WorkflowLog
    Figure 4

    Figure 4

    • WorkflowState – abstract base class for all workflow states
    • WorkflowIdleState – the task has not been executed
    • WorkflowDoneState – the task has finished executing
    • WorkflowRunningState – the task is currently running
    • WorkflowSuccessState – the task has finished successfully
    • WorkflowFailedState – the task has finished with a failure
    • WorkflowErrorState – the task has finished with an exception

    Getting The Workflow Model

    The model is contributed to the MWE sub-project under EMFT at Eclipse.org.  My contribution is not currently part of the MWE build, so you will have to start by extracting the model from CVS.  Start Eclipse and open the CVS Perspective.  Use the following connection:

    • Host: dev.eclipse.org
    • Path: /cvsroot/modeling
    • User: anonymous
    • Connection type: pserver

    Expand the path: HEAD -> org.eclipse.emf -> org.eclipse.emf.mwe -> plugins and checkout the bundles org.eclipse.emf.mwe.ewm.*.  The org.eclipse.emf.mwe.core bundle contains the EMF model that you will extend.  The other bundles are primarily for convenience of experimenting with the workflow model.

    Target Platform

    The EMF workflow model requires the following projects in your target platform:

    Extending The Workflow Model

    Workflow tasks are domain specific and the EMF workflow model provides a framework for assembling and executing your domain specific tasks.  To use the EMF workflow model, you must extend WorkflowUnitOfWork to provide domain specific functionality in the run() function.  Use the following steps to create an extension to the workflow model:

    1. Create a new Empty EMF Project named org.example.workflow
    2. Create a new Ecore Model
    3. Load Resource… platform:/resource/org.eclipse.emf.mwe.ewm.core/model/workflow.ecore
    4. Create a new EClass
    5. Name: ExampleTask
    6. ESuperTypes: WorkflowUnitOfWork

    EclipseNext, we need to create a genmodel and generate our code:

    1. Create a new EMF Generator Model
    2. Import from Ecore model
    3. Add org.eclipse.emf.mwe.ewm.core/model/workflow.genmodel as a referenced generator model
    4. Select both org.eclipse.emf.ecore and org.eclipse.emf.mwe.ewm.workflow as referenced generator models
    5. Suppress Interfaces: true
    6. Base Package: org.example.workflow
    7. Child Creation Extenders: true
    8. Extensible Provider Factory: true
    9. Generate Model Code
    10. Generate Edit Code

    Eclipse 9

    Eclipse 2

    You will notice that ExampleTask does not compile.  The run() function in WorkflowUnitOfWork is abstract and where you need to add your domain specific business logic.  Open ExampleTask in the editor and choose the quickfix: Add unimplemented methods.  Go to the run() function and provide the following implementation:

    @Override
    public WorkflowState run(WorkflowContext context) throws WorkflowRuntimeException
    {
    	System.out.println("Hello world");
    	return StateFactory.eINSTANCE.createWorkflowSuccessState();
    }
    

    Composing A Workflow

    Now that you have a domain specific task that does useful work (“hello world” is arguably not useful – just envision a useful task), your task can be assembled into a workflow.  To do this, we will use the EMF generated editor to create an instance of the workflow model:

    1. Create a new Eclipse Application launch configuration
    2. Launch the runtime workbench
    3. Create a new General -> Project
    4. Create a new Workflow Model
    5. Select the root model object: Composite Component
    6. Set the name of the composite to Root
    7. Create a New Child of Composite Component: Workflow Serial Orchestration Strategy
    8. Create a New Child of Composite Component: Workflow Component Orchestration Strategy
    9. Create a New Child of Composite Component: Workflow State Resolution Strategy
    10. Create a New Child of Composite Component: Task
    11. Set the name of the task to Task
    12. Create a New Child of Task: Workflow Component Orchestration Strategy

    Eclipse 4Every component must have an orchestration strategy.  In this model, there are two components: the root composite, and your example task.  Each of these components has a WorkflowComponentOrchestrationStrategy.  This strategy, by default, simply runs the component.  Every composite must have a composite orchestration strategy, and a state resolution strategy.  In this model, the composite orchestration strategy is WorkflowSerialOrchestrationStrategy which executes the children of the composite serially.  The WorkflowStateResolutionStrategy determines the state of the composite based on the state of the children after all of the children have executed.

    Running A Workflow

    To run the workflow you just assembled, you will need a runtime model and a launch configuration:

    1. Create a new Runtime Model
    2. Select the root model object: WorkflowContext
    3. Create a new EMF Workflow launch configuration
    4. Set Workflfow: platform:/resource/test/My.workflow
    5. Set Context: platform:/resource/test/My.runtime
    6. Launch the workflow

    Run ConfigurationsAs your workflow executes, it will print “Hello world” to the console.  Don’t forget that the output will go to the Console view in the host workbench and not the runtime workbench.  There is an alternate way to run your workflow by right-clicking the workflow model and selecting Run Workflow.  You will be prompted to select the runtime model, the workflow will execute, and the runtime will be saved back to the workspace.  Note that the launcher will not save the runtime after execution.  After your workflow has terminated, you can view the contents of the runtime by right-clicking on the runtime model and selecting View Workflow Status.  You can reset the workflow runtime information by right-clicking on the runtime model and selecting Reset Workflow State.

    Eclipse 5

    Parameterizing A Workflow

    Let’s make the workflow example task a bit more interesting by giving it a parameter for the message to print to the console instead of the hard-coded “Hello world”.  Change ExampleTask.run() to:

    @Override
    public WorkflowState run(WorkflowContext context) throws WorkflowRuntimeException
    {
    	System.out.println(getParameters().get(0).getValue(context));
    	return StateFactory.eINSTANCE.createWorkflowSuccessState();
    }
    

    This will print the value of the first parameter assigned to the task. Before you can run the workflow, you must model the parameter, and give it a value in the runtime model.

    1. Launch the runtime workbench
    2. Open the workflow model in the ecore editor
    3. Create a new Workflow Parameter as a child of Task
    4. Set the parameter name to Message
    5. Set the parameter type to EString
    6. Create a new Parameter Simple Value Strategy as a child of the Workflow Parameter
    7. Open the runtime model in the ecore editor
    8. Load Resource … platform:/resource/test/My.workflow
    9. Create a new Workflow Parameter Map as a child of WorkflowContext
    10. Select Parameter Message as the key
    11. Create a new Simple Any Type as a child of the workflow parameter map
    12. Set the Instance Type to EString
    13. Set the Value to “That was easy”
    14. Run the workflow and “That was easy” will print to the host Console

    Eclipse 6

    Eclipse 7

    Future Direction

    This tutorial will eventually be moved into on-line documentation as part of the project.  The workflow model uses an explicit visitor pattern for some utility functions.  The switch object generated by EMF can be used as a visitor and I will probably look at using the switch object instead of the explicit visitor.  There is a need for the ability to run the tasks of a workflow on a distributed system.  I will be looking at adding the ability to suspend a workflow execution and transfer it to another machine for continued execution.  Finally, as you have seen from this tutorial, composing a workflow with the generated EMF editor is painful and a custom, structured, editor would make this easier.  Ultimately, I would like to have a graphical editor for visualizing and composing a workflow.  If there is any interest in contributing to this project, please contact me.

    Native 2D Rendering In An Eclipse View Using OpenGL

    I’ve been learning how to render graphics in an Eclipse view using OpenGL by putting together (hacking) bits and pieces from various articles I’ve found on the web.  I have enough working to share what I’ve learned and show you how to natively draw basic 2D graphics.  The end result will look like this …

    End Result

    SWT provides a GLCanvas widget for displaying graphics rendered using OpenGL but does not provide the JNI code that interfaces to the OpenGL library to actually draw the graphics.  LWJGL is a JNI library that interfaces to OpenGL and can be installed as an Eclipse bundle from the LWJGL update site.  Another option is to write your own JNI to interface to OpenGL and do the rendering natively.  I have chosen the later path since LWJGL is not available for all platforms and I had a use-case where I needed to interface to an existing C library to retrieve the data to be rendered.  Writing the JNI is not all that hard as there are only a couple of functions that are needed to do basic rendering.

    Let’s start by creating a new C++ project called com.example.opengl.native.  Set the Project type to Shared Library -> Empty Project and Toolchains to MacOSX GCC.

    C++ ProjectAfter the project is created, bring up the project properties.  We need to tell the linker where to fine the OpenGL library, and the compiler where to find the JNI and OpenGL headers.  Navigate to the C/C++ Build -> Settings section and under MacOS X C++ Linker -> Miscellaneous settings, add -framework OpenGL to the Linker flags.  The -framework option is a OS X specific GCC option that tells the linker which OS X frameworks (shared libraries) to link against.  If you are not using OS X, then add the appropriate -L and -l settings for your platform under the Libraries section.

    Linker Flags

    Next, in the GCC C++ Compiler -> Directories settings add /System/Library/Frameworks/JavaVM.framework/Headers and /System/Library/Frameworks/OpenGL.framework as part of the include paths.  For other platforms add the appropriate directories containing the JRE and OpenGL header files.

    Header Dirs

    Finally, switch to the Build Artifact tab and change the Artifact name to opengl and the Artifact extension to jnilib.  Shared libraries in OS X typically have the extension .dylib; however, JNI libraries have a special .jnilib extension.  For other platforms, the default shared library extension is probably fine for JNI (.so for Linux as an example).

    Build Artifacts

    Now, let’s create a plug-in project called com.example.opengl.  You do not need to create an activator for this bundle.  First, we are going to create a class to hold the native functions that interface to OpenGL.

    package com.example.opengl;
    
    public class OpenGL
    {
    	static
    	{
    		System.loadLibrary("opengl");
    	}
    
    	public static native void glClearColor(float red, float green, float blue, float alpha);
    	public static native void glOrtho(double left, double right, double bottom, double top, double near, double far);
    	public static native void glTranslatef(float x, float y, float z);
    	public static native void glViewport(int x, int y, int width, int height);
    
    	public static native void init2D();
    }
    

    Once we have the native functions declared, we need to create the corresponding header (.h) file in the C++ project using the javah program that comes with the JDK.  You can do this right from Eclipse by creating an external program launch configuration.  Set up the launch configuration on the Main tab as follows:

    • Name: OpenGL JNI
    • Location: ${system_path:javah}
    • Working directory: ${workspace_loc:com.example.opengl.native}
    • Arguments: -classpath ${workspace_loc:com.example.opengl/bin} com.example.opengl.OpenGL

    OpenGL JNI MainOn the Refresh tab, select Specific resources, click Specify Resources… and check com.example.opengl.native.  This will cause the C++ project to be refreshed after the header file is created.

    OpenGL JNI Refresh

    Finally, click Run to execute javah and place the header file in the C++ project.  If you expand the C++ project in the Project Explorer, you will see that the header file com_example_opengl_OpenGL.h was created.  In the C++ project create a source file named com_example_opengl_OpenGL.cpp.

    #include "com_example_opengl_OpenGL.h"
    #include "gl.h"
    
    void JNICALL Java_com_example_opengl_OpenGL_glClearColor(JNIEnv* env, jclass clazz, jfloat red, jfloat green, jfloat blue, jfloat alpha)
    {
      glClearColor(red, green, blue, alpha);
    }
    
    void JNICALL Java_com_example_opengl_OpenGL_glOrtho(JNIEnv* env, jclass clazz, jdouble left, jdouble right, jdouble bottom, jdouble top, jdouble near, jdouble far)
    {
      glMatrixMode (GL_PROJECTION);
      glLoadIdentity();
      glOrtho(left, right, bottom, top, near, far);
      glMatrixMode (GL_MODELVIEW);
      glLoadIdentity();
    }
    
    void JNICALL Java_com_example_opengl_OpenGL_glTranslatef(JNIEnv* env, jclass clazz, jfloat x, jfloat y, jfloat z)
    {
      glTranslatef(x, y, z);
    }
    
    void JNICALL Java_com_example_opengl_OpenGL_glViewport(JNIEnv* env, jclass clazz, jint x, jint y, jint width, jint height)
    {
      glViewport(x, y, width, height);
    }
    
    void JNICALL Java_com_example_opengl_OpenGL_init2D(JNIEnv* env, jclass clazz)
    {
      glDisable(GL_DEPTH_TEST);
    }
    

    As you can see, the implementation of the JNI is almost a 1:1 mapping to the OpenGL API. I did a little extra work in glOrtho() which you could break out into separate functions if you prefer.  Now, let’s create the native rendering engine to draw a rectangle in OpenGL.

    package com.example.opengl;
    
    public class RenderEngine
    {
    	public native void draw();
    }
    

    Just like you did for the OpenGL class, create an external program launch configuration to run javah and create the header file in the C++ project.  Set up the launch configuration on the Main tab as follows:

    • Name: RenderEngine JNI
    • Location: ${system_path:javah}
    • Working directory: ${workspace_loc:com.example.opengl.native}
    • Arguments: -classpath ${workspace_loc:com.example.opengl/bin} com.example.opengl.RenderEngine

    RenderEngine JNI MainClick Run and the com_example_opengl_RenderEngine.h header file will be generated in the C++ project.  Switch to the C++ project and implement the draw() function by creating the source file com_example_opengl_RenderEngine.cpp. The implementation draws a series of lines to form a rectangle. I’ll explain the negative y axis in a minute.  This is all of the native code so you can build the C++ project which will result in a libopengl.jnilib in the Debug folder.

    #include "com_example_opengl_RenderEngine.h"
    #include "gl.h"
    
    void JNICALL Java_com_example_opengl_RenderEngine_draw(JNIEnv* env, jobject object)
    {
      glClear(GL_COLOR_BUFFER_BIT);
      glColor3ub(255, 255, 255);
      glBegin(GL_LINE_LOOP);
      {
        glVertex2d(10, -10);
        glVertex2d(150, -10);
        glVertex2d(150, -40);
        glVertex2d(10, -40);
      }
      glEnd();
    }
    

    Now that we have code that interfaces to OpenGL and draws a rectangle, let’s put it all together in an Eclipse view. In the Java plug-in project, create a new extension selecting org.eclipse.ui.views as the extension point. Set the following fields in the extension:

    • id: com.example.opengl.view
    • class: com.example.opengl.OpenGLView
    • name: OpenGL

    Implement the OpenGLView as:

    package com.example.opengl;
    
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.events.ControlAdapter;
    import org.eclipse.swt.events.ControlEvent;
    import org.eclipse.swt.events.SelectionAdapter;
    import org.eclipse.swt.events.SelectionEvent;
    import org.eclipse.swt.graphics.Rectangle;
    import org.eclipse.swt.layout.FillLayout;
    import org.eclipse.swt.opengl.GLCanvas;
    import org.eclipse.swt.opengl.GLData;
    import org.eclipse.swt.widgets.Composite;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.ui.part.ViewPart;
    
    public class OpenGLView extends ViewPart
    {
    	@Override
    	public void createPartControl(Composite parent)
    	{
    		parent.setLayout(new FillLayout());
    		GLData glData = new GLData();
    		glData.doubleBuffer = true;
    		canvas = new GLCanvas(parent, SWT.H_SCROLL | SWT.V_SCROLL, glData);
    		canvas.getHorizontalBar().setMaximum(2000);
    		canvas.getVerticalBar().setMaximum(2000);
    
    		renderEngine = new RenderEngine();
    
    		Display.getDefault().asyncExec(new Runnable()
    		{
    			public void run()
    			{
    				canvas.setCurrent();
    				OpenGL.init2D();
    				OpenGL.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    			}
    		});
    
    		canvas.addControlListener(new ControlAdapter()
    		{
    
    			@Override
    			public void controlResized(ControlEvent e)
    			{
    				redraw();
    			}
    		});
    
    		canvas.getHorizontalBar().addSelectionListener(new SelectionAdapter()
    		{
    			@Override
    			public void widgetSelected(SelectionEvent e)
    			{
    				redraw();
    			}
    		});
    
    		canvas.getVerticalBar().addSelectionListener(new SelectionAdapter()
    		{
    			@Override
    			public void widgetSelected(SelectionEvent e)
    			{
    				redraw();
    			}
    		});
    	}
    
    	@Override
    	public void setFocus()
    	{
    		canvas.setFocus();
    	}
    
    	private void redraw()
    	{
    		Display.getDefault().asyncExec(new Runnable()
    		{
    			public void run()
    			{
    				Rectangle bounds = canvas.getClientArea();
    				canvas.setCurrent();
    				int xOffset = canvas.getHorizontalBar().getSelection();
    				int yOffset = canvas.getVerticalBar().getSelection();
    				OpenGL.glViewport(bounds.x, bounds.y, bounds.width, bounds.height);
    				OpenGL.glOrtho(bounds.x + xOffset, bounds.width + xOffset, -bounds.height - yOffset, bounds.y - yOffset, 0.0, 1.0);
    				OpenGL.glTranslatef(0.375f, 0.375f, 0.0f);
    				renderEngine.draw();
    				canvas.swapBuffers();
    			}
    		});
    	}
    
    	private GLCanvas canvas;
    	private RenderEngine renderEngine;
    }
    

    Notice that all of the calls to OpenGL are made inside a Display.getDefault().asyncExec() call. Why is this necessary when the code is already running in the UI thread? This is the one thing that I don’t understand in this example. If anyone has an explanation, please comment and I’ll update the entry. The call to OpenGL.glViewport() is only necessary when the view is resized – it was just easier to have one redraw() function.  The call to OpenGL.glOrtho() does most of the interesting work.  It sets the orthographic projection of the 3D space into 2D space which translates to pan, zoom, and the axes origin and orientation in 2D.  The arguments to the function are: left, right, bottom, top, near, and far.  I will discuss each of the arguments in detail, but first, a word on the axes origin and orientation.

    OpenGL by default orients the axes with positive x to the right, negative x to the left, positive y up, negative y down, positive z out of the display, and negative z into the display.  By specifying positive or negative values for the arguments to glOrtho(), you can change the orientation of the axes.  The absolute values of the arguments specify the location of the origin.  I’ve set the axes origin to be the upper left corder of the canvas (view), and it is oriented such that positive x is to the right and negative y is down.  It is possible to orient the axes such that positive y is down making 2D drawing match what you are used to but when I tried this, text rendered using GLUT was upside down.  Rather than trying to figure out if there was a way to flip the text, I found it much easier to draw in negative y.  I set the scale to be 1:1 in pixels, meaning that if the canvas is 1000 pixels wide, drawing at x = 1000 will draw on the right edge of the canvas.  Now, let’s look at the glOrtho() arguments:

    Left: bounds.x + xOffset

    This argument sets the left clipping plane which effectively sets the origin of the x axis relative to the left edge of the viewport.  The xOffset takes into account the position of the horizontal scrollbar.  If the scrollbar is all the way to the left, then you will see what was drawn starting at x = 0 and extending into positive x.

    Right: bounds.width + xOffset

    This argument sets the right clipping plane which effectively sets the x scale relative to the viewport.  The xOffset takes into account the position of the horizontal scrollbar so that the scale doesn’t change when you pan.  If the scrollbar is all the way to the left, then you will see what was drawn from x = 0 to x = bounds.width.

    Bottom: -bounds.height – yOffset

    This argument sets the bottom clipping plane which effectively sets the y scale relative to the viewport.  The yOffset takes into account the position of the vertical scrollbar so that the scale doesn’t change when you pan.  If the scrollbar is all the way to the top, then you will see what was drawn from y = 0 to y = -bounds.height.

    Top: bounds.y – yOffset

    This argument sets the top clipping plane which effectively sets the origin of the y axis relative to the top edge of the viewport.  The yOffset takes into account the position of the vertical scrollbar.  If the scrollbar is all the way to the top, then you will see what was drawn starting at y = 0 and extending into negative y.

    Near: 0.0

    Far: 1.0

    These arguments set the values of the near and far clipping planes.  Since the z axis is not used in 2D, the examples I found all set the near clipping plane to 0.0, and the fat clipping plane to 1.0.

    As you move the scrollbar to the right, both the left and right clipping planes move which pans the drawn area in the x axis.  As you move the scrollbar to the bottom, both the top and bottom clipping planes move which pans the drawn area in the y axis.  One thing I have not done in this example is to take into account the size of the scrollbar thumb.  You may have to adjust the right and bottom arguments to glOrtho() taking into account scrollbar.getThumb().

    The call to OpenGL.glTranslatef(0.375f, 0.375f, 0.0f) is a “trick” necessary to make your drawing pixel accurate.  Try drawing a line from (0, -10) to (10, -10) and a line from (0, -20) to (0, -30) without the call to glTranslate().  The result will not be what you expect.

    Before we can launch the Eclipse runtime workbench, we need to hook the JNI library into the plug-in.  We are going to bundle the native library with the plug-in so that it can be delivered as a single unit. In the plug-in project, create a lib folder at the root of the project.  Copy the libopengl.jnilib file from the Debug folder in the C++ project to the lib folder in the plug-in project.  Edit the build.properties to include the newly created lib folder along with its contents.  Finally, edit the MANIFEST.MF of the plug-in project and using the source tab of the editor, add the line:

    Bundle-NativeCode: lib/libopengl.jnilib; osname=macosx; processor=x86

    This tells the class loader where to find the native library when System.loadLibrary() is called.  Now, we can create an Eclipse Application launch configuration to run the runtime workbench.  You can use the defaults for the launch configuration.  After you launch the runtime workbench, select Window -> Show View -> Other… and under the Other category, select the OpenGL view and click OK.  You should see the view with a white rectangle drawn on a black background just like the first screenshot above.