-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 posts ] 
Author Message
 Post subject: Dirty, but no dirty properties
PostPosted: Wed Apr 05, 2006 6:43 pm 
Regular
Regular

Joined: Wed Jul 07, 2004 2:00 pm
Posts: 64
I have a group of objects, each of which has a property 'createdSignature' mapped as a component. This class contains a user ID (String) and a date/time (Calendar).

I have an interceptor that traps onSave and onFlushDirty to set these values. Sometimes, but not always, I get an error 'dirty, but no dirty properties'.

For these tests with intermitent errors, I am calling merge, passing it a new object A, which has a 1-M bag containing a single new B.

This problem did not appear before adding an interceptor. The interceptor code is:
Code:
public class AuditInterceptor extends EmptyInterceptor {
    public boolean onFlushDirty(Object entity, Serializable id,
            Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        if (entity instanceof IAuditable) {
            for (int i = 0; i < propertyNames.length; i++) {
                if ("createdSignature".equals(propertyNames[i])) {
                    currentState[i] = createSignature();
                    return true;
                }
            }
        }
        return false;
    }

    public boolean onSave(Object entity, Serializable id, Object[] state,
            String[] propertyNames, Type[] types) {
        if (entity instanceof IAuditable) {
            for (int i = 0; i < propertyNames.length; i++) {
                if ("createdSignature".equals(propertyNames[i])) {
                    state[i] = createSignature();
                    return true;
                }
            }
        }
        return false;
    }

    private UserSignature createSignature() {
        UserSignature signature = new UserSignature();
        String assignID;
        try {
            assignID = SessionManager.getSession(
                    SecurityContext.getInstance().getSessionId())
                    .getMainAssignmentID();
        } catch (NoSessionException ex) {
            throw new HibernateException(ex);
        }
        signature.setAssignmentId(assignID);
        signature.setDate(Calendar.getInstance());
        return signature;
    }
}


By the way, I found that the event that triggered the exception was a flush on the child (B) entity.

Any ideas?

Hibernate version: 3.1.1

Full stack trace of any exception that occurs:
2006-04-05 18:18:58,151 ERROR [AssertionFailure.<init>] an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: dirty, but no dirty properties
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:249)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:114)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:195)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1007)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:354)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at ca.gc.csc_scc.model.impl.dao.hibernate.util.HibernateUtil.commitTransaction(HibernateUtil.java:127)
at ca.gc.csc_scc.model.impl.service.handler.TransactionHandler.invoke(TransactionHandler.java:47)
at $Proxy2.save(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.apache.axis.providers.java.RPCProvider.invokeMethod(RPCProvider.java:397)
at org.apache.axis.providers.java.RPCProvider.processMessage(RPCProvider.java:186)
at org.apache.axis.providers.java.JavaProvider.invoke(JavaProvider.java:323)
at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)
at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)
at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83)
at org.apache.axis.handlers.soap.SOAPService.invoke(SOAPService.java:453)
at org.apache.axis.server.AxisServer.invoke(AxisServer.java:281)
at org.apache.axis.transport.http.AxisServlet.doPost(AxisServlet.java:699)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
at org.apache.axis.transport.http.AxisServletBase.service(AxisServletBase.java:327)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at com.evermind.server.http.ResourceFilterChain.doFilter(ResourceFilterChain.java:65)
at com.osmoticweb.gzipfilter.GZIP2WayFilter.doFilter(GZIP2WayFilter.java:54)
at com.evermind.server.http.ServletRequestDispatcher.invoke(ServletRequestDispatcher.java:604)
at com.evermind.server.http.ServletRequestDispatcher.forwardInternal(ServletRequestDispatcher.java:317)
at com.evermind.server.http.HttpRequestHandler.processRequest(HttpRequestHandler.java:790)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:270)
at com.evermind.server.http.HttpRequestHandler.run(HttpRequestHandler.java:112)
at com.evermind.util.ReleasableResourcePooledExecutor$MyWorker.run(ReleasableResourcePooledExecutor.java:192)
at java.lang.Thread.run(Thread.java:534)


Name and version of the database you are using: Oracle


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 06, 2006 6:16 pm 
Regular
Regular

