Wednesday, July 8, 2009

Migrate oAW projects to Eclipse Galileo

As we all know, oAW moved into the Eclipse Galileo release.
More information on that here.

The new projects can be found here:
Modeling Workflow (MWE)
Model to Text (M2T)
Textual Modeling Framework (TMF)

In the project I'm working on, we use EMF to generate the model objects and oAW to generate services for the generated objects, which then are published using Eclipse Riena.
After the Galileo release, I had to update my current oAW workflow using the new workflow components and dependencies.

First I downloaded the required bundles for my target platform and IDE, you can get them from the Galileo update site.

Galileo - http://download.eclipse.org/releases/galileo/

> Modeling
  • MWE SDK
  • Xpand SDK
  • Xtext SDK
My generator plug-in now has the following dependencies:

Bundles
  • org.eclipse.emf.mwe.core;bundle-version="0.7.0",
  • org.eclipse.emf.mwe.utils;bundle-version="0.7.0",
  • org.eclipse.xtend;bundle-version="0.7.0",
  • org.eclipse.xpand;bundle-version="0.7.0"
Packages
  • com.ibm.icu.text;version="4.0.1",
  • org.antlr.runtime;version="3.0.0",
  • org.eclipse.jdt.core,
  • org.osgi.framework,
  • org.slf4j;version="1.5.6"
Done that I renamed my workflow file from generator.oaw to generator.mwe.
Now finally the changes I made on the file itself:

old generator.oaw

<?xml version="1.0"?>
<workflow>
<property file="workflow/settings.properties"/>

<!-- properties set through the generator -->
<property name="ecoreFile" value=""/>
<property name="outputLocation" value=""/>

<!-- set up EMF for standalone execution -->
<bean class="org.eclipse.mwe.emf.StandaloneSetup" >
<platformUri value=".."/>
</bean>

<!-- load basic model and store it in slot 'model' -->
<component class="org.eclipse.mwe.emf.Reader">
<uri value="${ecoreFile}" />
<modelSlot value="model" />
</component>


<!-- Service Interfaces -->
<component class="org.openarchitectureware.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.m2t.type.emf.EmfRegistryMetaModel"/>
<expand value="template::Service::interface FOREACH model.eClassifiers" />
<globalVarDef name="productName" value="'${productName}'"/>
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<outlet path="${outputLocation}${serviceInterfaceSrc}" >
<postprocessor class="org.openarchitectureware.xpand2.output.JavaBeautifier" />
</outlet>
</component>

<!-- Service Objects -->
<component class="org.openarchitectureware.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.m2t.type.emf.EmfRegistryMetaModel"/>
<expand value="template::Service::javaClass FOREACH model.eClassifiers" />
<globalVarDef name="productName" value="'${productName}'"/>
<globalVarDef name="serviceInternalPackage" value="'${serviceInternalPackage}'"/>
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<outlet path="${outputLocation}${serviceInternalSrc}">
<postprocessor class="org.openarchitectureware.xpand2.output.JavaBeautifier" />
</outlet>
</component>

<!-- Service Properties File -->
<component class="org.openarchitectureware.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.m2t.type.emf.EmfRegistryMetaModel"/>
<expand value="template::Service::properties FOR model" />
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<globalVarDef name="serviceInternalPackage" value="'${serviceInternalPackage}'"/>
<outlet path="${outputLocation}${serviceInternal}/META-INF/spring/">
<postprocessor class="com.softmodeler.generator.postprocessor.XmlBeautifier" />
</outlet>
</component>

<!-- Test Cases -->
<component class="org.openarchitectureware.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.m2t.type.emf.EmfRegistryMetaModel"/>
<expand value="template::Test::javaClass FOREACH model.eClassifiers" />
<globalVarDef name="productName" value="'${productName}'"/>
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<globalVarDef name="testPackage" value="'${testPackage}'"/>
<outlet path="${outputLocation}${testSrc}">
<postprocessor class="org.openarchitectureware.xpand2.output.JavaBeautifier" />
</outlet>
</component>

<!-- All Tests Suite -->
<component class="org.openarchitectureware.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.m2t.type.emf.EmfRegistryMetaModel"/>
<expand value="template::Test::allTests FOR model" />
<globalVarDef name="testPackage" value="'${testPackage}'"/>
<globalVarDef name="productName" value="'${productName}'"/>
<outlet path="${outputLocation}${testSrc}">
<postprocessor class="org.openarchitectureware.xpand2.output.JavaBeautifier" />
</outlet>
</component>
</workflow>




new generator.mwe

<?xml version="1.0"?>
<workflow>
<property file="workflow/settings.properties"/>

<!-- properties set through the generator -->
<property name="ecoreFile" value=""/>
<property name="outputLocation" value=""/>

<!-- set up EMF for standalone execution -->
<bean class="org.eclipse.emf.mwe.utils.StandaloneSetup" >
<platformUri value=".."/>
</bean>

<!-- load model and store it in slot 'model' -->
<component class="org.eclipse.emf.mwe.utils.Reader" uri="${ecoreFile}">
<modelSlot value="model" />
</component>

