Thursday, April 28, 2011

Hot Deploy OSGi CLI Commands in Apache Karaf with Eclipse PDE

Apache Felix GoGo is a really cool command line interface (CLI, aka shell aka console) framework that runs in an OSGi runtime.
Apache Karaf (based on Apache Felix OSGi runtime) just so happens to include Felix GoGo built-in, and the console is very extensible too, making it easy to add more commands at your will.

Karaf / Felix GoGo Features

A very nice feature of Apache Karaf is it has a built-in SSH server, so you can literally and practically just ssh to the Karaf server :

ceefour@annafi:/opt/apache-karaf-2.2.0$ ssh -p 8102 karaf@localhost
karaf@localhost's password: 
        __ __                  ____      
       / //_/____ __________ _/ __/      
      / ,<  / __ `/ ___/ __ `/ /_        
     / /| |/ /_/ / /  / /_/ / __/        
    /_/ |_|\__,_/_/   \__,_/_/         

  Apache Karaf (2.2.0)

Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit '<ctrl-d>' or 'osgi:shutdown' to shutdown Karaf.

karaf@bipposhell> erp:hello
Hello Ibrahim!

Default credentials is user=karaf, password=karaf .
And even (this I just found out!) the amazing thing is you can run a command directly from ssh :

$ ssh -p 8102 karaf@localhost erp:hello
karaf@localhost's password: 
Hello Ibrahim!

Isn't it amazing? What's more, the Karaf/Felix GoGo command line also supports POSIX/GNU-style option arguments, Tab completion, and UNIX-like pipes! (yes, pipes, where you can do 'grep' and stuff)

The other, and paramountly useful feature, is the ability to hot deploy exploded OSGi bundles! Now hot deploy in itself is really useful, the ability to deploy exploded bundles is even more fantastic. We'll see about that soon.

What About Virgo?

Some of you may (almost) cry that I didn't mention Eclipse Virgo Kernel. Which is, of course, a direct competitor to Apache Karaf.
I haven't used Virgo yet (but I should try it one day), so because I have used Karaf in the past so for now I'm using Karaf, and I know it has a cool built-in CLI ;-)
I think the steps below can be adapted to Virgo with little (or not so little?) work. Let me know if you have tips, ideas, etc. :-) I will appreciate it a lot.

Developing CLI on Felix GoGo

For my next Bippo Shell project I plan to use Felix GoGo as the CLI framework. I'll share my first steps experience on developing CLI Commands with Karaf and Felix GoGo with you.
To get started, I first download Apache Karaf OSGi runtime.

Felix GoGo looks for command services exposed by other OSGi bundles. To develop this OSGi bundle, my IDE of choice happens to be... (surprise!) Eclipse IDE. ;-)
Now there are several approaches to develop OSGi bundles in Eclipse IDE, but the path I want to take right now is using Eclipse PDE (Plug-in Development Environment). At least for developing the bundle (i.e. Eclipse plugin) itself, not for running/launching the runtime (yet...)

I assume you are already somewhat familiar with Eclipse IDE and Eclipse PDE. If not, it's not too late to reach enlightenment ;-) get Eclipse Rich Client Platform (2nd Edition) book.

Creating the Target Platform

The first thing I need to do before developing anything (for you guys familiar with Eclipse RCP would already know this: any Eclipse-based project for that matter) is prepare the target platform.
The target platform contains all the OSGi bundles (Eclipse plugins) that are already in the target runtime environment. In this case: Felix Karaf.

So, copy these files from Apache Karaf distribution (hint: look for lib/ and system/ folders) to another folder. These little commands may help you:

mkdir -vp ~/felix-karaf-target-platform/
cp -v lib/karaf-jaas-boot.jar ~/felix-karaf-target-platform
find system -iname '*.jar' -exec cp -v '{}' ~/felix-karaf-target-platform \;

Then I created a target platform in Eclipse PDE, and add content based on the directory that contains the Karaf JARs, then activate the target platform.

Creating the Eclipse Plugin Project

Although Karaf only needs an OSGi bundle, I decided to create an Eclipse plugin project. Why not, since an Eclipse plugin is always an OSGi bundle anyway! And it might be useful if I want to use my plugin projects in an Eclipse RCP project, for example.

First I create an Eclipse plugin project, then set the Import-Package in META-INF/MANIFEST.MF to:

Import-Package: org.apache.felix.gogo.commands;version="0.6.1",
 org.apache.karaf.shell.console;version="2.2.0",
 org.osgi.framework;version="1.3.0"

This allows me to refer to the Felix GoGo API classes and interfaces.

Writing the Command Implementation

Thanks to annotations in similar spirit of JAX-RS, writing the CLI command implementation class is simple:

package id.co.bippo.shell;

import org.apache.felix.gogo.commands.Command;
import org.apache.karaf.shell.console.OsgiCommandSupport;

@Command(scope="erp", name="hello", description="Says hello")
public class ErpCommand extends OsgiCommandSupport {

/* (non-Javadoc)
* @see org.apache.karaf.shell.console.AbstractAction#doExecute()
*/
@Override
protected Object doExecute() throws Exception {
System.out.println("Hello Ibrahim!");
return null;
}

}

I think the annotations and what this class does is pretty much self-explanatory.

Registering the Command Service using OSGi Blueprint

To register the command service implementation, OSGi Blueprint is used (although not mandatory, you can use manual OSGi wiring if you want, but why do it the hard way?)
Apache Karaf uses Apache Aries Blueprint as the OSGi Blueprint implementation, but I believe it should work with Eclipse Gemini Blueprint as well.