Joined: Wed Jul 07, 2004 2:00 pm
Posts: 64
I found something interesting. In the Interceptor, the property 'createdSignature' is mapped as a component. If I change things so that the Interceptor updates individual properties instead of the component (ie, collapse the component), then the problem does not occur. In addition, in the few cases where the Interceptor did not give the 'dirty...' message, I noticed that the insert was later followed by an update (changing the state in the onSave caused a later call to onFlushDirty for the new entity, and changing the state here caused the extra update). With the collapsed properties approach, this extra update goes away. A bug?


Top
 Profile  
 
 Post subject:
PostPosted: Mon Apr 17, 2006 11:19 am 
Newbie

Joined: Tue Feb 07, 2006 1:33 pm
Posts: 5
How are you collapsing the components? Is this some option in Hibernate, or did you write the code yourself? I'm experiencing the same thing, but it sounds like you've already got something figured out.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Apr 17, 2006 11:56 pm 
Regular
Regular

Joined: Wed Jul 07, 2004 2:00 pm
Posts: 64
My component is simple - consisting of only 2 properties, as you can see from the createSignature method. Instead of having an instance variable of type UserSignature and mapping the property, I declare 2 instance variables in my persistent class - the values for 'assignmentId' and 'date' shown in my earlier code example. These 2 instance variables are then mapped individually with field access. The 'createdSignature' JavaBean property still exists, but is not mapped by Hiberate and looks something like:

public UserSignature getCreatedSignature() {
return new UserSignature(createAssignmentId,createDate);
}

public void setCreatedSignature(UserSignature sig) {
createAssignmentId=sig.getAssignmentId();
createDate = sig.getDate();
}

Then in the interceptor, I just look for createAssignmentId and createDate in propertyNames[].

Not nice, especially since this introduces naming requirements for private instance variables. In fact, it is more complex than I described, since my interceptor has to deal with different types of signatures - sometimes just creation, sometimes create and update separately, sometimes create & update with the same fields, etc.

At least this approach works, though, and hides the mess in the implementation of the entity.

I'm really hoping that this will be explained or fixed by the Hibernate team.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 19, 2006 11:27 am 
Newbie

Joined: Tue Feb 07, 2006 1:33 pm
Posts: 5
All of the things I'm auditing are beans, so I could take a little bit of a different approach. If I detect a ComponentType, I call a little helper method to organize the data, then I call the appropriate Interceptor method.

So, the general concept is to go onSave()->handleComponentInsert()->onSave()

