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

Friday, April 6, 2012

Tackling Reverse Code Generation

Reverse Code Generation has been kindly discussed in depth by my friend Daniel Dietrich.

To review, the goal is to make it possible to generate 2 artifacts (generator templates and generated application) from 2 artifacts (model and prototype application).

Given this pseudo-Java prototype application:

public void runHello(String[] args) {
  System.out.println("Welcome to Hello World!");
}

And this model:

appName "Hello"
appTitle "Hello World"

I'd like to generate (or perhaps, "reverse generate") this generator template (in StringTemplate syntax):

public void run$appName$(String[] args) {
  System.out.println("Welcome to $appTitle$!");
}

You can think of the above as a representation of the "template" model too. (template is data therefore template is a model, right?)

Now given the actual application model :

appName "Car"
appTitle "Ultimate Car Selling"

This model plus the generated generator template would ultimately generate the final application :

public void runCar(String[] args) {
  System.out.println("Welcome to Ultimate Car Selling!");
}


Is it Worth It?

My first question even if all this is possible and practical, is it worth all the hassle?

My hunch tells me that right now, at best it's seldom used and at worst exotic.

And then about limitations. It's like projecting 3D onto 2D and then trying to get 3D back out of that 2D. It's not just challenging, but some information is lost.

These information can be recorded somewhere (like metadata for generated code), however if we used features like protected regions it'll make it even more challenging. (some heuristics would help though)

Then about loops and conditionals.

The Solution?

The way I think about it right now is that reverse code generation would be like a submodule inside a greater code generation task. Kinda like protected regions. So the reverse generator does not work globally, but only in specific parts of the project. And for those parts, we know that there are no loops or conditionals or complex constructs. UPCASE and down_case transformations etc. may still be practical.

The key lies in the prototype application. The prototype needs to serve a double role:
  1. It is directly executable/buildable as a project in the target platform environment
  2. It can be extracted to form generator templates

So we have to annotate the prototype app in some way. The annotations can be external (like Hibernate/JPA Persistence XML mappings) or internal (like JPA Java annotations, JAX-RS, etc.).

For internal annotations, there seems to be no other way than using the comment capability in the target language, just like protected regions. If the target language doesn't support comments, then the only possible approach is external annotations.

Simple syntax out of my mind:

/** GENERATOR: START
public void run$appName$(String[] args) {
  System.out.println("Welcome to $appTitle$!");
}
*/
public void runHello(String[] args) {
  System.out.println("Welcome to Hello World!");
}
//
GENERATOR: END

Basically we write the offending code twice, one as a template and one in the target language. It seems useless for that example but consider another example:

<?xml version="1.0" encoding="utf-8"?>
<Deployment xmlns="http://schemas.microsoft.com/windowsphone/2009/deployment" AppPlatformVersion="7.1">
<!-- GENERATOR START
  <App xmlns="" ProductID="$productId$" Title="$appTitle$"
       RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"  Author="$appAuthor$"

       Description="$appDescription$" Publisher="$appPublisher$">
-->
  <App xmlns="" ProductID="{6b7a1ae6-8d4e-4f85-b08e-387df81d6e8e}" Title="Info Bandung"
       RuntimeType="Silverlight" Version="1.0.0.0" Genre="apps.normal"  Author="Hendy Irawan"

       Description="Woyyy, Orang Bandung kita bagi2 Info" Publisher="Hendy Irawan">
<!-- GENERATOR END -->
    <IconPath IsRelative="true" IsResource="false">ApplicationIcon.png</IconPath>
    <Capabilities>
      <Capability Name="ID_CAP_NETWORKING"/>
    </Capabilities>
    <Tasks>
      <DefaultTask  Name ="_default" NavigationPage="MainPage.xaml"/>
    </Tasks>
    <Tokens>
<!-- GENERATOR SINGLE
      <PrimaryToken TokenID="$appName$Token" TaskName="_default">
-->
      <PrimaryToken TokenID="InfoBandungToken" TaskName="_default">
        <TemplateType5>
          <BackgroundImageURI IsRelative="true" IsResource="false">Background.png</BackgroundImageURI>
          <Count>0</Count>
<!-- GENERATOR SINGLE
          <Title>$appTitle</Title>
-->
          <Title>Info Bandung</Title>
        </TemplateType5>
      </PrimaryToken>
    </Tokens>
  </App>
</Deployment>

Doesn't look too bad, does it? And I can foresee writing the reverse code generator for that is doable.

And the above code (not the reverse code generator though) isn't entirely imaginary. I have taken it out directly from my open source Windows Phone 7 project Info Bandung.

What do you think?


To learn Modeling with Eclipse Modeling Framework (EMF), I highly recommend the book EMF: Eclipse Modeling Framework.

Tuesday, January 10, 2012

Xperiencing Xtend in a Java EE 6 Web Application

Just saying that to date I'm quite happy with using Xtend in a regular Java EE 6 web application.

It allows me to write this:

    def void reindexSlugs() {
        log.debug("Reindexing slugs")
        slugProvider.clear
        val slugList = userRepo.findAllSlugs.toList
        log.debug("Reindexing slugs for {} users", slugList.size)
        val slugMap = slugList.fold(new HashMap<String, String>(),
            [map, row | map.put(String::valueOf(row.get("slug")), String::valueOf(row.get("id"))); map ]
        )
        slugProvider.batchUpdate(slugMap)
        log.debug("Reindexing slugs")
    }

Instead of: (Xtend-generated Java)

  public void reindexSlugs() {
      this.log.debug("Reindexing slugs");
      this.slugProvider.clear();
      Iterable<Map<String,Object>> _findAllSlugs = this.userRepo.findAllSlugs();
      List<Map<String,Object>> _list = IterableExtensions.<Map<String,Object>>toList(_findAllSlugs);
      final List<Map<String,Object>> slugList = _list;
      int _size = slugList.size();
      this.log.debug("Reindexing slugs for {} users", Integer.valueOf(_size));
      HashMap<String,String> _hashMap = new HashMap<String,String>();
      final Function2<HashMap<String,String>,Map<String,Object>,HashMap<String,String>> _function = new Function2<HashMap<String,String>,Map<String,Object>,HashMap<String,String>>() {
          public HashMap<String,String> apply(final HashMap<String,String> map, final Map<String,Object> row) {
            HashMap<String,String> _xblockexpression = null;
            {
              Object _get = row.get("slug");
              String _valueOf = String.valueOf(_get);
              Object _get_1 = row.get("id");
              String _valueOf_1 = String.valueOf(_get_1);
              map.put(_valueOf, _valueOf_1);
              _xblockexpression = (map);
            }
            return _xblockexpression;
          }
        };
      HashMap<String,String> _fold = IterableExtensions.<Map<String,Object>, HashMap<String,String>>fold(slugList, _hashMap, _function);
      final HashMap<String,String> slugMap = _fold;
      this.slugProvider.batchUpdate(slugMap);
      this.log.debug("Reindexing slugs");
  }

I do miss Scala's syntax though. Xtend is more "Java-compatible" and works as CDI beans.

Closure support and type inference are the two most useful features in languages like Xtend, Scala, and Groovy. :)

To learn Modeling with Eclipse Modeling Framework (EMF), I highly recommend the book EMF: Eclipse Modeling Framework.