Put this in OSGI-INF/blueprint/myshell.xml (actual filename doesn't matter):

    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.0.0">
        <command name="erp/hello">
            <action class="id.co.bippo.shell.ErpCommand"/>
        </command>
    </command-bundle>

</blueprint>

This registers the command that can be executed by typing: erp:hello

I wondered why the command name is repeated again here. It turns out that the Blueprint declaration actually registers the command under the specified name, and the class annotations are only used for usage help, i.e. when typing: erp:hello --help

Deploying the Command Bundle/Plugin

I'm not yet using fancy launcher PDE feature here, just old-school style:
  1. Export the plugin as a binary JAR bundle to a folder
  2. Copy the plugin JAR to Karaf's deploy/ folder
Karaf will detect it and register the command:

karaf@bipposhell> la
START LEVEL 100 , List Threshold: 0
   ID   State         Blueprint      Level  Name
[   0] [Active     ] [            ] [    0] System Bundle (3.0.8)
[   1] [Active     ] [            ] [    5] OPS4J Pax Url - wrap: (1.2.5)
...
[  40] [Active     ] [Created     ] [   30] Apache Karaf :: Deployer :: Blueprint (2.2.0)
[  41] [Active     ] [Created     ] [   30] Apache Karaf :: Shell :: ConfigAdmin Commands (2.2.0)
[  42] [Active     ] [Created     ] [   60] Bippo Shell (1.0.0.qualifier)

karaf@bipposhell> erp:hello
Hello Ibrahim!

karaf@bipposhell> erp:hello --help
DESCRIPTION
        erp:hello

        Says hello

SYNTAX
        erp:hello [options]

OPTIONS
        --help
                Display this help message

Hot Deploying Eclipse Plugins

Karaf has very useful ability to do hot deploy of OSGi bundles during development. (actually, being OSGi, it can perform hot deployment anytime, not just "during development")
osgi:install and dev:watch Karaf commands will help you on this.

But I prefer the low-tech solution:

ceefour@annafi:~/project/bipponext/workspace/id.co.bippo.shell$ ln -s $PWD/bin /opt/apache-karaf-2.2.0/instances/bipposhell/deploy/id.co.bippo.shell

This creates a symlink to the ${project_loc}/bin folder inside the Karaf's deploy/ folder.
You may want to also copy the META-INF/MANIFEST.MF file. If not, it will still work in most cases, because Karaf is very smart to wrap non-OSGi "bundles" to be OSGi-friendly.
This magic works because Karaf supports "exploded bundles", i.e. OSGi bundles not packaged in a JAR, but exploded as a directory structure.

karaf@bipposhell> erp:hello
Hello Ibrahim!
(......edit the source in Eclipse, then Save...and......)
karaf@bipposhell> erp:hello
Hello Yudha!

No need to restart, nothing! You don't even need to build/package the JAR!
Pretty nifty, eh? :-)

Note: While doing this, I uncovered bug KARAF-602.
Karaf Tip: If something goes wrong, tail:log, log:set, and dev:show-tree Karaf commands are your friends. :-)

What's Next ?

As demonstrated, OSGi development can be easy and in some cases even easier than "plain" Java.

Actually I've managed to do a headless build using Eclipse Tycho (formerly Sonatype Tycho Maven plugin), so I hope to write about this in a later post.

To learn more about Eclipse plugins, I recommend the Eclipse Rich Client Platform (2nd Edition) book.
For a thorough explanation in developing OSGi applications, check out the recently released OSGi in Action, Creating Modular Applications in Java.

Sunday, January 2, 2011

Unit Testing EMF Models with EasyMock

