Sunday, July 15, 2012

Using Javassist to Create a Karaf/GoGo Shell Command Dynamically

Karaf OSGi runtime provides an excellent shell console functionality called Felix GoGo that can be extended easily using Blueprint XML :

<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.0.0">
    <command name="process/run">
        <action class="com.soluvas.process.shell.ProcessRunCommand">
            <argument ref="processContainer"/>
            <argument ref="ksession"/>
            <argument ref="dependencyLookup"/>
        </action>
    </command>
    <command name="process/ls">
        <action class="com.soluvas.process.shell.ProcessLsCommand">
            <argument ref="processContainer"/>
        </action>
    </command>
    <command name="process/class">
        <action class="com.soluvas.process.shell.MakeClassCommand">
            <argument ref="blueprintBundleContext"/>
        </action>
    </command>
</command-bundle>

A Karaf Shell Blueprint command element is equivalent to:

<bean id="processLsCommand" scope="prototype"

class="com.soluvas.process.shell.ProcessLsCommand">

<argument ref="processContainer"/>

</bean>

<service auto-export="interfaces">

<service-properties>

<entry key="osgi.command.scope" value="test"/>

<entry key="osgi.command.function" value="ls"/>

</service-properties>

<bean class="org.apache.karaf.shell.console.commands.BlueprintCommand">

<property name="blueprintContainer" ref="blueprintContainer"/>

<property name="blueprintConverter" ref="blueprintConverter"/>

<property name="actionId"><idref component-id="processLsCommand"/></property>

</bean>

</service>


Using the bytecode tooling library Javassist, it's possible (and almost quite practical) to create GoGo commands dynamically at runtime :

ClassPool pool = new ClassPool();
pool.appendClassPath(new LoaderClassPath(getClass().getClassLoader()));

CtClass helloClass = pool.makeClass("cc.HelloCommand");
helloClass.setSuperclass(pool.get("org.apache.karaf.shell.console.OsgiCommandSupport"));
helloClass.addInterface(pool.get("org.apache.karaf.shell.console.CompletableFunction"));
helloClass.addInterface(pool.get("org.apache.felix.service.command.Function"));

ClassFile cf = helloClass.getClassFile();
AnnotationsAttribute attr = new AnnotationsAttribute(cf.getConstPool(), AnnotationsAttribute.visibleTag);
Annotation annotation = new Annotation("org.apache.felix.gogo.commands.Command", cf.getConstPool());
annotation.addMemberValue("scope", new StringMemberValue("test", cf.getConstPool()));
annotation.addMemberValue("name", new StringMemberValue("hello", cf.getConstPool()));
annotation.addMemberValue("description", new StringMemberValue("Hello!", cf.getConstPool()));
attr.addAnnotation(annotation);
cf.addAttribute(attr);

CtMethod doExecuteMethod = CtMethod.make("protected Object doExecute() throws Exception { System.out.println(\"OMG!\"); return \"Hello!\"; }", helloClass);
helloClass.addMethod(doExecuteMethod);

System.out.println("Class: " + helloClass);
Class clazz = helloClass.toClass(getClass().getClassLoader(), getClass().getProtectionDomain());
System.out.println("Created " + clazz);
final Object obj = clazz.newInstance();

BlueprintCommand cmd = new BlueprintCommand() {
    @Override
    public Action createNewAction() {
        return (Action)obj;
    }
};
Hashtable<String, String> props = new Hashtable<String, String>();
props.put(CommandProcessor.COMMAND_SCOPE, "test");
props.put(CommandProcessor.COMMAND_FUNCTION, "hello");
bundleContext.registerService(new String[] { CompletableFunction.class.getName(),
                Function.class.getName() }, cmd, props);

return null;

JBoss jBPM 5.3.0.Final problem in Apache Karaf OSGi Runtime