<!-- first do some cleanup -->
<component class="org.eclipse.emf.mwe.utils.DirectoryCleaner" directory="${outputLocation}${serviceInterfaceSrc}" />
<component class="org.eclipse.emf.mwe.utils.DirectoryCleaner" directory="${outputLocation}${serviceInternalSrc}" />
<component class="org.eclipse.emf.mwe.utils.DirectoryCleaner" directory="${outputLocation}${serviceInternal}" />
<component class="org.eclipse.emf.mwe.utils.DirectoryCleaner" directory="${outputLocation}${testSrc}" />

<!-- Service Interfaces -->
<component class="org.eclipse.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel"/>
<expand value="template::Service::interface FOREACH model.eClassifiers" />
<globalVarDef name="productName" value="'${productName}'"/>
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<outlet path="${outputLocation}${serviceInterfaceSrc}" >
<postprocessor class="org.eclipse.xpand2.output.JavaBeautifier" />
</outlet>
</component>

<!-- Service Objects -->
<component class="org.eclipse.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel"/>
<expand value="template::Service::javaClass FOREACH model.eClassifiers" />
<globalVarDef name="productName" value="'${productName}'"/>
<globalVarDef name="serviceInternalPackage" value="'${serviceInternalPackage}'"/>
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<outlet path="${outputLocation}${serviceInternalSrc}">
<postprocessor class="org.eclipse.xpand2.output.JavaBeautifier" />
</outlet>
</component>

<!-- Service Properties File -->
<component class="org.eclipse.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel"/>
<expand value="template::Service::properties FOR model" />
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<globalVarDef name="serviceInternalPackage" value="'${serviceInternalPackage}'"/>
<outlet path="${outputLocation}${serviceInternal}/META-INF/spring/">
<postprocessor class="com.softmodeler.generator.postprocessor.XmlBeautifier" />
</outlet>
</component>

<!-- Test Cases -->
<component class="org.eclipse.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel"/>
<expand value="template::Test::javaClass FOREACH model.eClassifiers" />
<globalVarDef name="productName" value="'${productName}'"/>
<globalVarDef name="serviceInterfacePackage" value="'${serviceInterfacePackage}'"/>
<globalVarDef name="testPackage" value="'${testPackage}'"/>
<outlet path="${outputLocation}${testSrc}">
<postprocessor class="org.eclipse.xpand2.output.JavaBeautifier" />
</outlet>
</component>

<!-- All Tests Suite -->
<component class="org.eclipse.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel"/>
<expand value="template::Test::allTests FOR model" />
<globalVarDef name="testPackage" value="'${testPackage}'"/>
<globalVarDef name="productName" value="'${productName}'"/>
<outlet path="${outputLocation}${testSrc}">
<postprocessor class="org.eclipse.xpand2.output.JavaBeautifier" />
</outlet>
</component>
</workflow>



Basically only the namespace of the component classes changed.

Ending the whole story I have to say that I launch the workflow in my application using the org.eclipse.emf.mwe.core.WorkflowRunner.run(...) which works fine.
If I launch the workflow by itself "Run As -> MWE Workflow" I get an strange
"java.lang.ClassNotFoundException: org.eclipse.jface.text.BadLocationException" Exception, strange because I don't get why there should be an dependency on jface.

Friday, May 22, 2009

Jackrabbit/JCR Versioning FAQ

There is not a lot of documentation about Jackrabbit/JCR versioning around. So I thought I could publish a little FAQ about JCR versioning.


How do I make a Node versionable?
node.addMixin(JcrConstants.MIX_VERSIONABLE);

How to get the current Version of a Node?
node.getBaseVersion();

How do I increment the minor version of a Node?
node.checkout();
node.setProperty("title", new StringValue("test"));
getSession().save();
node.checkin();


How do I increment the major version of a Node?
You can't, simple as that.
http://osdir.com/ml/apache.jackrabbit.devel/2005-10/msg00417.html

How to restore a node?
node.checkout();
node.setProperty("title", new StringValue("test 2"));
getSession().save();
Version lastVersion = node.checkin();

node.checkout();
node.setProperty("title", new StringValue("test 3"));
getSession().save();
node.checkin();

System.out.println("CURRENT: " + node.getProperty("title").getValue().getString());
node.restore(lastVersion, true);
System.out.println("RESTORED: " + node.getProperty("title").getValue().getString());

The output will be:
CURRENT: test 3
RESTORED: test 2

How do I iterate over all versions of a Node?
VersionIterator i = node.getVersionHistory().getAllVersions();
i.skip(1); // important, otherwise the currentNode will fail to read the 'title' property
while (i.hasNext()) {
Version v = i.nextVersion();
NodeIterator nodeIterator = v.getNodes();
while (nodeIterator.hasNext()) {
Node currentNode = nodeIterator.nextNode();
System.out.println("Version: " + v.getName());
System.out.println(currentNode.getProperty("title").getValue().getString());
}
}