During unit testing, I use EasyMock to mock an EMF-generated interface (I don't plan on persisting the interface nor its implementation).

Example usage is like this:

protected void setUp() {
initXmi();
voucher = AbispulsaFactory.eINSTANCE.createVoucher();
voucher.setCode("I10");
voucher.setPrice(BigDecimal.valueOf(11500));
voucher.setCost(BigDecimal.valueOf(9900));
owner = new DummyOwner();
fixture.setRefillListener(createMock(RefillListener.class));
}

refillListener will be used somewhere in the implementation :

...
refill.setVoucher(voucher);
DealerImpl.this.getRefills().add(refill);
beforeSave(refill);
try {
refill.eResource().save(null);
} catch (IOException e) {
throw new AbispulsaException(e);
}
getRefillListener().created(refill);
return refill;

The intention is to use a mock object for refillListener attribute/interface so I don't have to provide a real object.

But I got this error:

java.lang.ClassCastException: $Proxy0 cannot be cast to org.eclipse.emf.ecore.InternalEObject
at org.eclipse.emf.ecore.impl.EStructuralFeatureImpl$InternalSettingDelegateSingleEObject.dynamicGet(EStructuralFeatureImpl.java:2337)
at org.eclipse.emf.ecore.impl.BasicEObjectImpl.eDynamicGet(BasicEObjectImpl.java:1055)
at com.abispulsa.impl.DealerImpl.getRefillListener(DealerImpl.java:135)
at com.abispulsa.impl.DealerImpl$RefillTemplate.create(DealerImpl.java:85)
at com.abispulsa.impl.DealerImpl.refill(DealerImpl.java:288)
at com.abispulsa.tests.DealerTest.testRefill__Voucher_String_RefillOwner(DealerTest.java:351)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at junit.framework.TestCase.runTest(TestCase.java:168)
at junit.framework.TestCase.runBare(TestCase.java:134)
at junit.framework.TestResult$1.protect(TestResult.java:110)
at junit.framework.TestResult.runProtected(TestResult.java:128)
at junit.framework.TestResult.run(TestResult.java:113)
at junit.framework.TestCase.run(TestCase.java:124)
at junit.framework.TestSuite.runTest(TestSuite.java:232)
at junit.framework.TestSuite.run(TestSuite.java:227)
at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

The solution is to mark the attribute as Resolve Proxies = false. (In addition, I also mark Transient = true)

I'm not sure how this affects persistence mechanism such as Teneo or CDO, and if this is the proper way or not. If you know the proper way please advise. Thanks.

Saturday, January 1, 2011

POJO Remoting over Camel-XMPP within Eclipse App

I spent a lot of hours trying to make Remote Observer Pattern with Camel over XMPP work inside my Eclipse RCP Application, so here's my guide to make it run successfully.

Getting Camel


Getting Apache Camel itself to run is the easiest part, because Camel is already packaged as OSGi bundles, also available from the Maven Central repository.

Here are the artifacts that I needed:

  1. org.apache.camel:camel-core:2.5.0 (dependency of the ones below)
  2. org.apache.camel:camel-blueprint:2.5.0
  3. org.apache.camel:camel-eclipse:2.5.0
  4. org.apache.camel:camel-xmpp:2.5.0
  5. org.apache.camel:camel-xstream:2.5.0
These will pull additional dependencies, here are the JARs:

camel-blueprint-2.5.0.jar
camel-core-2.5.0.jar
camel-eclipse-2.5.0.jar
camel-xmpp-2.5.0.jar
camel-xstream-2.5.0.jar
commons-management-1.0.jar
javax.xml_1.3.4.v201005080400.jar
javax.xml.stream_1.0.1.v201004272200.jar
jettison-1.2.jar
org.apache.aries.blueprint-0.2-incubating.jar
org.apache.servicemix.bundles.xmlpull-1.1.3.1_1.jar
org.apache.servicemix.bundles.xpp3-1.1.4c_4.jar
org.apache.servicemix.bundles.xstream-1.3_4.jar

You'll notice that it includes the Apache Aries Blueprint container.
Camel can also use Blueprint service to run in OSGi. (It seems Camel can work in Eclipse without Blueprint, but I haven't tried it. Besides I think Blueprint is a nice touch, hehe..)
It's nice to know that Apache Aries Blueprint works well within Eclipse/Equinox.
(I was tempted to use Eclipse Gemini Blueprint but it seems "not ready yet", and I suspect it brings on Spring Framework dependencies, which I'm not interested at the moment.)

I need to replace some of the dependencies with OSGi bundles from:


Note that I've left out Smack JARs there, for reasons later below.

Hint: To know which OSGi-ready dependencies are available, check Camel for Karaf features list here :
http://repo1.maven.org/maven2/org/apache/camel/karaf/apache-camel/2.5.0/

Starting Apache Camel Bundles


Apache Camel and the Apache Aries Blueprint needs to be explicitly started, so after copying these files to the target platform, I need to go the Eclipse Launch configuration and mark these bundles as "start". I'm not sure which exact bundles need to be started, but I just start all camel-* bundles and all its dependencies and Apache Aries Blueprint.

If there is a trouble, it might be useful to tweak the start order, but I hope it's not necessary.

Using Camel with Blueprint

There are several ways to use Camel inside Eclipse, the traditional way without Blueprint is like this:

PackageScanClassResolver eclipseResolver = new EclipsePackageScanClassResolver();
CamelContext context = new DefaultCamelContext();
context.setPackageScanClassResolver(eclipseResolver);

To use Camel from a Blueprint beans XML file, create OSGI-INF/blueprint/config.xml file (directly below your plugin project folder, not inside the src folder) like this:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">

<camelContext xmlns="http://camel.apache.org/schema/blueprint" id="camelContext" autoStartup="true">
<route>
<from uri="direct:invoice" />
<to uri="seda:invoice.async" />
</route>
</camelContext>

<bean id="invoicing" class="id.co.bippo.usexmpp.Invoicing"
init-method="init" destroy-method="destroy">
<property name="camelContext" ref="camelContext" />
</bean>
</blueprint>

Note that autoStartup is true by default, you can change it to false.

The CamelContext instance will be injected into the class by Blueprint, I created a class like this:

import org.apache.camel.CamelContext;
import org.apache.camel.builder.ProxyBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.eclipse.EclipsePackageScanClassResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Invoicing {

private CamelContext camelContext;
private Logger logger = LoggerFactory.getLogger(getClass());
protected InvoiceListener loggerInvoiceListener = new LoggerInvoiceListener();

public void init() throws Exception {
camelContext.setPackageScanClassResolver(new EclipsePackageScanClassResolver());
camelContext.addRoutes(new RouteBuilder() {

@Override
public void configure() throws Exception {
// .. configure additional routes ...
}
});
camelContext.start();

InvoiceListener invoiceListener = new ProxyBuilder(camelContext)
.endpoint("direct:invoice").build(InvoiceListener.class);
invoiceListener.invoiceCreated(123, "Bippo Indonesia");
}

public void destroy() throws Exception {
camelContext.stop();
}

public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}

}

The simple application seems to run fine without setting EclipsePackageScanClassResolver, but I guess it's safer to use it.

Another way create a CamelContext using Blueprint XML is by instantiating it:

<bean id="camelContext" class="org.apache.camel.blueprint.BlueprintCamelContext">
<property name="bundleContext" ref="blueprintBundleContext" />
<property name="blueprintContainer" ref="blueprintContainer" />
<property name="packageScanClassResolver">
<bean class="org.apache.camel.component.eclipse.EclipsePackageScanClassResolver" />
</property>
</bean>

Using the factory bean directly:

<bean id="camelFactory" class="org.apache.camel.blueprint.CamelContextFactoryBean">
<property name="bundleContext" ref="blueprintBundleContext" />
<property name="blueprintContainer" ref="blueprintContainer" />
<property name="autoStartup" value="false" />
</bean>
<bean id="camelContext" factory-ref="camelFactory" factory-method="getContext" />

Xstream Dependencies

Xstream is needed to marshal/serialize objects to/from XML representation and/or JSON.

Xstream requires several dependencies:

  • javax.xml
  • javax.stream (aka StaX API)
  • xmlpull
  • xpp3 (aka xml.pull.mxp1)
  • Jettison

These are available from Apache ServiceMix repositories (above).

Making Xstream work with OSGi

Camel has a shortcut way of marshalling/unmarshalling objects with Xstream :

from("direct:invoice-json").marshal().json().("log:invoice");
from("direct:invoice-xstream").marshal().xstream().("log:invoice");
from("seda:json").unmarshal().json().("log:unmarshal");
from("seda:xml").unmarshal().xstream().("log:unmarshal");

However with OSGi we need to set the classloader which is used to resolve marshalled classes. The configuration becomes:

import org.apache.camel.dataformat.xstream.XStreamDataFormat;
import com.thoughtworks.xstream.XStream;
...
camelContext.addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {
XStream xstream = new XStream();
xstream.setClassLoader(getClass().getClassLoader());
XStreamDataFormat xstreamDataFormat = new XStreamDataFormat(xstream);

from("direct:invoice-xstream").marshal(xstreamDataFormat).("log:invoice");
from("seda:xml").unmarshal(xstreamDataFormat).("log:unmarshal");
}
});