EMF works, Drools works, but now cannot start the jBPM bundle :
registering api : org.jbpm.process.instance.ProcessRuntimeFactoryServiceImpl@70cf08b1 : interface org.drools.runtime.process.ProcessRuntimeFactoryService  registering compiler : org.jbpm.process.instance.ProcessRuntimeFactoryServiceImpl@70cf08b1 : interface org.drools.runtime.process.ProcessRuntimeFactoryService  registering api : org.jbpm.marshalling.impl.ProcessMarshallerFactoryServiceImpl@69c78571 : interface org.drools.marshalling.impl.ProcessMarshallerFactoryService  registering api : org.jbpm.process.builder.ProcessBuilderFactoryServiceImpl@7442df79 : interface org.drools.compiler.ProcessBuilderFactoryService  ERROR: Bundle org.jbpm.bpmn2 [170] Error starting mvn:org.jbpm/jbpm-bpmn2/5.3.0.Final (org.osgi.framework.BundleException: Activator start error in bundle org.jbpm.bpmn2 [170].)  java.lang.NullPointerException          at org.apache.felix.framework.resolver.ResolverImpl.toStringBlame(ResolverImpl.java:1583)          at org.apache.felix.framework.resolver.ResolverImpl.checkPackageSpaceConsistency(ResolverImpl.java:1007)          at org.apache.felix.framework.resolver.ResolverImpl.resolve(ResolverImpl.java:171)          at org.apache.felix.framework.Felix$FelixResolver.resolve(Felix.java:4103)          at org.apache.felix.framework.ModuleImpl.searchDynamicImports(ModuleImpl.java:1412)          at org.apache.felix.framework.ModuleImpl.findClassOrResourceByDelegation(ModuleImpl.java:734)          at org.apache.felix.framework.ModuleImpl.access$400(ModuleImpl.java:71)          at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.loadClass(ModuleImpl.java:1768)          at java.lang.ClassLoader.loadClass(ClassLoader.java:266)          at org.jbpm.osgi.bpmn2.Activator.start(Activator.java:35)          at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:629)          at org.apache.felix.framework.Felix.activateBundle(Felix.java:1842)          at org.apache.felix.framework.Felix.startBundle(Felix.java:1759)          at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1163)          at org.apache.felix.framework.StartLevelImpl.run(StartLevelImpl.java:264)          at java.lang.Thread.run(Thread.java:679)  
Still not sure why... Even doing dev:show-tree breaks Karaf :

2012-07-15 14:10:48,870 | INFO  | l Console Thread | Console                          | 36 - org.apache.karaf.shell.console - 2.2.8 | Exception caught while executing command
java.lang.ArrayIndexOutOfBoundsException: 18
        at org.apache.karaf.shell.dev.util.Import.split(Import.java:144)[18:org.apache.karaf.shell.dev:2.2.8]
        at org.apache.karaf.shell.dev.util.Import.parse(Import.java:104)[18:org.apache.karaf.shell.dev:2.2.8]
        at org.apache.karaf.shell.dev.ShowBundleTree.createNodesForImports(ShowBundleTree.java:136)[18:org.apache.karaf.shell.dev:2.2.8]
        at org.apache.karaf.shell.dev.ShowBundleTree.createTree(ShowBundleTree.java:128)[18:org.apache.karaf.shell.dev:2.2.8]
        at org.apache.karaf.shell.dev.ShowBundleTree.doExecute(ShowBundleTree.java:58)[18:org.apache.karaf.shell.dev:2.2.8]
        at org.apache.karaf.shell.dev.AbstractBundleCommand.doExecute(AbstractBundleCommand.java:61)[18:org.apache.karaf.shell.dev:2.2.8]
        at org.apache.karaf.shell.console.OsgiCommandSupport.execute(OsgiCommandSupport.java:38)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.commands.basic.AbstractCommand.execute(AbstractCommand.java:35)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.runtime.CommandProxy.execute(CommandProxy.java:78)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.runtime.Closure.executeCmd(Closure.java:474)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.runtime.Closure.executeStatement(Closure.java:400)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.runtime.Pipe.run(Pipe.java:108)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:183)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:120)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.felix.gogo.runtime.CommandSessionImpl.execute(CommandSessionImpl.java:89)[36:org.apache.karaf.shell.console:2.2.8]
        at org.apache.karaf.shell.console.jline.Console.run(Console.java:166)[36:org.apache.karaf.shell.console:2.2.8]
        at java.lang.Thread.run(Thread.java:679)[:1.6.0_23]


