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 <[EMAIL PROTECTED]>
 * @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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to