Do I have to add JcrConstants.MIX_VERSIONABLE to my subnodes?
No, changed subnodes are correctly stored under the version of the versionable Node.

Node node = root.addNode(name);
node.addMixin(JcrConstants.MIX_VERSIONABLE);
node.setProperty("title", new StringValue("test 1"));
Node subnode = node.addNode("subnode");
subnode.setProperty("descr", "this is a description");
getSession().save();
node.checkin();

node.checkout();
subnode = node.getNode("subnode");
subnode.setProperty("descr", "this is a NEW description");

node.setProperty("title", new StringValue("test 2"));
getSession().save();
node.checkin();

VersionIterator i = node.getVersionHistory().getAllVersions();
i.skip(1);
while (i.hasNext()) {
Version v = i.nextVersion();
NodeIterator nodeIterator = v.getNodes();
while (nodeIterator.hasNext()) {
Node currentNode = nodeIterator.nextNode();
System.out.println("Version: " + v.getName());
System.out.println(currentNode.getProperty("title").getValue().getString());
if (currentNode.hasNodes()) {
for (NodeIterator childs = currentNode.getNodes(); childs.hasNext();) {
Node child = childs.nextNode();
System.out.println("Description: " + child.getProperty("descr").getValue().getString());
}
}
}
}


OUTPUT:
Version: 1.0
test 1
Description: this is a description
Version: 1.1
test 2
Description: this is a NEW description

How can I export a Node and all it's Versions?
That's not possible.

If I delete a Node will it's history be deleted as well?
No you have to iterate over the versions and call VersionHistory.removeVersion(String versionName);

Usefull links:
http://www.onjava.com/pub/a/onjava/2006/10/04/what-is-java-content-repository.html?page=1
http://jcp.org/en/jsr/detail?id=170

Wednesday, May 13, 2009

jBPM Deploy Problem "no start activity in process"

I had a jBPM workflow engine running in my OSGi server environment.

Then I updated from 4.0.0 Beta1 to 4.0.0 Beta2. Well there are quite a few changes. The "exclusive" tag turned into "decision" and yeah subprocesses are supported now!
Check the readme.html in your jBPM directory for more details.

Anyway why I write this blog entry is this little bastard. After the update I kept getting the following error message: error: no start activity in process

After a little debugging I found the source of the problem. In your *.jpdl.xml file change
<process name="..." xmlns="http://jbpm.org/4/jpdl">
to
<process name="..." xmlns="http://jbpm.org/4.0/jpdl">

Problem solved.

Monday, March 16, 2009

Use EMF and Riena

EMF, Teneo and Riena are great Eclipse projects. Use EMF to model your business objects and generate the Java code, use Teneo to persist through Hibernate and Riena to transfer your objects to the Eclipse RCP client and back. Isn't that sexy!

Nevertheless As I tried to use that setup, I ran into a few problems.
  • Transferring a 0...* reference causes an Exception: java.util.ArrayList ([...]) cannot be assigned to org.eclipse.emf.common.util.EList
  • Registered Adapters (EObjectImpl:eAdapters) can not be passed over the wire and causes Exception
  • In some cases I had trouble transferring the EObjectImpl:eProperties attribute
The way I figured to solve those issues, is the AbstractSerializerFactory.

Through extension points I can register my own EObjectSerializerFactory which uses my Deserialiser/Serializer for all EObjects.
My classes need to be in a "common" plug-in which is active on the server and client.
Both the EObjectSerializer and EObjectDeserializer are based on the JavaSerializer/JavaDeserializer provided by Caucho.

Basiclly all I do, is serializing the content of the EStructuralFeatures instead of serializing the Java fields.
So this way eFlags, eAdapters, eContainer, eContainerFeatureID and eProperties (EObjectImpl fields) are not passed over the wire.
I my case this works since I don't need the eContainer on the server.

Here the classes, maybe they help you to use the same combination:
EObjectSerializerFactory.java


/*******************************************************************************
* $URL: $
*
* Copyright (c) 2007 henzler informatik gmbh, CH-4106 Therwil
*******************************************************************************/
package com.softmodeler.service.communication;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;

import com.caucho.hessian.io.AbstractSerializerFactory;
import com.caucho.hessian.io.Deserializer;
import com.caucho.hessian.io.HessianProtocolException;
import com.caucho.hessian.io.Serializer;

