First ... sorry for my english .. it could be better ...
I found the Auditable example on the hibernate web site .
i want to use the same principle but without any change in my code .
I found a way to dynamicly mark an hibernate pojo as auditable
Here is the example :
I store the auditable classes names in a list , and pass it to a custom classloader .
This classloader use javaassist to dynamicly extends the Auditable class .
Here is the source .
Code:
/*
* Créé le 25 sept. 2006
*
*/
package fr.generali.gb.commun.hibernate.interceptor;
import fr.generali.gb.commun.log.LogManager;
import org.apache.commons.logging.Log;
import java.util.List;
import javassist.ClassPool;
import javassist.CtClass;
/**
*
* Ce class loader permet de modifier dynamiquement les pojos hibernate selectionnés
*
* afin de leur ajouter des attributs d'audit .
*
* @author ponthiaux.eric@gmail.com
*
*/
public class AuditableClassLoader extends ClassLoader {
private List list;
/** Generali vie framework logger */
private Log classLogger = LogManager.instance ().getLog (this.getClass ());
/**
* Creates a new AuditableClassLoader object.
*
* @param parent parent class loader
* @param auditables list de classe à auditer
*
*/
public AuditableClassLoader (ClassLoader parent, List auditables) {
super(parent);
this.list = auditables;
}
public Class loadClass (String name) throws ClassNotFoundException {
if (list.contains (name)) {
makeAuditable (name);
}
return super.loadClass (name);
}
public void makeAuditable (String name) {
try {
ClassPool pool = ClassPool.getDefault ();
CtClass cl = ClassPool.getDefault ().get (name);
if(cl.getSuperclass().getName().equals(Auditable.class.getName())) {
classLogger.debug("This class already extends auditable !");
return ;
}
classLogger.debug ("*********************************************************************************************************");
classLogger.debug ("DomainObject superclass : " + name);
classLogger.debug ("Rewriting : " + name);
cl.setSuperclass (ClassPool.getDefault ().get (Auditable.class.getName ()));
cl.toClass (getParent ());
classLogger.debug ("Rewriting : " + name + " done ...... ");
classLogger.debug ("*********************************************************************************************************");
list.remove (name);
} catch (Exception ex) {
ex.printStackTrace ();
}
}
protected Class findClass (String name) throws ClassNotFoundException {
if (list.contains (name)) {
makeAuditable (name);
}
return super.findClass (name);
}
protected synchronized Class loadClass (String name, boolean resolve) throws ClassNotFoundException {
if (list.contains (name)) {
makeAuditable (name);
}
return super.loadClass (name, resolve);
}
}
I use spring to configure my hibernate session factory .
So i made a custom local session factory to set the classloader
and add the missing auditable property at runtime .
Code:
/*
* Créé le 25 sept. 2006
*
*/
package fr.generali.gb.commun.hibernate.factory;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Value;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import fr.generali.gb.commun.hibernate.interceptor.AuditableClassLoader;
import fr.generali.gb.commun.log.LogManager;
/**
* @author ponthiaux.eric@gmail.com
*
*/
public class AuditableLocalSessionFactoryBean extends LocalSessionFactoryBean {
private Log classLogger = LogManager.instance().getLog(this.getClass());
private String CREATED_COLUMN_NAME = "CREATED";
private String LAST_UPDATED_COLUMN_NAME = " LAST_UPDATED";
private String CREATED_BY_COLUMN_NAME = "CREATED_BY";
private String UPDATED_BY_COLUMN_NAME = "UPDATED_BY";
private List auditables;
protected SessionFactory newSessionFactory(Configuration configuration) throws HibernateException {
Iterator iter = configuration.getClassMappings();
while (iter.hasNext()) {
RootClass cl = (RootClass) iter.next();
if (auditables.contains(cl.getClassName())) {
classLogger.info(cl.getClassName() + " marked as auditable !");
addPropertyCreated(cl);
addPropertyCreatedBy(cl);
addPropertyLastUpdated(cl);
addPropertyUpdatedBy(cl);
}
}
SessionFactory factory = super.newSessionFactory(configuration);
return factory;
}
private void addPropertyCreated(RootClass cl) {
classLogger.info("Adding property created for " + cl.getClassName());
Property created = new Property();
created.setInsertable(true);
created.setName("created");
created.setPersistentClass(cl);
created.setNodeName("created");
created.setPropertyAccessorName("property");
SimpleValue value = new SimpleValue(cl.getTable());
value.addColumn(new Column(CREATED_COLUMN_NAME));
value.setTypeName("timestamp");
created.setValue(value);
cl.addProperty(created);
}
private void addPropertyCreatedBy(RootClass cl) {
classLogger.info("Adding property created by for " + cl.getClassName());
Property createdBy = new Property();
createdBy.setInsertable(true);
createdBy.setName("createdBy");
createdBy.setPersistentClass(cl);
createdBy.setNodeName("createdBy");
createdBy.setPropertyAccessorName("property");
SimpleValue value = new SimpleValue(cl.getTable());
value.addColumn(new Column(CREATED_BY_COLUMN_NAME));
value.setTypeName("long");
createdBy.setValue(value);
cl.addProperty(createdBy);
}
private void addPropertyLastUpdated(RootClass cl) {
classLogger.info("Adding property lastUpdated for " + cl.getClassName());
Property lastUpdated = new Property();
lastUpdated.setInsertable(true);
lastUpdated.setName("lastUpdated");
lastUpdated.setPersistentClass(cl);
lastUpdated.setNodeName("lastUpdated");
lastUpdated.setPropertyAccessorName("property");
SimpleValue value = new SimpleValue(cl.getTable());
value.addColumn(new Column(LAST_UPDATED_COLUMN_NAME));
value.setTypeName("timestamp");
lastUpdated.setValue(value);
cl.addProperty(lastUpdated);
}
private void addPropertyUpdatedBy(RootClass cl) {
classLogger.info("Adding property lastUpdated for " + cl.getClassName());
Property updatedBy = new Property();
updatedBy.setInsertable(true);
updatedBy.setName("updatedBy");
updatedBy.setPersistentClass(cl);
updatedBy.setNodeName("updatedBy");
updatedBy.setPropertyAccessorName("property");
SimpleValue value = new SimpleValue(cl.getTable());
value.addColumn(new Column(UPDATED_BY_COLUMN_NAME));
value.setTypeName("long");
updatedBy.setValue(value);
cl.addProperty(updatedBy);
}
public AuditableLocalSessionFactoryBean(List auditables) {
this.auditables = auditables;
AuditableClassLoader classLoader = new AuditableClassLoader(Thread.currentThread().getContextClassLoader(), auditables);
Thread.currentThread().setContextClassLoader(classLoader);
}
}
And each time a pojo is created or saved , an hibernate interceptor set the right values if the pojo extends the auditable class
Code:
/*
* Créé le 22 sept. 2006
*
* ponthiaux.eric@gmail.com
*
*
*/
package fr.generali.gb.commun.hibernate.interceptor;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Iterator;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.hibernate.CallbackException;
import org.hibernate.EntityMode;
import org.hibernate.Interceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type;
import fr.generali.gb.commun.log.LogManager;
/**
* Version modifiée de l'empty interceptor hibernate fournit en exemple sur le site hibernate
*
* @author ponthiaux.eric@gmail.com
*
*/
public class AcegyInterceptor implements Interceptor, Serializable {
private Log classLogger = LogManager.instance ().getLog (this.getClass ());
/**
*
* Creates a new UserInterceptor object.
*
*/
public AcegyInterceptor () {
}
private Long getCreatedBy(){
return new Long(0);
}
private Long getUpdatedBy(){
return new Long(1);
}
public void afterTransactionBegin (Transaction tx) {
}
public void afterTransactionCompletion (Transaction tx) {
}
public void beforeTransactionCompletion (Transaction tx) {
}
public int [] findDirty (Object entity, Serializable id, Object currentState[], Object previousState[], String propertyNames[], Type types[]) {
classLogger.debug ("Enter public int [] findDirty (Object entity, Serializable id, Object currentState[], Object previousState[], String propertyNames[], Type types[]) ");
if( (entity instanceof Auditable) ) {
Auditable auditable = ((Auditable)entity);
classLogger.debug("Setting last updated attribute in " + entity.getClass().getName());
classLogger.debug("Setting updated by attribute in " + entity.getClass().getName());
auditable.setLastUpdated(new Timestamp(new Date().getTime()));
auditable.setUpdatedBy(getUpdatedBy());
}
classLogger.debug ("Exit public int [] findDirty (Object entity, Serializable id, Object currentState[], Object previousState[], String propertyNames[], Type types[]) ");
return null;
}
public Object getEntity (String entityName, Serializable id) {
return null;
}
public String getEntityName (Object object) {
return null;
}
public Object instantiate (String entityName, EntityMode entityMode, Serializable id) {
return null;
}
public Boolean isTransient (Object entity) {
return null;
}
public void onCollectionRecreate (Object collection, Serializable key) throws CallbackException {
}
public void onCollectionRemove (Object collection, Serializable key) throws CallbackException {
}
public void onCollectionUpdate (Object collection, Serializable key) throws CallbackException {
}
public void onDelete (Object entity, Serializable id, Object state[], String propertyNames[], Type types[]) {
}
public boolean onFlushDirty (Object entity, Serializable id, Object currentState[], Object previousState[], String propertyNames[], Type types[]) {
if( (entity instanceof Auditable) ) {
Auditable auditable = ((Auditable)entity);
classLogger.debug("Setting last updated attribute in " + entity.getClass().getName());
classLogger.debug("Setting updated by attribute in " + entity.getClass().getName());
auditable.setLastUpdated(new Timestamp(new Date().getTime()));
auditable.setUpdatedBy(getUpdatedBy());
}
return false;
}
public boolean onLoad (Object entity, Serializable id, Object state[], String propertyNames[], Type types[]) {
classLogger.debug ("Enter public boolean onLoad (Object entity, Serializable id, Object state[], String propertyNames[], Type types[]) ");
if (entity instanceof Auditable) {
classLogger.debug ("loading an instance of auditable " +ToStringBuilder.reflectionToString(entity));
}
classLogger.debug ("Exit public boolean onLoad (Object entity, Serializable id, Object state[], String propertyNames[], Type types[]) ");
return false;
}
public String onPrepareStatement (String sql) {
classLogger.debug ("Enter public String onPrepareStatement (String sql) ");
classLogger.debug ("Exit public String onPrepareStatement (String sql)");
return sql;
}
public boolean onSave (Object entity, Serializable id, Object state[], String propertyNames[], Type types[]) {
classLogger.debug ("Enter public boolean onSave (Object entity, Serializable id, Object state[], String propertyNames[], Type types[]) ");
classLogger.debug ("Is instance of Auditable " + (entity instanceof Auditable));
if( (entity instanceof Auditable) ) {
Auditable auditable = ((Auditable)entity);
if( auditable.getCreated()==null) {
classLogger.debug("Setting created by attribute in " + entity.getClass().getName());
classLogger.debug("Setting created attribute in " + entity.getClass().getName());
auditable.setCreated(new Timestamp(new Date().getTime()));
auditable.setCreatedBy(getCreatedBy());
auditable.setLastUpdated(new Timestamp(new Date().getTime()));
auditable.setUpdatedBy(getCreatedBy());
} else {
classLogger.debug("Setting last updated attribute in " + entity.getClass().getName());
classLogger.debug("Setting updated by attribute in " + entity.getClass().getName());
auditable.setLastUpdated(new Timestamp(new Date().getTime()));
auditable.setUpdatedBy(getUpdatedBy());
}
}
classLogger.debug ("Exit public boolean onSave (Object entity, Serializable id, Object state[], String propertyNames[], Type types[]) ");
return false;
}
public void postFlush (Iterator entities) {
}
public void preFlush (Iterator entities) {
}
}
The result is that any pojo can now be auditable if the right columns are created in the database .
I have nothing to change in my code .
The spring config look like this :
Code:
<bean id="acegyInterceptor" class="fr.generali.gb.commun.hibernate.interceptor.AcegyInterceptor">
</bean>
<bean id="hibernateRessource" class="fr.generali.gb.commun.hibernate.factory.AuditableLocalSessionFactoryBean">
<constructor-arg>
<list>
<value>fr.generali.transaction.test.dao.pojo.MyDomainObject</value>
</list>
</constructor-arg>
<property name="configLocation">
<value>hibernate.cfg.xml</value>
</property>
<property name="entityInterceptor">
<ref bean="acegyInterceptor" />
</property>
</bean>
and the auditable class come from the Hibernate Auditable example
Code:
/*
* Créé le 22 sept. 2006
*
*/
package fr.generali.gb.commun.hibernate.interceptor;
import java.sql.Timestamp;
/**
* @author ponthiaux.eric@gmail.com
*
* Classe hibernate permettant de mettre à jour les infos d'accés a un objet de la base
*
*
*/
public class Auditable {
private Timestamp lastUpdated ;
private Timestamp created ;
private Long updatedBy ;
private Long createdBy ;
public Timestamp getLastUpdated() { return lastUpdated; }
protected void setLastUpdated(Timestamp lastUpdated) { this.lastUpdated = lastUpdated; }
public Timestamp getCreated() { return created; }
protected void setCreated(Timestamp created) { this.created = created; }
public Long getUpdatedBy() { return updatedBy; }
protected void setUpdatedBy(Long updatedBy) { this.updatedBy = updatedBy; }
public Long getCreatedBy() { return createdBy; }
public void setCreatedBy(Long createdBy) { this.createdBy = createdBy; }
public static boolean isAuditable(Object obj ) {
return (obj instanceof Auditable);
}
public static Auditable toAuditable(Object obj ) {
return (Auditable)obj;
}
}
All comments will be apreciated .