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.  [ 7 posts ] 
Author Message
 Post subject: Audit Interceptor needs to look up current username
PostPosted: Wed Nov 30, 2005 7:54 pm 
Newbie

Joined: Mon Sep 26, 2005 2:40 pm
Posts: 17
Location: Seattle
I've written an interceptor that populates Audit information on objects of a particular type (SpeakeasyAuditedObject). Among this audit information is the current User. Acegi gives me a username, and the Interceptor looks up the User.

The problem is that apparently the process of looking up the user by its name is causing the session to be flushed, which causes the saved object to actually be written, which causes a null-constraint error on the not-yet-saved "createdBy" field. Which, of course, is the field I was trying to put the user IN.

How can I look up a new record without causing this to happen?

Right now, I'm only trying to get it working for "save" actions, "update" actions will, of course, be my next task.

Here's the Interceptor code:

Code:
public class AuditInterceptor extends EmptyInterceptor {

   private SpeakeasyDomainDao<User> userDao;

   static final String DATE_CREATED_PROPERTY = "dateCreated";

   static final String CREATED_BY_PROPERTY = "createdBy";

   static final String DATE_MODIFIED_PROPERTY = "dateModified";

   static final String MODIFIED_BY_PROPERTY = "modifiedBy";

   /**
    * This class is Serializable so that it may be persisted when sessions move
    * or are 'paused' for server restarts.
    */
   private static final long serialVersionUID = 1L;
   
   /**
    * Modifies the state to add the values for the four friendly fields
    */
   @Override
   public boolean onSave(Object entity, Serializable id, Object[] state,
         String[] propertyNames, Type[] types) {

      boolean isChanged = false;

      if (entity instanceof SpeakeasyAuditedObject) {
         User currentUser = getCurrentUser();
         Date now = new Date();
         isChanged = true;
         for (int i = 0; i < propertyNames.length; i++) {
            String propertyName = propertyNames[i];
            if (propertyName.equals(DATE_CREATED_PROPERTY)) {
               state[i] = now;
            } else if (propertyName.equals(CREATED_BY_PROPERTY)) {
               state[i] = currentUser;
            } else if (propertyName.equals(DATE_MODIFIED_PROPERTY)) {
               state[i] = now;
            } else if (propertyName.equals(MODIFIED_BY_PROPERTY)) {
               state[i] = currentUser;
            }
         }
      }
      return isChanged;
   }

   /**
    * Gets the current user's id from the Acegi secureContext
    *
    * @return current user's userId
    */
   private User getCurrentUser() {

      SecureContext secureContext = SecureContextUtils.getSecureContext();
      assert secureContext != null : "no secure context available";
      Authentication auth = secureContext.getAuthentication();
      assert auth != null : "no authentication available";

      Object principal = auth.getPrincipal();
      assert principal != null : "no principal available";

      String userName = null;
      if (principal instanceof UserDetails) {
         UserDetails userDetails = (UserDetails) principal;
         userName = userDetails.getUsername();
      } else {

         userName = principal.toString();
      }

      // isn't this is going to be very slow?
      assert userName != null : "No username available";
      User exampleUser = new User();
      exampleUser.setName(userName);
      User user = userDao.get(exampleUser);
      assert user != null : "Unable to determine current user";
      return user;
   }

   public SpeakeasyDomainDao<User> getUserDao() {
      return userDao;
   }

   /**
    * Sets the userDao to use to find the User object for the current username.
    * This attribute is required.
    */
   public void setUserDao(SpeakeasyDomainDao<User> userDao) {
      this.userDao = userDao;
   }
}




Here's the code for the "SpeakeasyDomainDao.get"

Code:
public abstract class AbstractDao<T> extends HibernateDaoSupport {

(snip)

   public T get(T example) {
      return (T) getSession().createCriteria(example.getClass()).add(
            Example.create(example)).uniqueResult();
   }
}


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 01, 2005 4:12 am 
Expert
Expert

Joined: Mon Jul 04, 2005 5:19 pm
Posts: 720
use logging statement to pin point the exact instruction you believe is causing the Session to be flushed.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 01, 2005 4:48 pm 
Newbie

Joined: Mon Sep 26, 2005 2:40 pm
Posts: 17
Location: Seattle
In the selection process for a User, the UserDao calls this:

getSession().createCriteria(example.getClass()).add(
Example.create(example)).uniqueResult();

The sessionImpl.list() method calls this:
autoFlushIfRequired(spaces);

Which goes eventually to the

DefaultAutoFlushEventListener.flushEverythingToExecutions(FlushEvent event)

Which calls

AbstractFlushingEventListener.flushEntities(flushEvent)

The last debug statement I get before the exception is thrown (not-null violated on createdBy field)

11:16:20,391 DEBUG DefaultFlushEntityEventListener:212 - Updating entity: [net.speakeasy.domain.account.Customer#300311]

Clearly I need to prevent this flush, either by getting the User reference a different way, or by configuring the flush behavior to avoid this case. I don't see how to do either.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 01, 2005 5:10 pm 
Expert
Expert

Joined: Mon Jul 04, 2005 5:19 pm
Posts: 720
perhaps you have found one of the lesser common use cases for two sessions per unit of work ?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 01, 2005 5:24 pm 
Newbie

Joined: Mon Sep 26, 2005 2:40 pm
Posts: 17
Location: Seattle
Wouldn't merging the newly queried User from the other session cause a flush as well?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 01, 2005 6:01 pm 
Expert
Expert

Joined: Mon Jul 04, 2005 5:19 pm
Posts: 720
hmm ... try getting the user w/out uniqueResult() . Session.load() or Session.get() perhaps.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 01, 2005 6:46 pm 
Newbie

Joined: Mon Sep 26, 2005 2:40 pm
Posts: 17
Location: Seattle
Okay, so it doesn't. Here's the final code for the Interceptor's User lookup method, for those of you following along at home:



Code:
      User exampleUser = new User();
      exampleUser.setName(userName);
      
      SessionFactory sessionFactory = userDao.getSessionFactory();
      Session session = sessionFactory.openSession();
      User user = (User) session.createCriteria(User.class).add(
            Example.create(exampleUser)).uniqueResult();
      
      //User user = userDao.get(exampleUser);
      Session currentSession = SessionFactoryUtils.getSession(sessionFactory, false);
      assert currentSession != null : "No current session";
      assert user != null : "Unable to determine current user";
      User mergedUser = (User) currentSession.merge(user);
      return mergedUser;


Now I need to look up how expensive an operation the 'merge' action is.

-Kevin


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 7 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.