/**
* EObjectSerializerFactory, provides a serializer and deserializer for EObjects
*
* @see EObjectSerializer
* @see EObjectDeserializer
* @author created by Author: fdo, last update by $Author: fdo $
* @version $Revision: 1236 $, $Date: 2009-03-03 22:51:32 +0100 (Di, 03 Mrz 2009) $
*/
public class EObjectSerializerFactory extends AbstractSerializerFactory {
/** class name suffix for implemented EClassifiers */
private static final String CLASS_SUFFIX = "Impl"; //$NON-NLS-1$
/** packages that should be excluded from reading */
private static final String[] EXCLUDE_PACKAGE_NS_URI = new String[] { "http://www.eclipse.org/emf/2002/Ecore" }; //$NON-NLS-1$

/** internal cache of all EClassifiers existing in the system, ClassifierName=>EClassifier */
private Map allClassifiers = null;

/**
* Returns a map with all relevant EClassifiers
*
* @return map ClassifierName=>EClassifier
*/
private Map getAllClassifiers() {
if (allClassifiers == null) {
allClassifiers = new HashMap();

// iterate the EPackages and place all classifiers in the allClassifiers map
for (Object value : EPackage.Registry.INSTANCE.values()) {
if (value instanceof EPackage) {
EPackage ePackage = (EPackage) value;
if (isValidPackage(ePackage.getNsURI())) {
for (EClassifier eClassifier : ePackage.getEClassifiers()) {
allClassifiers.put(eClassifier.getName(), eClassifier);
}
}
}
}
}
return allClassifiers;
}

/**
* Returns true if the package is valid (not in the EXCLUDE_PACKAGE_NS_URI list)
*
* @param nsURI the package NameSpace URI
* @return true if valid
*/
private boolean isValidPackage(String nsURI) {
for (String excludePackage : EXCLUDE_PACKAGE_NS_URI) {
if (excludePackage.equals(nsURI)) {
return false;
}
}
return true;
}

/**
* Returns the EClassifier for the passed Class
*
* @param cl
* @return returns null if the class is not an EObject or not found in the EPackages
*/
@SuppressWarnings("unchecked")
private EClass getClassifier(Class cl) {
if (EObject.class.isAssignableFrom(cl)) {
String name = cl.getSimpleName();
if (name.endsWith(CLASS_SUFFIX)) {
EClassifier classifier = getAllClassifiers().get(name.substring(0, name.indexOf(CLASS_SUFFIX)));
if (classifier instanceof EClass) {
return (EClass) classifier;
}
}
}
return null;
}

@SuppressWarnings("unchecked")
@Override
public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
EClass classifier = getClassifier(cl);
if (classifier != null) {
return new EObjectDeserializer(classifier);
}
return null;
}

@SuppressWarnings("unchecked")
@Override
public Serializer getSerializer(Class cl) throws HessianProtocolException {
EClass classifier = getClassifier(cl);
if (classifier != null) {
return new EObjectSerializer(classifier);
}
return null;
}
}




EObjectSerializer.java


/*******************************************************************************
* $URL: $
*
* Copyright (c) 2007 henzler informatik gmbh, CH-4106 Therwil
*******************************************************************************/
package com.softmodeler.service.communication;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.impl.EObjectImpl;

import com.caucho.hessian.io.AbstractHessianOutput;
import com.caucho.hessian.io.AbstractSerializer;
import com.caucho.hessian.io.JavaSerializer;