To use the JSON format with Xstream, we need to configure the Xstream driver:

import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
...
xstreamDataFormat.setXstreamDriver(new JettisonMappedXmlDriver());

OSGi-fying Smack


In the plain Java world, we'll be done. But not so with Eclipse and OSGi.

I had the most trouble with the Smack library. The repackaged Smack library from Apache ServiceMix also didn't just work due to missing META-INF/smack.providers file and XMPPConnection not registering ServiceDiscoveryManager, which seem to happen because of static { ... } statements.

One problem is this code from Smack:

    public static Collection<String> getServiceNames(XMPPConnection connection) throws XMPPException {
        final List<String> answer = new ArrayList<String>();
        ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
        DiscoverItems items = discoManager.discoverItems(connection.getServiceName());
        for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) {

discoManager returns null, because the map of instances is empty.

The static code block here is not being called within OSGi runtime:

    // Create a new ServiceDiscoveryManager on every established connection
    static {
        XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() {
            public void connectionCreated(XMPPConnection connection) {
                new ServiceDiscoveryManager(connection);
            }
        });
    }

So I had to tweak Smack and published the changes to my Smack repository on GitHub.

I've also published the tweaked Smack 3.1.0 library as Eclipse plugin to Bippo OSS p2 Repository.

You can also download the bundle directly from: http://oss.bippo.co.id/p2/features/id.co.bippo.oss.smack_3.1.0.201101020232.jar

Note that my Smack bundle now requires SLF4j API for logging.

Tip: A nice way to debug Smack is by adding -Dsmack.debugEnabled=true to VM launch options.

I also learned several (re-)packaging tips with Eclipse plugins:

  • make sure to add "." in Runtime classpath when adding other classpaths (Apache ServiceMix repackaged Smack library includes other dependencies in folder containing .class files)
  • Workarounds for missing files can potentially be done using OSGi Fragment bundles, for example to augment
META-INF/smack.providers if so desired.

The App

Here's how the Camel XMPP test Eclipse app looks like:

import org.apache.camel.CamelContext;
import org.apache.camel.builder.ProxyBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.eclipse.EclipsePackageScanClassResolver;
import org.apache.camel.dataformat.xstream.XStreamDataFormat;
import org.apache.camel.model.dataformat.JsonDataFormat;
import org.apache.camel.model.dataformat.JsonLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

public class Invoicing {

private CamelContext camelContext;
private Logger logger = LoggerFactory.getLogger(getClass());
protected InvoiceListener loggerInvoiceListener = new LoggerInvoiceListener();

public void init() throws Exception {
logger.info("Invoicing started");
camelContext.setPackageScanClassResolver(new EclipsePackageScanClassResolver());
camelContext.addRoutes(new RouteBuilder() {

@Override
public void configure() throws Exception {
JsonDataFormat dataFormat = new JsonDataFormat(JsonLibrary.XStream);
XStream xstream = new XStream();
xstream.setClassLoader(getClass().getClassLoader());
XStreamDataFormat xstreamDataFormat = new XStreamDataFormat(xstream);
xstreamDataFormat.setXstreamDriver(new JettisonMappedXmlDriver());

final String xmppUri = "xmpp://abispulsabot@geulis.local/?room=invoice&password=test";
from("direct:invoice").marshal(xstreamDataFormat).threads().multicast()
.to(xmppUri)
.to("log:xmpp-output");
from(xmppUri).threads().multicast()
.to("log:xmpp-input")
.to("seda:loggerInvoiceBean");
from("seda:loggerInvoiceBean").unmarshal(xstreamDataFormat).bean(loggerInvoiceListener);
}
});
camelContext.start();

InvoiceListener invoiceListener = new ProxyBuilder(camelContext)
.endpoint("direct:invoice").build(InvoiceListener.class);
invoiceListener.invoiceCreated(123, "Bippo Indonesia");
}

public void destroy() throws Exception {
camelContext.stop();
logger.info("Invoicing stopped");
}

public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}

}

Log output:

04:41:04.302 [Blueprint Extender: 2] WARN  o.a.a.b.c.BlueprintContainerImpl - Bundle id.co.bippo.usexmpp is waiting for namespace handlers [(&(objectClass=org.apache.aries.blueprint.NamespaceHandler)(osgi.service.blueprint.namespace=http://camel.apache.org/schema/blueprint))]
04:41:04.301 [Start Level Event Dispatcher] INFO  org.apache.camel.impl.osgi.Activator - Camel activator starting
04:41:04.318 [Start Level Event Dispatcher] INFO  org.apache.camel.impl.osgi.Activator - Camel activator started
04:41:05.683 [Blueprint Extender: 2] INFO  org.apache.camel.impl.osgi.Activator - Found 13 @Converter classes to load
04:41:05.707 [Blueprint Extender: 2] INFO  o.a.c.b.BlueprintCamelContext - Starting Apache Camel as property ShouldStartContext is true
04:41:05.708 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camelContext) is starting
04:41:05.708 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - JMX enabled. Using ManagedManagementStrategy.
04:41:05.711 [Blueprint Extender: 2] WARN  o.a.camel.impl.DefaultCamelContext - Could not find needed classes for JMX lifecycle strategy. Needed class is in spring-context.jar using Spring 2.5 or newer (spring-jmx.jar using Spring 2.0.x). NoClassDefFoundError: org/springframework/jmx/export/metadata/JmxAttributeSource
04:41:05.711 [Blueprint Extender: 2] WARN  o.a.camel.impl.DefaultCamelContext - Cannot use JMX. Fallback to using DefaultManagementStrategy (non JMX).
04:41:05.717 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Total 0 routes, of which 0 is started.
04:41:05.718 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camelContext) started in 0.010 seconds
04:41:05.718 [Blueprint Extender: 2] INFO  id.co.bippo.usexmpp.Invoicing - Invoicing started
04:41:06.613 [Smack Packet Reader (0)] ERROR org.jivesoftware.smack.PacketReader - Empty IQ packet! </iq>
04:41:06.715 [Blueprint Extender: 2] INFO  o.a.c.c.xmpp.XmppGroupChatProducer - Joined room: invoice@conference.geulis as: abispulsabot
04:41:06.729 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Route: route1 started and consuming from: Endpoint[direct://invoice]
04:41:06.779 [Blueprint Extender: 2] INFO  o.a.c.component.xmpp.XmppConsumer - Joined room: invoice@conference.geulis as: abispulsabot
04:41:06.780 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Route: route2 started and consuming from: Endpoint[xmpp://abispulsabot@geulis.local/?password=******&room=invoice]
04:41:06.805 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Route: route3 started and consuming from: Endpoint[seda://loggerInvoiceBean]
04:41:06.806 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camelContext) is starting
04:41:06.806 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Total 3 routes, of which 3 is started.
04:41:06.806 [Blueprint Extender: 2] INFO  o.a.camel.impl.DefaultCamelContext - Apache Camel 2.5.0 (CamelContext: camelContext) started in 0.000 seconds
04:41:06.846 [Camel Thread 1 - Threads] INFO  xmpp-output - Exchange[ExchangePattern:InOut, BodyType:byte[], Body:{"org.apache.camel.component.bean.BeanInvocation":{"org.apache.camel.component.bean.MethodBean":{"name":"invoiceCreated","type":"id.co.bippo.usexmpp.InvoiceListener","parameterTypes":{"java-class":["int","java.lang.String"]}},"object-array":{"int":123,"string":"Bippo Indonesia"}}}]
04:41:06.858 [Camel Thread 2 - Threads] INFO  xmpp-input - Exchange[ExchangePattern:InOnly, BodyType:String, Body:{"org.apache.camel.component.bean.BeanInvocation":{"org.apache.camel.component.bean.MethodBean":{"name":"invoiceCreated","type":"id.co.bippo.usexmpp.InvoiceListener","parameterTypes":{"java-class":["int","java.lang.String"]}},"object-array":{"int":123,"string":"Bippo Indonesia"}}}]
04:41:06.880 [Camel Thread 0 - seda://loggerInvoiceBean] INFO  i.c.b.usexmpp.LoggerInvoiceListener - Invoice #123 name: Bippo Indonesia created
04:41:08.380 [Camel Thread 0 - seda://loggerInvoiceBean] INFO  i.c.b.usexmpp.LoggerInvoiceListener - 123/Bippo Indonesia done!

Conclusion


Apache Camel is unobtrusive way to add flexibility of routing, messaging, and integration patterns with XMPP or other connectors supported by Camel.
(you can create your own connectors if you want...)

I highly suggest the Camel in Action Book for the best in-depth guide and examples for using Camel to develop your applications more productively.

Wednesday, December 29, 2010

Activating the EMF Edit ItemProvider AdapterFactory-s (with CDO)

I used to have a problem with CDO Editor UI: it does not use my EMF Edit models' ItemProvider implementation (like label formatting, image/icon, and so on).

Today I found the reason, I changed the model's nsURI and did not change all references to it.

The relevant portion is plugin.xml for your.model.edit plugin :

   <extension point="org.eclipse.emf.edit.itemProviderAdapterFactories">
      <factory
            uri="http://www.abispulsa.com/model/1.0"
            class="com.abispulsa.provider.AbispulsaItemProviderAdapterFactory"
            supportedTypes=
              "org.eclipse.emf.edit.provider.IEditingDomainItemProvider
               org.eclipse.emf.edit.provider.IStructuredItemContentProvider
               org.eclipse.emf.edit.provider.ITreeItemContentProvider
               org.eclipse.emf.edit.provider.IItemLabelProvider
               org.eclipse.emf.edit.provider.IItemPropertySource"/>
   </extension>

The way to get ComposedAdapterFactory I previously blogged turned out to be incorrect:

public class AbispulsaContentProvider extends AdapterFactoryContentProvider {

private static AdapterFactory adapterFactory;

static {
adapterFactory = new ComposedAdapterFactory(new AdapterFactory[] {
new ResourceItemProviderAdapterFactory(),
new AbispulsaItemProviderAdapterFactory(),
new ReflectiveItemProviderAdapterFactory()
});
}

public AbispulsaContentProvider() {
super(adapterFactory);
}

}

The correct way is like this:

public class AbispulsaContentProvider extends AdapterFactoryContentProvider {

private static ComposedAdapterFactory adapterFactory;

static {
Registry registry = EMFEditPlugin.getComposedAdapterFactoryDescriptorRegistry();
adapterFactory = new ComposedAdapterFactory(registry);
adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
}

public AbispulsaContentProvider() {
super(adapterFactory);
}

}

Although then you should move the ComposedAdapterFactory creation to another singleton class so it can be reused from both ContentProvider and LabelProvider.

Note that for above to work, the extension org.eclipse.emf.edit.itemProviderAdapterFactories must also be set properly.

This is actually not CDO specific, but applies to all EMF Edit / Editor UI in general.
But it highlights a very important CDO feature: it reuses your EMF Edit ItemProvider implementations! :-)

Persisting EMF Objects to RDBMS with Teneo and Hibernate

In my project, EMF Objects are now being persisted in a CDO Model Repository. It used to use "plain Teneo", but before scraping the code I'd like to share it in my blog so in case I want to use Teneo directly again I won't be in trouble. ;-)

Teneo is a way to persist EMF Objects in a relational database using Hibernate and JPA annotations (you can also use EclipseLink, but I think Hibernate is better supported by Teneo).

JPA annotations are generated automatically by Teneo but you can configure the JPA mapping/annotations manually by using Teneo's JPA model annotations (not JPA Java annotations) on the EMF metamodel.

Connecting to the Hibernate EMF Data Store

private void initHibernate() {
// Set the database information, Environment is org.hibernate.cfg.Environment
final Properties props = new Properties();

// props.setProperty(Environment.DRIVER, "org.hsqldb.jdbcDriver");
// props.setProperty(Environment.USER, "sa");
// //props.setProperty(Environment.URL, "jdbc:hsqldb:file:/tmp/hsqldb");
// props.setProperty(Environment.URL, "jdbc:hsqldb:mem:abispulsamodel");
// props.setProperty(Environment.PASS, "");
// props.setProperty(Environment.DIALECT, org.hibernate.dialect.HSQLDialect.class.getName());
props.setProperty(Environment.DRIVER, "com.mysql.jdbc.Driver");
props.setProperty(Environment.USER, "root");
props.setProperty(Environment.URL, "jdbc:mysql://127.0.0.1:3306/abispulsamodel");
props.setProperty(Environment.PASS, "");
props.setProperty(Environment.DIALECT, MySQLInnoDBDialect.class.getName());
// Set cascade policy for containment and non-containment references
props.setProperty(PersistenceOptions.CASCADE_POLICY_ON_CONTAINMENT, "ALL");
props.setProperty(PersistenceOptions.CASCADE_POLICY_ON_NON_CONTAINMENT,
  "MERGE,PERSIST,REFRESH");

// Create and register the datastore, it will be accessible using
// this EMF Resource URI: hibernate://?dsname=abispulsa
hbds = HbHelper.INSTANCE.createRegisterDataStore("abispulsa");
// Set the EPackages that will be used with this data store
hbds.setEPackages(new EPackage[]{AbispulsaPackage.eINSTANCE});
hbds.setProperties(props);
hbds.initialize();
}

Note: the above syntax is for Teneo 1.1.2. For Teneo 1.2.0, setProperties() becomes setDataStoreProperties().

As you can see, configuring data store properties is just like with "plain" Hibernate.

You can change the DBMS backend easily by simply changing a few lines.
And of course you can externalize the configuration to a .properties file or .xml or whatever suits you best.

Using the Hibernate EMF Resource

Simple load:

HibernateResource resource = new HibernateResource(URI.createURI("hibernate://?dsname=abispulsa"));
resource.load(null);

Load with query, get specific EMF Object classes:
HibernateResource resource = new HibernateResource(
  URI.createURI("hibernate://?dsname=abispulsa&query1=FROM Organization"));
resource.load(null);

After you have a Resource, it's pretty much standard EMF / Resource stuff from here just like regular XMI-based resources.

"Copy and paste" (aka import) EMF Objects from an XMI file to Hibernate Resource:

ResourceSet rset = new ResourceSetImpl();
Resource xmiResource = rset.createResource(URI.createFileURI("/path/to/model/Catalog.xmi"));
try {
xmiResource.load(null);
log.info("XMI Resource has " + xmiResource.getContents().size() + " objects.");
for (EObject obj : xmiResource.getContents()) {
log.info(obj.toString());
}
Collection<EObject> copies = EcoreUtil.copyAll(xmiResource.getContents());
resource.getContents().addAll( copies );
resource.save(null);
} catch (Exception e) {
log.error("Error", e);
throw new RuntimeException(e);
}

Each time you open a Hibernate resource, a new Hibernate session is created by Teneo, which is fine for most purposes.

But you can change this by using your own session manager for Teneo.

Disconnecting from the Data Store

void closeHibernate() {
if (hbds != null) {
hbds.close();
HbHelper.INSTANCE.deRegisterDataStore(hbds);
hbds = null;
}
}

Comparison with CDO

With CDO it's almost the same, but with CDO there's a need to explicitly openTransaction() before editing objects in a CDO resource.

After manipulating a resource, either resource.save() or transaction.commit() will persist the changes.

Connect to CDO Server
private static final String CONNECTION_ADDRESS = "tcp://localhost:2036";
private static final String REPO_NAME = "abispulsabisnis";
...
log.info("Connecting to CDO to {} using repo {}", CONNECTION_ADDRESS, REPO_NAME);
org.eclipse.net4j.connector.IConnector connector = Net4jUtil.getConnector(IPluginContainer.INSTANCE, CONNECTION_ADDRESS);
// Create configuration
org.eclipse.emf.cdo.net4j.CDOSessionConfiguration sessionConfiguration = CDONet4jUtil.createSessionConfiguration();
sessionConfiguration.setConnector(connector);
sessionConfiguration.setRepositoryName(REPO_NAME);
org.eclipse.emf.cdo.session.CDOSession session = sessionConfiguration.openSession();
session.getPackageRegistry().putEPackage(AbispulsaPackage.eINSTANCE);

CDO is designed to be used over a network using TCP connector. But it's possible to run the CDO server and CDO client on the same VM instance, and a JVM Net4j connector is provided.

Open a Resource
org.eclipse.emf.cdo.transaction.CDOTransaction transaction = session.openTransaction();
org.eclipse.emf.cdo.eresource.CDOResource resource = transaction.getResource("/organization");

Save Changes
try {
//resource.save(null); // You can also do this (non-CDO aware)
transaction.commit(); // or this (CDO-aware)
} catch (Exception e) {
...
}

Update: As CDO committer Eike Stepper points out, CDO's resource.save() simply delegates to the underlying CDO Transaction's commit().

Close CDO Session
session.close();

That's it! I originally wanted to cover only Teneo but you got CDO Crash Course as a bonus. ;-)

Building a Workspace Resources-powered Common Navigator Framework (CNF) View

Common Navigator Framework (CNF) View is a very useful and flexible component to display structure of any object in an Eclipse RCP Application.

There are two ways to use CNF :

  1. Displaying Eclipse Resources inside Workspace
  2. Displaying custom content (non-resources)
The good news is the two ways can be mixed.
If you use Resources you can extend it with "custom content" underneath (using Navigator Content Extensions aka NCEs).

The bad news is even on the current milestone of Eclipse Indigo 3.7 it's still not straightforward to use CNF with Resources.
Again the good news is that there is a solution. :-)

I'll focus on displaying workspace resources (folders and files inside the workspace physical directory) in CNF because it's not obvious how to do it inside Eclipse RCP.
Note that if you use the IDE Application (at least the IDE WorkbenchAdvisor) it just works by default. But in Eclipse RCP you have to do some preparatory work.

The steps are outlined in Eclipse Help > Common Navigator Framework > Content and Action Binding, as follows.

Please note, that using CNF inside of your own RCP requires three additional steps.

  1. Add the
org.eclipse.ui.ide as dependent plug-in
  • To get the resource workspace root (
  • IWorkspaceRoot) as default page input, override the WorkbenchAdvisor.getDefaultPageInput() method in your WorkbenchAdvisor:
    public IAdaptable getDefaultPageInput()         {                 return ResourcesPlugin.getWorkspace().getRoot();         }
  • Hook the workbench adapters correctly before they are used, so add this code to the
  • WorkbenchAdvisor.initialize(IWorkbenchConfigurer) method:
    public void initialize(IWorkbenchConfigurer configurer)         {                 IDE.registerAdapters();         }

    This will display workspace resources with labels but there are no icons yet.

    To display icons you still need to "steal" the internal declareWorkbenchImages() from org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisor.java in org.eclipse.ide.ui.application plugin.

    So the complete relevant parts become like this:

    import org.eclipse.core.resources.IWorkspaceRoot;
    import org.eclipse.core.resources.ResourcesPlugin;
    import org.eclipse.core.runtime.FileLocator;
    import org.eclipse.core.runtime.IAdaptable;
    import org.eclipse.core.runtime.Path;
    import org.eclipse.core.runtime.Platform;
    import org.eclipse.jface.resource.ImageDescriptor;
    import org.eclipse.ui.application.IWorkbenchConfigurer;
    import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
    import org.eclipse.ui.application.WorkbenchAdvisor;
    import org.eclipse.ui.application.WorkbenchWindowAdvisor;
    import org.eclipse.ui.ide.IDE;
    import org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages;
    import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
    import org.osgi.framework.Bundle;
    ...
    @Override
    public void initialize(IWorkbenchConfigurer configurer) {
    super.initialize(configurer);
    //configurer.setSaveAndRestore(true); // if you want
    IDE.registerAdapters();
    declareWorkbenchImages();
    }
    ...
    /**
    * Declares all IDE-specific workbench images. This includes both "shared"
    * images (named in {@link IDE.SharedImages}) and internal images (named in
    * {@link org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages}).
    *
    * @see IWorkbenchConfigurer#declareImage
    */
    private void declareWorkbenchImages() {

    final String ICONS_PATH = "$nl$/icons/full/";//$NON-NLS-1$
    final String PATH_ELOCALTOOL = ICONS_PATH + "elcl16/"; // Enabled //$NON-NLS-1$

    // toolbar
    // icons.
    final String PATH_DLOCALTOOL = ICONS_PATH + "dlcl16/"; // Disabled //$NON-NLS-1$
    // //$NON-NLS-1$
    // toolbar
    // icons.
    final String PATH_ETOOL = ICONS_PATH + "etool16/"; // Enabled toolbar //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons.
    final String PATH_DTOOL = ICONS_PATH + "dtool16/"; // Disabled toolbar //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons.
    final String PATH_OBJECT = ICONS_PATH + "obj16/"; // Model object //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons
    final String PATH_WIZBAN = ICONS_PATH + "wizban/"; // Wizard //$NON-NLS-1$
    // //$NON-NLS-1$
    // icons

    // View icons
    final String PATH_EVIEW= ICONS_PATH + "eview16/"; //$NON-NLS-1$


    Bundle ideBundle = Platform.getBundle(IDEWorkbenchPlugin.IDE_WORKBENCH);

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC, PATH_ETOOL
    + "build_exec.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC_HOVER,
    PATH_ETOOL + "build_exec.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC_DISABLED,
    PATH_DTOOL + "build_exec.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC, PATH_ETOOL
    + "search_src.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC_HOVER,
    PATH_ETOOL + "search_src.gif", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_SEARCH_SRC_DISABLED,
    PATH_DTOOL + "search_src.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_NEXT_NAV, PATH_ETOOL
    + "next_nav.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PREVIOUS_NAV, PATH_ETOOL
    + "prev_nav.gif", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_NEWPRJ_WIZ, PATH_WIZBAN
    + "newprj_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_NEWFOLDER_WIZ,
    PATH_WIZBAN + "newfolder_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_NEWFILE_WIZ, PATH_WIZBAN
    + "newfile_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_IMPORTDIR_WIZ,
    PATH_WIZBAN + "importdir_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_IMPORTZIP_WIZ,
    PATH_WIZBAN + "importzip_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_EXPORTDIR_WIZ,
    PATH_WIZBAN + "exportdir_wiz.png", false); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_EXPORTZIP_WIZ,
    PATH_WIZBAN + "exportzip_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_WIZBAN_RESOURCEWORKINGSET_WIZ,
    PATH_WIZBAN + "workset_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_DLGBAN_SAVEAS_DLG, PATH_WIZBAN
    + "saveas_wiz.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_DLGBAN_QUICKFIX_DLG, PATH_WIZBAN
    + "quick_fix.png", false); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJ_PROJECT,
    PATH_OBJECT + "prj_obj.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDE.SharedImages.IMG_OBJ_PROJECT_CLOSED, PATH_OBJECT
    + "cprj_obj.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OPEN_MARKER,
    PATH_ELOCALTOOL + "gotoobj_tsk.gif", true); //$NON-NLS-1$


    // Quick fix icons
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ELCL_QUICK_FIX_ENABLED,
    PATH_ELOCALTOOL + "smartmode_co.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_DLCL_QUICK_FIX_DISABLED,
    PATH_DLOCALTOOL + "smartmode_co.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_FIXABLE_WARNING,
    PATH_OBJECT + "quickfix_warning_obj.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_FIXABLE_ERROR,
    PATH_OBJECT + "quickfix_error_obj.gif", true); //$NON-NLS-1$


    // task objects
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_HPRIO_TSK,
    // PATH_OBJECT+"hprio_tsk.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_MPRIO_TSK,
    // PATH_OBJECT+"mprio_tsk.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_LPRIO_TSK,
    // PATH_OBJECT+"lprio_tsk.gif");

    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJS_TASK_TSK,
    PATH_OBJECT + "taskmrk_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle, IDE.SharedImages.IMG_OBJS_BKMRK_TSK,
    PATH_OBJECT + "bkmrk_tsk.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_COMPLETE_TSK, PATH_OBJECT
    + "complete_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_INCOMPLETE_TSK, PATH_OBJECT
    + "incomplete_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_ITEM, PATH_OBJECT
    + "welcome_item.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_WELCOME_BANNER, PATH_OBJECT
    + "welcome_banner.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_ERROR_PATH, PATH_OBJECT
    + "error_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_WARNING_PATH, PATH_OBJECT
    + "warn_tsk.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_OBJS_INFO_PATH, PATH_OBJECT
    + "info_tsk.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_LCL_FLAT_LAYOUT, PATH_ELOCALTOOL
    + "flatLayout.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_LCL_HIERARCHICAL_LAYOUT,
    PATH_ELOCALTOOL + "hierarchicalLayout.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEM_CATEGORY,
    PATH_ETOOL + "problem_category.gif", true); //$NON-NLS-1$

    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW,
    PATH_EVIEW + "problems_view.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW_ERROR,
    PATH_EVIEW + "problems_view_error.gif", true); //$NON-NLS-1$
    declareWorkbenchImage(ideBundle,
    IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEMS_VIEW_WARNING,
    PATH_EVIEW + "problems_view_warning.gif", true); //$NON-NLS-1$

    // synchronization indicator objects
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_WBET_STAT,
    // PATH_OVERLAY+"wbet_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_SBET_STAT,
    // PATH_OVERLAY+"sbet_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_CONFLICT_STAT,
    // PATH_OVERLAY+"conflict_stat.gif");

    // content locality indicator objects
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_NOTLOCAL_STAT,
    // PATH_STAT+"notlocal_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_LOCAL_STAT,
    // PATH_STAT+"local_stat.gif");
    // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_FILLLOCAL_STAT,
    // PATH_STAT+"filllocal_stat.gif");
    }

    /**
    * Declares an IDE-specific workbench image.
    *
    * @param symbolicName
    *            the symbolic name of the image
    * @param path
    *            the path of the image file; this path is relative to the base
    *            of the IDE plug-in
    * @param shared
    *            <code>true</code> if this is a shared image, and
    *            <code>false</code> if this is not a shared image
    * @see IWorkbenchConfigurer#declareImage
    */
    private void declareWorkbenchImage(Bundle ideBundle, String symbolicName,
    String path, boolean shared) {
    URL url = FileLocator.find(ideBundle, new Path(path), null);
    ImageDescriptor desc = ImageDescriptor.createFromURL(url);
    getWorkbenchConfigurer().declareImage(symbolicName, desc, shared);
    }

    Additional resources:

    Types of Applications You Can Create with Eclipse Platform

    The Eclipse Platform provides many tools and frameworks to build your application. They are flexible so you can do it in several ways according to your requirements.

    Here are the most common application types:

    1. OSGi (Equinox) Application

    Using the bare minimum OSGi runtime Equinox (or your favorite OSGi runtime).
    You can use OSGi services and OSGi bundles, but not loading the Eclipse Runtime (org.eclipse.core.runtime).

    2. Eclipse Console Application

    Using Eclipse Runtime (org.eclipse.core.runtime), but without any UI bundles.
    You can actually do a lot of things in this mode.

    3. Server/Daemon-style Eclipse Console Application

    This is actually just an Eclipse Console application, but that does not exit and continue running in the background.
    Daemon applications usually started with the -noExit command line argument or configuration.

    4. Eclipse Rich Client Platform (RCP) for Desktop Applications

    The most typical Eclipse application for the desktop.
    Uses the org.eclipse.rcp plugin.
    With features like perspectives, views, Common Navigator Framework, editors, action bars (toolbars), status line, menus, popup menus, ...
    It offers the rich plugins and functionality provided by Eclipse framework, and manageable overhead (disk space, memory usage, and performance).
    A well designed Eclipse RCP application starts just as fast as a hand-coded Swing application!
    Don't let the (slow) startup time of Eclipse Java IDE fool you...

    5. Eclipse IDE Application

    The most representative example is the Eclipse Java IDE itself.
    Uses org.eclipse.ui.ide plugin and other plugins, including org.eclipse.jdt.ui, org.eclipse.team.ui.
    Most application developers do not use these plugins, for smaller disk and memory footprint requirements.
    However, just to let you know that you can use them if you want. :-)

    6. Eclipse Rich Ajax Platform (RAP) for Web Applications

    The new kid on the block, but is already very potential and is gaining momentum very fast.
    Uses org.eclipse.rap.ui plugin.
    RAP applications can be single sourced so that it runs on both Eclipse RCP and Eclipse RAP runtimes, however you need to be aware that RAP applications are multi-user / multi-session, where RCP applications are single user.
    The most common Eclipse UI plugins are supported by RAP runtime (including EMF Editor UI), and more (Draw2d, Common Navigator Framework aka CNF) are still coming soon.

    7. Eclipse Riena RCP Application

    Framework for multi-tier / distributed application development.
    Write your plugins / application components in an uniform approach.
    Then deploy them in any side, whether in server or client-side.
    Eclipse Riena applications have at least two deployment artifacts, the one in client and the one in server.
    However, components do not care about the deployment, you write the components using the same framework.
    Riena abstracts the details of the deployment.

    8. Eclipse Riena RAP Application

    The exact same application that you can write on Eclipse Riena RCP, can be deployed as a web application using RAP frontend!

    9. Standalone Java application

    Contrary to what you might have believed, many Eclipse projects that bears the prefix "org.eclipse" in its name do not require running within Eclipse Runtime.
    For example, EMF (Eclipse Modeling Framework) runs fine within a standard Java static main() method.
    You just need to configure several stuff, that are normally done automatically using Eclipse extension mechanism. ;-)