Some pseudocode and the actual helpers:
Code:
   public boolean onSave(.....) {
      for(int i = 0; i < state.length; i++) {
         if(types != null && types[i] instanceof ComponentType) {
            handleComponentInsert(state[i]);
            continue;
         }

         ....
      }
   }

   public void onDelete(.....) {
      for(int i = 0; i < state.length; i++) {
         if(types != null && types[i] instanceof ComponentType) {
            handleComponentDelete(state[i]);
            continue;
         }


         .....
      }
   }

   public boolean onFlushDirty(.....) {
      for (int i = 0; i < currentState.length; i++) {
         if(types != null && types[i] instanceof ComponentType) {
            handleComponentDirty(previousState[i], currentState[i]);
            continue;
         }

         .....
      }
   }
   
   /**
    * Helper method that onSave() can call when it encounters a ComponentType. This method
    * organizes data about the component and calls onSave(component), since Hibernate
    * does not do this automatically.
    *
    * @param component the Component that's being inserted
    */
   private void handleComponentInsert(Object component) {
      try {
         String[] propertyNames = getPropertyNames(component);
         Object[] state = getState(component);
         this.onSave(component, null, state, propertyNames, null);
      } catch(Exception e) {
         logger.error("An error occurred while handling a component insertion in the Audit Trail", e);
      }
   }   
   
   
   /**
    * Helper method that onFlushDirty() can call when it encounters a ComponentType. This method
    * organizes data about the component and calls onFlushDirty(), since Hibernate
    * does not do this automatically.
    *
    * @param priorComponent the previous state of the Component
    * @param newComponent the current state of the Component
    */
   private void handleComponentDirty(Object priorComponent, Object newComponent) {
      try {
         String[] propertyNames = getPropertyNames(priorComponent);
         Object[] previousState = getState(priorComponent);
         Object[] currentState = getState(newComponent);
         
         this.onFlushDirty(newComponent, null, currentState, previousState, propertyNames, null);
      } catch(Exception e) {
         logger.error("An error occurred while handling a dirty component in the Audit Trail", e);
      }
   }
   
   /**
    * Helper method that onDelete() can call when it encounters a ComponentType. This method
    * organizes data about the component and calls onDelete(), since Hibernate
    * does not do this automatically.
    *
    * @param component the Component that's being deleted
    */
   private void handleComponentDelete(Object component) {
      //Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types
      try {
         String[] propertyNames = getPropertyNames(component);
         Object[] state = getState(component);
         this.onDelete(component, null, state, propertyNames, null);
      } catch(Exception e) {
         logger.error("An error occurred while handling a component insertion in the Audit Trail", e);
      }
   }
   
   /**
    * Gets all of the property values for a bean
    *
    * @param o the object whose getters will be called
    * @return the property values for a bean
    *
    * @throws IntrospectionException
    * @throws IllegalAccessException
    * @throws InvocationTargetException
    */
   private Object[] getState(Object o) throws IntrospectionException, IllegalAccessException, InvocationTargetException {
      BeanInfo bi = Introspector.getBeanInfo(o.getClass());
      PropertyDescriptor[] descriptors = bi.getPropertyDescriptors();
      
      Object[] states = new Object[descriptors.length];
      for(int i = 0; i < descriptors.length; i++) {
         Method m = descriptors[i].getReadMethod();
         
         states[i] = m.invoke(o, null);
      }
      
      return states;
   }
   
   /**
    * This method discovers all of the property names for a bean
    *
    * @param o the object whose properties will be discovered
    * @return the property names
    *
    * @throws IntrospectionException
    */
   private String[] getPropertyNames(Object o) throws IntrospectionException {
      //Use an Introspector to get all of the PropertyDescriptors
      BeanInfo bi = Introspector.getBeanInfo(o.getClass());
      PropertyDescriptor[] descriptors = bi.getPropertyDescriptors();
      
      //Use the PropertyDescriptors to get the name of each bean property
      String[] names = new String[descriptors.length];
      for(int i = 0; i < descriptors.length; i++) {
         names[i] = descriptors[i].getName();
      }
      return names;
   }


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 20, 2006 3:19 pm 
Regular
Regular

Joined: Wed Jul 07, 2004 2:00 pm
Posts: 64
This works, except for one problem. In onSave and onFlushDirty, the component object may not exist yet (ie, state[i]=null). My interceptor is responsible for setting these values.

By the way, why are you calling onSave, etc from within your handComponent... methods? It appears that Hibernate doesn't usually call the interceptor passing a component in place of the entity.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 21, 2006 8:14 am 
Newbie

Joined: Tue Feb 07, 2006 1:33 pm
Posts: 5
DWright wrote:
This works, except for one problem. In onSave and onFlushDirty, the component object may not exist yet (ie, state[i]=null). My interceptor is responsible for setting these values.


I actually do, but I cut that part out of onSave(), etc for brevity.

DWright wrote:
By the way, why are you calling onSave, etc from within your handComponent... methods? It appears that Hibernate doesn't usually call the interceptor passing a component in place of the entity.



The whole point is to get around Hibernate's failure to intercept components. I'm trying to log changes to properties of beans, so onSave, etc should behave the same, whether the entity is a Component or not. If Hibernate's not going to intercept some things for me, I'm just going to organize the data in my helper functions and send it back through the onSave, etc functions. This way it doesn't matter how the object is mapped, it gets handled correctly by common code. You could think of it as manually adding another layer of tree traversal to what Hibernate's already doing.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 21, 2006 4:37 pm 
Regular
Regular

Joined: Wed Jul 07, 2004 2:00 pm
Posts: 64
I have a property of type UserSignature of my persistent entity. The only code in the system that sets that property is the interceptor. As a result, the state[i] value for the property will be null in onSave and onFlushDirty. y original attempt was to then assign a value to state[i], and this is the cause of the exception I was receiving. Your approach works only when state[i] is not null, I believe.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.