JBoss BRMS claims that jBPM is OSGi-friendly.
jBpm Forum Thread with exact same problem.

Bugs filed:

Plain EMF inside Apache Karaf OSGi Runtime

Since EMF bundles doesn't work out-of-the-box inside Karaf, and installing Eclipse libraries wreak havoc. I think it's better to simulate "plain EMF" inside OSGi.

Required patches are:

  1. org.eclipse.emf.common :
    remove Require-Bundle from MANIFEST.MF

    remove Bundle-Activator

  2. org.eclipse.emf.ecore:
    replace Require-Bundle with: Require-Bundle: org.eclipse.emf.common

    Import-Package: org.eclipse.emf.ecore,org.eclipse.emf.ecore.impl,org.e
    clipse.emf.ecore.plugin,org.eclipse.emf.ecore.resource,org.eclipse.em
    f.ecore.resource.impl,org.eclipse.emf.ecore.util,org.eclipse.emf.ecor
    e.xml.namespace,org.eclipse.emf.ecore.xml.namespace.impl,org.eclipse.
    emf.ecore.xml.namespace.util,org.eclipse.emf.ecore.xml.type,org.eclip
    se.emf.ecore.xml.type.impl,org.eclipse.emf.ecore.xml.type.internal,or
    g.eclipse.emf.ecore.xml.type.util,org.xml.sax,org.xml.sax.helpers,
    org.xml.sax.ext,org.w3c.dom,javax.xml.parsers,javax.xml.namespace,
    javax.xml.datatype

    remove Bundle-Activator

  3. org.eclipse.emf.ecore.xmi:
    replace Require-Bundle with: Require-Bundle: org.eclipse.emf.common,org.eclipse.emf.ecore

    Import-Package: org.eclipse.emf.ecore.xmi,org.eclipse.emf.ecore.xmi.im
    pl,org.eclipse.emf.ecore.xmi.util,org.xml.sax,org.xml.sax.helpers,
    org.xml.sax.ext,org.w3c.dom,javax.xml.parsers,javax.xml.namespaceremove Bundle-Activator

Saturday, July 14, 2012

EMF not yet runnable in Apache Karaf OSGi Runtime

My attempt at deploying EMF inside Karaf 2.2.8 has not yet been successful.

I've tried adding Import-Package instruction:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>2.3.7</version>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Import-Package>*, org.eclipse.emf.ecore, org.eclipse.emf.ecore.impl, org.eclipse.emf.ecore.plugin</Import-Package>
        </instructions>
    </configuration>
    <executions>
        <execution>
            <id>manifest</id>
            <phase>process-classes</phase>
            <goals>
                <goal>manifest</goal>
            </goals>
            <configuration>
            </configuration>
        </execution>
    </executions>
</plugin>

However the mere mention of an EMF Factory :

processCatalog = ProcessFactory.eINSTANCE.createProcessCatalog();

Throws exception:

2012-07-15 11:57:25,057 | ERROR | rint Extender: 3 | BlueprintContainerImpl           | 9 - org.apache.aries.blueprint - 0.3.2 | Unable to start blueprint container for bundle com.soluvas.com.soluvas.process.shell
org.osgi.service.blueprint.container.ComponentDefinitionException: Error when instanciating bean mock of class class com.soluvas.process.shell.Mock
        at org.apache.aries.blueprint.container.BeanRecipe.getInstance(BeanRecipe.java:271)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BeanRecipe.internalCreate(BeanRecipe.java:708)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.di.AbstractRecipe.create(AbstractRecipe.java:64)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BlueprintRepository.createInstances(BlueprintRepository.java:219)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BlueprintRepository.createAll(BlueprintRepository.java:147)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BlueprintContainerImpl.instantiateEagerComponents(BlueprintContainerImpl.java:631)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BlueprintContainerImpl.doRun(BlueprintContainerImpl.java:337)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BlueprintContainerImpl.run(BlueprintContainerImpl.java:230)[9:org.apache.aries.blueprint:0.3.2]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)[:1.6.0_23]
        at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)[:1.6.0_23]
        at java.util.concurrent.FutureTask.run(FutureTask.java:166)[:1.6.0_23]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:165)[:1.6.0_23]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:266)[:1.6.0_23]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)[:1.6.0_23]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)[:1.6.0_23]
        at java.lang.Thread.run(Thread.java:679)[:1.6.0_23]
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.soluvas.process.ProcessFactory
        at com.soluvas.process.shell.Mock.<init>(Mock.java:12)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)[:1.6.0_23]
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)[:1.6.0_23]
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)[:1.6.0_23]
        at java.lang.reflect.Constructor.newInstance(Constructor.java:532)[:1.6.0_23]
        at org.apache.aries.blueprint.utils.ReflectionUtils.newInstance(ReflectionUtils.java:257)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BeanRecipe.newInstance(BeanRecipe.java:842)[9:org.apache.aries.blueprint:0.3.2]
        at org.apache.aries.blueprint.container.BeanRecipe.getInstance(BeanRecipe.java:269)[9:org.apache.aries.blueprint:0.3.2]
        ... 15 more

Here are the list of bundles I've installed:

karaf@root> list -s
START LEVEL 100 , List Threshold: 50
   ID   State         Blueprint      Level  Symbolic name
[  75] [Active     ] [Created     ] [   80] org.ops4j.pax.wicket.service (1.0.1)
[  79] [Resolved   ] [            ] [   80] com.google.guava (12.0.0)
[  91] [Resolved   ] [            ] [   80] org.apache.servicemix.bundles.cglib (2.2.2.1)
[ 136] [Active     ] [            ] [   80] tomcat-sample (0)
[ 147] [Active     ] [            ] [   80] com.wikindonesia.place (0.0.0)
[ 148] [Active     ] [            ] [   80] com.wikindonesia.checkin (0.0.0)
[ 150] [Active     ] [            ] [   80] com.wikindonesia.cafe (0.0.0)
[ 151] [Active     ] [            ] [   80] org.soluvas.process (0.0.0)
[ 158] [Active     ] [            ] [   50] org.apache.camel.camel-core (2.10.0)
[ 159] [Active     ] [Created     ] [   50] org.apache.camel.karaf.camel-karaf-commands (2.10.0)
[ 160] [Active     ] [Created     ] [   50] org.apache.camel.camel-blueprint (2.10.0)
[ 161] [Active     ] [            ] [   80] org.apache.geronimo.specs.geronimo-annotation_1.1_spec (1.0.1)
[ 162] [Active     ] [            ] [   80] org.drools.core (5.4.0.Final)
[ 163] [Active     ] [            ] [   80] org.drools.compiler (5.4.0.Final)
[ 164] [Active     ] [            ] [   80] org.drools.templates (5.4.0.Final)
[ 166] [Active     ] [            ] [   80] org.mvel2 (2.1.0.drools16)
[ 167] [Active     ] [            ] [   80] org.drools.api (5.4.0.Final)
[ 168] [Active     ] [            ] [   80] org.jbpm.flow.core (5.3.0.Final)
[ 169] [Active     ] [            ] [   80] org.jbpm.flow.builder (5.3.0.Final)
[ 170] [Resolved   ] [            ] [   80] org.jbpm.bpmn2 (5.3.0.Final)
[ 172] [Active     ] [            ] [   80] org.apache.servicemix.bundles.jaxb-xjc (2.2.4.2)
[ 179] [Active     ] [            ] [   80] wrap_mvn_com.sun.istack_istack-commons-runtime_2.12_Export-Package___version_2.12.0 (0)
[ 182] [Active     ] [            ] [   80] org.apache.servicemix.specs.jaxb-api-2.2 (2.0.0)
[ 183] [Active     ] [            ] [   80] org.apache.servicemix.bundles.jaxb-impl (2.2.4.2)
[ 185] [Resolved   ] [            ] [   80] wrap_mvn_com.thoughtworks.xstream_xstream_1.4.2_Export-Package___version_1.4.2 (0)
[ 186] [Resolved   ] [            ] [   80] wrap_mvn_com.google.protobuf_protobuf-java_2.4.1_Export-Package___version_2.4.1 (0)
[ 187] [Resolved   ] [            ] [   80] org.drools.internalapi (5.4.0.Final)
[ 188] [Installed  ] [            ] [   80] wrap_mvn_antlr_antlr_2.7.7_Export-Package___version_2.7.7 (0)
[ 189] [Resolved   ] [            ] [   80] org.apache.servicemix.bundles.antlr-runtime (3.4.0.2)
[ 190] [Active     ] [Failure     ] [   80] com.soluvas.com.soluvas.process.shell (1.0.0.SNAPSHOT)
[ 210] [Resolved   ] [            ] [   80] org.eclipse.osgi (3.7.0.v20110613)
[ 212] [Resolved   ] [            ] [   80] org.eclipse.core.runtime (3.7.0.v20110110)
[ 213] [Resolved   ] [            ] [   80] org.eclipse.emf.common (2.8.0.v20120606-0717)
[ 214] [Resolved   ] [            ] [   80] org.eclipse.emf.ecore (2.8.0.v20120606-0717)
[ 215] [Resolved   ] [            ] [   80] org.eclipse.emf.ecore.xmi (2.8.0.v20120606-0717)
[ 216] [Resolved   ] [            ] [   80] org.eclipse.equinox.common (3.6.0.v20110523)
[ 217] [Resolved   ] [            ] [   80] org.eclipse.core.jobs (3.5.100.v20110404)
[ 218] [Resolved   ] [            ] [   80] org.eclipse.equinox.registry (3.5.0.v20100503)
[ 219] [Resolved   ] [            ] [   80] org.eclipse.equinox.preferences (3.4.0.v20110502)
[ 220] [Resolved   ] [            ] [   80] org.eclipse.core.contenttype (3.4.100.v20110423-0524)
[ 221] [Resolved   ] [            ] [   80] org.eclipse.equinox.app (1.3.0.v20100512)
[ 222] [Active     ] [            ] [   80] org.soluvas.async (0.0.0)