/**
* Serializes EObjects, this class is based on the {@link JavaSerializer} instead of working with java fields,
* {@link EStructuralFeature} are used to serialize the 'data' values of the {@link EObject}.

* Java fields of the {@link EObjectImpl}; eAdapters, eFlags, eContainer, eContainerFeatureID and eProperties are
* ignored
*
* @see JavaSerializer
* @author created by Author: fdo, last update by $Author: fdo $
* @version $Revision: 1151 $, $Date: 2009-02-25 22:47:19 +0100 (Mi, 25 Feb 2009) $
*/
public class EObjectSerializer extends AbstractSerializer {
private List fieldSerializers;
private List fields;

/**
* constructor
*
* @param classifier
*/
@SuppressWarnings("unchecked")
public EObjectSerializer(EClass classifier) {
fields = classifier.getEAllStructuralFeatures();
fieldSerializers = new ArrayList();

for (EStructuralFeature feature : fields) {
if (!feature.isMany()) {
EClassifier eDataType = feature.getEType();
Class type = eDataType.getInstanceClass();
fieldSerializers.add(getFieldSerializer(type));
} else {
fieldSerializers.add(ListFieldSerializer.SER);
}
}
}

@SuppressWarnings("unchecked")
@Override
public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
if (out.addRef(obj)) {
return;
}

Class cl = obj.getClass();

int ref = out.writeObjectBegin(cl.getName());

if (ref < -1) { writeObject10((EObject) obj, out); } else { if (ref == -1) { writeDefinition20(out); out.writeObjectBegin(cl.getName()); } writeInstance((EObject) obj, out); } } private void writeObject10(EObject obj, AbstractHessianOutput out) throws IOException { for (int i = 0; i < feature =" fields.get(i);" i =" 0;" feature =" fields.get(i);" ser =" new" value =" null;" value =" obj.eGet(feature);" ser =" new" value =" false;" value =" (Boolean)" ser =" new" value =" 0;" value =" (Integer)" ser =" new" value =" 0;" value =" (Long)" ser =" new" value =" 0;" value =" (Double)" ser =" new" value =" null;" value =" (String)" ser =" new" value =" null;" objvalue =" (List)" value =" new">



EObjectDeserializer.java


/*******************************************************************************
* $URL: $
*
* Copyright (c) 2007 henzler informatik gmbh, CH-4106 Therwil
*******************************************************************************/
package com.softmodeler.service.communication;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;

import com.caucho.hessian.io.AbstractHessianInput;
import com.caucho.hessian.io.AbstractMapDeserializer;
import com.caucho.hessian.io.HessianFieldException;
import com.caucho.hessian.io.IOExceptionWrapper;
import com.caucho.hessian.io.JavaDeserializer;

/**
* Deserializes EObjects, this class is based on the {@link JavaDeserializer} instead of working with java fields,
* {@link EStructuralFeature} are used to deserialize the 'data' values of the {@link EObject}.

* Java fields of the {@link EObjectImpl}; eAdapters, eFlags, eContainer, eContainerFeatureID and eProperties are
* ignored
*
* @see JavaDeserializer
* @author created by Author: fdo, last update by $Author: fdo $
* @version $Revision: 1236 $, $Date: 2009-03-03 22:51:32 +0100 (Di, 03 Mrz 2009) $
*/
public class EObjectDeserializer extends AbstractMapDeserializer {
private Map fieldMap;
private EClass classifier;

/**
* constructor
*
* @param classifier
*/
public EObjectDeserializer(EClass classifier) {
this.classifier = classifier;
fieldMap = getFieldMap(classifier);
}

@SuppressWarnings("unchecked")
@Override
public Class getType() {
return classifier.getInstanceClass();
}

@Override
public Object readMap(AbstractHessianInput in) throws IOException {
try {
EObject obj = instantiate();

return readMap(in, obj);
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(classifier.getName() + ":" + e.getMessage(), e); //$NON-NLS-1$
}
}

@Override
public Object readObject(AbstractHessianInput in, String[] fieldNames) throws IOException {
try {
Object obj = instantiate();
return readObject(in, (EObject) obj, fieldNames);
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(classifier.getName() + ":" + e.getMessage(), e); //$NON-NLS-1$
}
}

public Object readMap(AbstractHessianInput in, EObject obj) throws IOException {
try {
int ref = in.addRef(obj);

while (!in.isEnd()) {
Object key = in.readObject();

FieldDeserializer deser = fieldMap.get(key);

if (deser != null) {
deser.deserialize(in, obj);
} else {
in.readObject();
}
}

in.readMapEnd();

in.setRef(ref, obj);
return obj;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
}

public Object readObject(AbstractHessianInput in, EObject obj, String[] fieldNames) throws IOException {
try {
int ref = in.addRef(obj);

for (String name : fieldNames) {
FieldDeserializer deser = fieldMap.get(name);

if (deser != null) {
deser.deserialize(in, obj);
} else {
in.readObject();
}
}

in.setRef(ref, obj);
return obj;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(obj.getClass().getName() + ":" + e, e); //$NON-NLS-1$
}
}

/**
* create an instance of the passed classifier
*
* @return
* @throws Exception
*/
protected EObject instantiate() throws Exception {
return EcoreUtil.create(classifier);
}

/**
* Creates a map featureName=>FieldDeserializer.
*/
@SuppressWarnings("unchecked")
protected Map getFieldMap(EClass classifier) {
Map fieldMap = new HashMap();

for (EStructuralFeature feature : classifier.getEAllStructuralFeatures()) {
if (feature.isTransient() || fieldMap.containsKey(feature.getName())) {
continue;
}
FieldDeserializer deser;

EClassifier eDataType = feature.getEType();
Class type = eDataType.getInstanceClass();

if (String.class.equals(type)) {
deser = new StringFieldDeserializer(feature);
} else if (byte.class.equals(type)) {
deser = new ByteFieldDeserializer(feature);
} else if (short.class.equals(type)) {
deser = new ShortFieldDeserializer(feature);
} else if (int.class.equals(type)) {
deser = new IntFieldDeserializer(feature);
} else if (long.class.equals(type)) {
deser = new LongFieldDeserializer(feature);
} else if (float.class.equals(type)) {
deser = new FloatFieldDeserializer(feature);
} else if (double.class.equals(type)) {
deser = new DoubleFieldDeserializer(feature);
} else if (boolean.class.equals(type)) {
deser = new BooleanFieldDeserializer(feature);
} else if (feature.isMany()) {
deser = new EListDeserializer(feature);
} else {
deser = new ObjectFieldDeserializer(feature);
}

fieldMap.put(feature.getName(), deser);
}
return fieldMap;
}

abstract static class FieldDeserializer {
protected EStructuralFeature feature;

public FieldDeserializer(EStructuralFeature feature) {
this.feature = feature;
}

abstract void deserialize(AbstractHessianInput in, EObject obj) throws IOException;

}

static class EListDeserializer extends FieldDeserializer {

EListDeserializer(EStructuralFeature feature) {
super(feature);
}

@SuppressWarnings("unchecked")
@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
List value = null;

try {
value = (List) in.readObject(List.class);
if (value.size() > 0) {
obj.eSet(feature, value);
}
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class ObjectFieldDeserializer extends FieldDeserializer {

ObjectFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
Object value = null;

try {
value = in.readObject(feature.getEType().getInstanceClass());

if (value != null) {
obj.eSet(feature, value);
}
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class BooleanFieldDeserializer extends FieldDeserializer {
BooleanFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
boolean value = false;

try {
value = in.readBoolean();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class ByteFieldDeserializer extends FieldDeserializer {
ByteFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
int value = 0;

try {
value = in.readInt();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class ShortFieldDeserializer extends FieldDeserializer {
ShortFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
int value = 0;

try {
value = in.readInt();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class IntFieldDeserializer extends FieldDeserializer {
IntFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
int value = 0;

try {
value = in.readInt();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class LongFieldDeserializer extends FieldDeserializer {
LongFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
long value = 0;

try {
value = in.readLong();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class FloatFieldDeserializer extends FieldDeserializer {
FloatFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
double value = 0;

try {
value = in.readDouble();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class DoubleFieldDeserializer extends FieldDeserializer {
DoubleFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
double value = 0;

try {
value = in.readDouble();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static class StringFieldDeserializer extends FieldDeserializer {
StringFieldDeserializer(EStructuralFeature feature) {
super(feature);
}

@Override
void deserialize(AbstractHessianInput in, EObject obj) throws IOException {
String value = null;

try {
value = in.readString();

obj.eSet(feature, value);
} catch (Exception e) {
logDeserializeError(feature, obj, value, e);
}
}
}

static void logDeserializeError(EStructuralFeature feature, Object obj, Object value, Throwable e)
throws IOException {
String fieldName = (feature.getContainerClass().getName() + "." + feature.getName()); //$NON-NLS-1$

if (e instanceof HessianFieldException) {
throw (HessianFieldException) e;
} else if (e instanceof IOException) {
throw new HessianFieldException(fieldName + ": " + e.getMessage(), e); //$NON-NLS-1$
}

if (value != null) {
throw new HessianFieldException(fieldName + ": " + value.getClass().getName() + " (" + value + ")" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ " cannot be assigned to " + feature.getEType().getName()); //$NON-NLS-1$
} else {
throw new HessianFieldException(fieldName + ": " + feature.getEType().getName() //$NON-NLS-1$
+ " cannot be assigned from null", e); //$NON-NLS-1$
}
}
}

Tuesday, December 2, 2008

Generic Hibernate DAO

I intended to create a post about Hibernate subselects, but I decided to post the whole Generic Hibernate DAO.

A short briefing of the surrounding system.
I have different child classes of "BasicObject" they all got an according "ObjectRef" which is a small object (id,name,locale) used as reference on the larger BasicObject (like in a tree or document content). The reference object and the BasicObject have the same id (UUID).
The whole software is a server/client application using Eclipse EMF to model, generate and handle the business objects, Eclipse Riena for remoting and Hibernate/Teneo for persistence. Ohh and for sure Eclipse RCP as client. Isn't the Eclipse movement great :-).

To the subselects, I used them in the search(...) method.
The search() restricts the select on BasicObject fields and returns a list of ObjectRefs. This saves us a lot of bandwidth since BasicObjects can have n fields of any size and the ObjectRef only has three.
The first query (DetachedCriteria) which is restricted based on the BasicObject fields (i.e. name LIKE 'test' AND description LIKE 'Start%') is used to restrict the actual query on ObjectRef using the result (ids) of the DetachedCriteria query.

As for the rest of the class, it's a Generic Hibernate Data Access Object even though the name ends in "Service". I just combined the Service and DAO, since in our case, it doesn't make sense to separate them.
Maybe the create() method is a bit confusing, that's because we use the Eclipse Modeling Framework to create the business objects.

BasicObjectService.java


/*******************************************************************************
* $RCSfile: $ Copyright (c) 2007 henzler informatik gmbh, CH-4106 Therwil
*******************************************************************************/
package com.softmodeler.service.impl;

import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;

import com.softmodeler.model.BasicObject;
import com.softmodeler.model.ObjectRef;
import com.softmodeler.service.IBasicObjectService;
import com.softmodeler.service.ModelUtil;

/**
* The BasicObjectService implements the default handling for all BasicObject sub services
*
* @author created by Author: fdo, last update by $Author: $
* @version $Revision: $, $Date: $
*/
public abstract class BasicObjectService implements
IBasicObjectService {
/** The Hibernate Session */
protected Session session;
/** The Entity Manager */
protected EntityManager entityManager;

/**
* The constructor
*
* @param session
* @param entityManager
*/
public BasicObjectService(Session session, EntityManager entityManager) {
this.session = session;
this.entityManager = entityManager;
}

/**
* Returns the class of the BasicObject
*
* @return
*/
@SuppressWarnings("unchecked")
protected Class getObjectClass() {
return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

}

/**
* Returns the class of the ObjectRef
*
* @return
*/
@SuppressWarnings("unchecked")
protected Class getRefClass() {
return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}

/**
* Begins a Transaction
*
* @return
*/
protected EntityTransaction beginTransaction() {
EntityTransaction transaction = entityManager.getTransaction();
if (!transaction.isActive()) {
transaction.begin();
}
return transaction;
}

@SuppressWarnings("unchecked")
@Override
public K create() {
// create the objects using the according EPackage
EPackage modelPackage = ModelUtil.getModelPackage();
EPackage refPackage = ModelUtil.getRefPackage();
EClass objectEClass = (EClass) modelPackage.getEClassifier(getObjectClass().getSimpleName());
EClass refEClass = (EClass) refPackage.getEClassifier(getRefClass().getSimpleName());
K object = (K) modelPackage.getEFactoryInstance().create(objectEClass);
V ref = (V) refPackage.getEFactoryInstance().create(refEClass);

// set the ids
object.setId(EcoreUtil.generateUUID());
ref.setId(object.getId());

// store objects in the database
EntityTransaction transaction = beginTransaction();
session.save(object);
session.save(ref);
transaction.commit();
return object;
}

@SuppressWarnings("unchecked")
@Override
public void delete(K object) {
EntityTransaction transaction = beginTransaction();

// load first, to prevent delete problems
Criteria criteria = session.createCriteria(getObjectClass());
criteria.add(Restrictions.eq(ID, object.getId()));
K loadedObject = (K) criteria.uniqueResult();

Criteria criteriaRef = session.createCriteria(getRefClass());
criteriaRef.add(Restrictions.eq(ID, object.getId()));
V loadedRef = (V) criteria.uniqueResult();

session.delete(loadedObject);
session.delete(loadedRef);
transaction.commit();
}

@SuppressWarnings("unchecked")
@Override
public List findAllRefs() {
Criteria critica = session.createCriteria(getRefClass());
return critica.list();
}

@SuppressWarnings("unchecked")
@Override
public K findById(String uuid) {
Criteria criteria = session.createCriteria(getObjectClass());
criteria.add(Restrictions.eq(ID, uuid));
return (K) criteria.uniqueResult();
}

@SuppressWarnings("unchecked")
@Override
public K findByRef(V ref) {
Criteria criteria = session.createCriteria(getObjectClass());
criteria.add(Restrictions.eq(ID, ref.getId()));
return (K) criteria.uniqueResult();
}

@SuppressWarnings("unchecked")
@Override
public V getRef(K object) {
Criteria criteria = session.createCriteria(getRefClass());
criteria.add(Restrictions.eq(ID, object.getId()));
return (V) criteria.uniqueResult();
}

@Override
public void store(K object) {
EntityTransaction transaction = beginTransaction();
entityManager.merge(object);
transaction.commit();
}

@Override
public void storeRef(ObjectRef ref) {
EntityTransaction transaction = beginTransaction();
entityManager.merge(ref);
transaction.commit();
}


@SuppressWarnings("unchecked")
@Override
public List search(Map fieldValues) {
DetachedCriteria objCritica = DetachedCriteria.forEntityName(getObjectClass().getSimpleName(), "obj");
for (String field : fieldValues.keySet()) {
objCritica.add(Restrictions.like(field, fieldValues.get(field)));
}
objCritica.setProjection(Property.forName(ID));
Criteria criteria = session.createCriteria(getRefClass());
criteria.add(Property.forName(ID).in(objCritica));
return criteria.list();
}
}



Here the according interface:
IBasicObjectService.java


/*******************************************************************************
* $RCSfile: $ Copyright (c) 2007 henzler informatik gmbh, CH-4106 Therwil
*******************************************************************************/
package com.softmodeler.service;

import java.util.List;
import java.util.Map;

import com.softmodeler.model.BasicObject;
import com.softmodeler.model.ObjectRef;

/**
* @author created by Author: fdo, last update by $Author: $
* @version $Revision: $, $Date: $
*/
public interface IBasicObjectService {
/** Id field */
public static final String ID = "id";

/**
* Creates, stores and returns an object
*
* @return
*/
K create();

/**
* Creates, stores and returns an object reference
*
* @return
*/
V getRef(K object);

/**
* Deletes an object
*
* @param object
*/
void delete(K object);

/**
* Stores an object
*
* @param object
*/
void store(K object);

/**
* Stores a reference object
*
* @param ref
*/
void storeRef(V ref);

/**
* Finds an object, by it's uuid
*
* @param AutoJobRef
* @return
*/
K findById(String uuid);

/**
* Finds an object, using it's reference object
*
* @param ref
* @return
*/
K findByRef(V ref);

/**
* Returns a list containing all ref objects
*
* @return
*/
List findAllRefs();

/**
* Searches ObjectRefs using the passed map to create the query
*
* @param fieldValues field name => value
* @return
*/
List search(Map fieldValues);
}

Monday, December 1, 2008

Eclipse Nebula

I was searching for a better SWT DateTime widget, since the standard widget isn't really "done" yet.
Durring my search I stumbled over the Eclipse Nebula project, which provides a few real nice widgets that extend the SWT widget collection.

Currently the widgets are in Alpha and Beta status. Right now I'm using the DateChooserCombo in a CellEditor, I found and reported a Bug and hope the widgets will be stabilzed soon.

http://www.eclipse.org/nebula/

Thursday, August 21, 2008

oaw Output Postprocessor (Beautifier)

I'm working on a project using oaw to generate java classes, spring definitions, ecore models and a lot more.

There is a pretty nice JavaBeautifier to clean up my generated classes. The problem was the XmlBeautifier, it didn't remove white spaces. This resulted in some pretty ugly xml files.

So I searched the web for some alternative, didn't find one so I modified the original for my own needs.
Here is the code if someone approaches the same problem.



/*
*
*
* Copyright (c) 2005-2006 Sven Efftinge (http://www.efftinge.de) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sven Efftinge (http://www.efftinge.de) - Initial API and implementation
*
*

*/
package com.softmodeler.generator.postprocessor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openarchitectureware.util.EncodingDetector;
import org.openarchitectureware.xpand2.output.FileHandle;
import org.openarchitectureware.xpand2.output.PostProcessor;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
* *
*
* @author Sven Efftinge (http://www.efftinge.de)
* @author Bernd Kolb
*/
public class XmlBeautifier implements PostProcessor {

private final Log log = LogFactory.getLog(getClass());

private String[] fileExtensions = new String[] { ".xml", ".xsl", ".xsd", ".wsdd", ".wsdl", ".ecore" };

public void setFileExtensions(final String[] fileExtensions) {
this.fileExtensions = fileExtensions;
}

public void beforeWriteAndClose(final FileHandle info) {
if (isXmlFile(info.getTargetFile().getAbsolutePath())) {
try {
// TODO this is only a heuristic, but it should work for most cases. This really is the beginning of reimplementing
// the XML parser just because we do RAM rather then file based beautification...
final String bufferedString = info.getBuffer().toString().trim();
final int indEncoding = bufferedString.indexOf("encoding");
final int indEndHeader = bufferedString.indexOf("?>");
String readEncoding = null;
Document doc = null;
if (bufferedString.startsWith(" 0 && indEncoding < readencoding =" info.getFileEncoding();" doc =" parseDocument(bufferedString," doc =" parseDocument(bufferedString," tfactory =" TransformerFactory.newInstance();" threadid="562510&tstart="90" showtopic="788" serializer =" tfactory.newTransformer();" systemvalue =" doc.getDoctype().getSystemId();" publicid =" doc.getDoctype().getPublicId();" bytearrayoutputstream =" new" string =" byteArrayOutputStream.toString(info.getFileEncoding());" nodelist =" node.getChildNodes();" i =" 0;" child =" nodeList.item(i);"> encodingsToTry = new ArrayList();
if (encoding != null) {
encodingsToTry.add(encoding);
} else {
byte[] sampleBytes = bufferedString.substring(0, Math.min(64, bufferedString.length())).getBytes();
encodingsToTry.add(EncodingDetector.detectEncoding(sampleBytes).displayName());
encodingsToTry.add("ISO-8859-1");
encodingsToTry.add("UTF-8");
encodingsToTry.add("MacRoman");
encodingsToTry.add("UTF-16");
encodingsToTry.add("UTF-16BE");
encodingsToTry.add("UTF-16LE");
}
encodingsToTry.add(System.getProperty("file.encoding"));

Document doc = null;
Exception lastException = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(false);
factory.setValidating(false);

DocumentBuilder builder = factory.newDocumentBuilder();

builder.setEntityResolver(new EntityResolver() {
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return new InputSource(new StringReader(""));
}
});
builder.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException exception) throws SAXException {
log.warn(exception.getMessage());
}

public void fatalError(SAXParseException exception) throws SAXException {
if (exception.getMessage() != null && exception.getMessage().startsWith("Invalid byte")) {
// ignore, since we try other encodings
} else {
log.warn(exception.getMessage());
}
}

public void warning(SAXParseException exception) throws SAXException {
log.debug(exception.getMessage());
}
});

for (Iterator it = encodingsToTry.iterator(); it.hasNext();) {
String enc = it.next();
try {
doc = builder.parse(new ByteArrayInputStream(bufferedString.getBytes(enc)));
// if no error exit here
break;
} catch (Exception e) {
lastException = e;
}
}
if (doc == null && lastException != null) {
throw lastException;
} else {
return doc;
}
}

public boolean isXmlFile(final String absolutePath) {
for (int i = 0; i <>



The only difference to the original Beautifier is the removeWhiteSpaces method and it's call.

If you want to use this class or make your own for any other job, place the class in your generator plug-in and add the following config in your oaw workflow file:

<component class="org.openarchitectureware.xpand2.Generator">
<metaModel id="mm" class="org.eclipse.m2t.type.emf.EmfRegistryMetaModel"/>
<expand value="template::Nls::modelEcoreFile FOR model" />
<outlet path="${model-ecore}">
<postprocessor class="com.softmodeler.generator.postprocessor.XmlBeautifier"/>
</outlet>
</component>