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>

Wednesday, June 11, 2008

Spring Dynamic Modules and Hibernate

I'm currently working on a Sourceforge project.
On the server side I want to use Spring DM and Hibernate.
Eclipse RCP and Spring DM builds the client.

I spent the last few evenings trying and searching for a working Spring DM/Hibernate example. I had a lot of classloading issues.
Yesterday I got it to work, you can download the plug-in here.

It's a simple application handling a User object and using HSQL as database.

I added all the dependent jars in the /lib directory. You can create separated plug-ins for them if you want.
Here a list of all the jar files, because thats where I had my problems:
  • cglib-nodep-2.1_3.jar
  • commons-collections.jar
  • commons-dbcp.jar
  • commons-pool.jar
  • dom4j-1.6.1.jar
  • hibernate3.jar
  • hsqldb.jar
  • jta.jar
  • spring-core.jar
  • spring-jdbc.jar
  • spring-orm.jar
  • spring-tx.jar // why is the org.springframework.dao package in this jar?

You can access the service in the Test class:

package com.blogspot.swissdev.springservice;

import org.osgi.framework.BundleContext;

/**
*
* @author Flavio Donze
*/
public class Test {

private BundleContext context = Activator.getDefault().getContext();

public void start() {
System.out.println("starting the test...");

UserService service = (UserService) context.getService(context.getServiceReference(UserService.class.getName()));

User user = (User) context.getService(context.getServiceReference(User.class.getName()));
user.setPassword("pass");
user.setUsername("user");
service.store(user);

for (User u : service.findAll())
{
System.out.println("User: "+u.getId() + ", " + u.getUsername() + ", " + u.getPassword());
}
}
}


And here is my Spring configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="META-INF/spring/database.properties"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>User.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
</props>
</property>
</bean>

<bean id="test" init-method="start" class="com.blogspot.swissdev.springservice.Test"/>

<!-- USER beans, POJO, DAO, Service -->
<bean id="user" class="com.blogspot.swissdev.springservice.UserImpl" scope="prototype">
</bean>

<bean id="userDao" class="com.blogspot.swissdev.springservice.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<bean id="userService" class="com.blogspot.swissdev.springservice.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>

</beans>
Since I just wanted to get Spring DM and Hibernate to work, I didn't really test the rest, just in case you encounter some bugs.

To setup your workspace with Spring DM read the first part of my previous post.

For this example I used:
Eclipse 3.4 RC3
Spring Dynamic Modules for OSGi(tm) 1.0.2

Wednesday, June 4, 2008

Eclipse RCP Application using Spring DM

Last week I was searching the web for some example of Spring DM running on a Eclipse RCP application.
Didn't find one, so I thought I could write a post.

Download:
Eclipse 3.4 RC3
Spring Dynamic Modules for OSGi(tm) 1.0.2

Now create a new Plug-in-Project, using the settings below:
























As a next step you have to import the Spring DM Plug-ins.
File->Import->Plug-in Development->Plug-ins and Fragments.
Point the plug-in location to the extracted "spring-osgi-1.0.2/dist" directory.



On the next page, add the following plug-ins:
  • org.springframework.bundle.osgi.core
  • org.springframework.bundle.osgi.extender
  • org.springframework.bundle.osgi.io
You just imported the Spring DM OSGi part, you also need the actual Spring framework.
Do the same as above, but instead of the "dist" directory select the "spring-osgi-1.0.2/lib" dir and select those plugins:
  • org.springframework.bundle.spring.aop
  • org.springframework.bundle.spring.beans
  • org.springframework.bundle.spring.context
  • org.springframework.bundle.spring.core
  • org.springframework.osgi.aopalliance.osgi
We have to manually create a apache commons logging plugin.
Spring DM has some classloading problems with the existing plugin contained in the eclipse platform.
New->Plug-in Development->Plug-in from existing JAR archives.
Add External...
Now if you have the Spring framework including dependencies on your machine select the commons-logging.jar located at "spring-framework-2.5.x/lib/jakarta-commons", otherwise download the jar here.
Name the plugin "org.apache.commons.logging

Ok, so now your workspace is ready.
Let's create a Spring service.

package swissdev.springdm;

public interface IMyService {

String getSomething();
}


package swissdev.springdm;

public class MyService implements IMyService {

@Override
public String getSomething() {
return "something";
}

public void start() {
System.out.print("start service");
}

public void stop() {
System.out.print("stop service");
}
}

META-INF/spring/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="myService"
class="swissdev.springdm.MyService"
init-method="start"
destroy-method="stop"/>

</beans>


META-INF/spring/applicationContext-osgi.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">

<osgi:service id="myServiceOsgi" ref="myService" interface="swissdev.springdm.IMyService"/>

</beans>


Our application is basically ready to launch, so lets do that.
Right click on the swissdev.springdm plug-in: Run-As -> Eclipse Application.
A simple window containing a view is launched, nothing special.

Open the run configuration: Run -> Run Configurations...
Select your "swissdev.springdm.application" launch config and change to the "Arguments" tab.
Add "-console" to the Program arguments.
Switch to the Plug-ins tab and select all the "Workspace" plug-ins, hit the "Add Required Plug-ins" button and run again.
Type ss in the console, you will get the following output:



So our Spring plug-ins are not active.
Open the run configurations again and switch to the "Configurations" tab, change the Configurations File option to "Use existing config.ini file as a template" and enter ${workspace_loc}/swissdev.springdm/config.ini.
We can get the default generated config.ini in our workspace.
It's located at /.metadata/.plugins/org.eclipse.pde.core/swissdev.springdm.application/config.ini, copy it to your plug-in.
Right click on the ini file and select Open With -> Text Editor.
Ctrl+f Find: extender
You should find ....org.springframework.bundle.osgi.extender add @start at the end:
org.springframework.bundle.osgi.extender@start
Do the same with the swissdev.springdm plugin.

Launch again, you will see a lot of output and somewhere between "start service", thats the output we defined in the MyService class.
Spring DM is working!

To use our service we first need to modify our Activator:

private BundleContext context;

public BundleContext getContext() {
return context;
}

public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
this.context = context;
}



Open the generated View class and edit the createPartControl method:

public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL);
viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setInput(getViewSite());
viewer.addDoubleClickListener(new IDoubleClickListener() {

@Override
public void doubleClick(DoubleClickEvent event) {
ServiceTracker tracker = new ServiceTracker(Activator.getDefault().getContext(), IMyService.class.getName(),null);
tracker.open();
IMyService service = (IMyService) tracker.getService();
System.out.println(service.getSomething());
}
});
}


Launch again and double click one of the itms in the window, in the console there should appear an "something" output.

That was it....