Note that I'm also unable to start any EMF bundles:

karaf@root> start 213
org.osgi.framework.BundleException: Activator start error in bundle org.eclipse.emf.common [213].

But there's no error log to be found.

How to Install EMF (Eclipse Modeling Framework) Plug-ins/Bundles on Apache Karaf OSGi Runtime

EMF depends on org.eclipse.core.runtime via Require-Bundle, which in turn depends on org.eclipse.osgi, org.eclipse.equinox.common etc.

I think there's should a better way to use EMF "cleanly" in Karaf. But for now this works:

install mvn:org.jibx.config.3rdparty.org.eclipse/org.eclipse.equinox.common/3.6.0.v20110523
install mvn:org.eclipse.equinox/org.eclipse.equinox.registry/3.5.0.v20100503
install mvn:org.jibx.config.3rdparty.org.eclipse/org.eclipse.equinox.preferences/3.4.0.v20110502
install mvn:org.eclipse.equinox/org.eclipse.equinox.app/1.3.0.v20100512
install mvn:org.jibx.config.3rdparty.org.eclipse/org.eclipse.osgi/3.7.0.v20110613
install mvn:org.jibx.config.3rdparty.org.eclipse/org.eclipse.core.runtime/3.7.0.v20110110
install mvn:org.jibx.config.3rdparty.org.eclipse/org.eclipse.core.jobs/3.5.100.v20110404
install mvn:org.jibx.config.3rdparty.org.eclipse/org.eclipse.core.contenttype/3.4.100.v20110423-0524

install mvn:org.eclipse.emf/org.eclipse.emf.common/2.8.0.v20120606-0717
install mvn:org.eclipse.emf/org.eclipse.emf.ecore/2.8.0.v20120606-0717
install mvn:org.eclipse.emf/org.eclipse.emf.ecore.xmi/2.8.0.v20120606-0717