You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by Apache Wiki <wi...@apache.org> on 2008/01/19 19:59:27 UTC

[Tapestry Wiki] Update of "Tapestry5BrainlessEntityTimestamping" by ChrisLewis

Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Tapestry Wiki" for change notification.

The following page has been changed by ChrisLewis:
http://wiki.apache.org/tapestry/Tapestry5BrainlessEntityTimestamping

The comment on the change is:
How to implement entity timestamping with Hibernate using tapestry-hibernate.

New page:
If you've used [http://www.rubyonrails.org/ RoR] at all, or a knock-off such as [http://cakephp.org/ CakePHP], you may have found it's auto-magic time stamping quite useful. It works like this - if your model/domain object/entity defines a property named 'created', then every time a new object of that type is saved that field will be populated with the current timestamp in the resulting database row. Similarly, if the entity defines a 'modified' property then each time an existing row is updated the property will be set to the time of updating. I found this feature to be very useful, and so sought out how to do it using [http://tapestry.apache.org/tapestry5/tapestry-hibernate/index.html tapestry-hibernate] in a Tapestry 5 application.

== 1) The Hibernate Part ==
I am not a hibernate expert by any means, and my knowledge of what I am sharing comes directly from researching the different ways in which this could be accomplished.

To automatically have fields populated by Java code (so we don't rely on database-specific triggers), I assumed that hibernate probably exposed a kind of event system. As it turns out I was right, and the events system is robust and fully capable of doing this. However I chose the [http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Interceptor.html Interceptor] method. Following the advice shared in [http://www.google.com/search?client=safari&rls=it-it&q=java+persistence+with+hibernate&ie=UTF-8&oe=UTF-8 Java Persistence With Hibernate], I extended the [http://www.hibernate.org/hib_docs/v3/api/org/hibernate/EmptyInterceptor.html EmptyInterceptor] so I could simply override the methods I needed; namely, `onSave` and `isTransient`. Following is my simple implementation:

{{{
import java.io.Serializable;
import java.util.Date;

import org.hibernate.EmptyInterceptor;
import org.hibernate.Session;
import org.hibernate.TransientObjectException;
import org.hibernate.type.Type;
import org.slf4j.Logger;

/**
 * EntityInterceptor
 * 
 * @author Chris Lewis 19/gen/08 <ch...@thegodcode.net>
 * @version $Id: EntityInterceptor.java 25 2008-01-19 18:00:09Z burningodzilla $
 */
public class EntityInterceptor extends EmptyInterceptor {
	
	private Logger log;
	private Session session;
	
	public EntityInterceptor(Session session, Logger log) {
		this.session = session;
		this.log = log;
	}
	
	public boolean onSave(
			Object entity, 
			Serializable id, 
			Object[] state, 
			String[] propertyNames, 
			Type[] types) {
		
		boolean modified = false;
		Date dateModified = new Date();
		if(isTransient(entity)) {
			modified = modifyProperty("created", dateModified, state, propertyNames);
		}
		
		return modifyProperty("modified", dateModified, state, propertyNames) || modified;
	}
	
	public Boolean isTransient(Object entity) {
		/*
		 * Our algorithm for transience is extremely general. If the entity identifier
		 * is null, then we assume it is transient. 
		 */
		try {
			return this.session.getIdentifier(entity) == null;
		} catch (TransientObjectException e) {
			return true;
		}
		
	}
	
	/**
	 * Modify a property in an entity state array.
	 * @param prop the property name to modify
	 * @param value the value to assign
	 * @param state the current entity state array
	 * @param propertyNames the current entity property array
	 * @return <code>true</code> if a modification was made, <code>false</code> if not
	 */
	protected boolean modifyProperty(String prop, Object value, Object[] state, String[] propertyNames) {
		boolean modified = false;
		for(int i = 0; i < propertyNames.length; i++) {
			if(propertyNames[i].equals(prop)) {
				if(state[i] != value) {
					state[i] = value;
					modified = true;
				}
			}
		}
		return modified;
	}
	
}
}}}

This is just a simple [http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Interceptor.html Interceptor], it has nothing to do with Tapestry and could be used in any hibernate application. Looking at the `onSave` method, you can see that it attempts to update the fields `created` and `modified` of the entity being saved. The `modified` field is updated with the current date anytime the entity is saved, while the `created` field is set only when the entity is first saved. Note that no reflection, annotations, or interfaces are used here. If the entity has either of these fields, they will be updated according to this logic. Of course if you wish to be more restrictive or precise you can do that as well. Perhaps you want to change the field names, or maybe you want to use an annotation or explicit interface. That part would be easy to implement, so I leave that to you.


== 2) Wiring up the Interceptor via Tapestry IoC ==
Before proceeding it may be helpful to check out the official [http://tapestry.apache.org/tapestry5/tapestry-hibernate/conf.html configuration documentation] for tapestry-hibernate. All done? Good, moving on. You'll have noticed that the module does a little indirection by having you provide an implementation of [http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/hibernate/HibernateConfigurer.html HibernateConfigurer], which has the single method `configure`. As an argument to this method, you get a reference to the [http://www.hibernate.org/hib_docs/v3/api/org/hibernate/cfg/Configuration.html Configuration] object of the current session, which we need to attach our interceptor. The `HibernateConfigurer ` is skinny, so we'll use an anonymous implementation directly in our app module:

{{{
	public static void contributeHibernateSessionSource(
		OrderedConfiguration<HibernateConfigurer> config,
		final Session session) {
		
		config.add("HibernateConfiguration", new HibernateConfigurer() {
			public void configure(Configuration configuration) {
				/*
				 * I'm having trouble getting a reference to an implementation at the moment,
				 * so we'll settle on this for the moment.
				 */
				configuration.setInterceptor(new EntityInterceptor(session,
					LoggerFactory.getLogger(EntityInterceptor.class)));
			}
		});
	}
}}}

With this now wired up you can simply add `created` and `modified` fields (of type java.util.Date) to your entity class, and now the corresponding rows will auto-magically have these times updated for you.

== Conclusion ==
Obviously there are a handful of things to consider, including things like if you want a more strict system with annotations or interfaces marking the entity classes to be handled or what the fields should be called. You may also want to use a different data type for the field, and you may even prefer to use hibernate's events system instead of an interceptor. That is all up to you as the developer, this is simply an example of how you can be a little lazier ;-).

Thanks to TomTom in #tapestry on irc.freenode.net (IRC) for the chats on interceptors - they were most helpful.

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tapestry.apache.org
For additional commands, e-mail: dev-help@tapestry.apache.org