[jboss-cvs] jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener ...

Christian Bauer christian at hibernate.org
Tue Dec 18 23:29:21 EST 2007


  User: cbauer  
  Date: 07/12/18 23:29:21

  Added:       examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener      
                        InsertNestedSetOperation.java
                        NestedSetPostInsertEventListener.java
                        NestedSetOperation.java
                        NestedSetPostDeleteEventListener.java package.html
                        DeleteNestedSetOperation.java
  Log:
  Major rewrite of the most of the application
  
  Revision  Changes    Path
  1.1      date: 2007/12/19 04:29:21;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener/InsertNestedSetOperation.java
  
  Index: InsertNestedSetOperation.java
  ===================================================================
  /*
   * JBoss, Home of Professional Open Source
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.seam.wiki.core.nestedset.listener;
  
  import org.hibernate.StatelessSession;
  import org.hibernate.Query;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.jboss.seam.wiki.core.nestedset.NestedSetNode;
  
  import java.util.Collection;
  
  /**
   * Moves the values of all nodes on the right side of an inserted node.
   *
   * @author Christian Bauer
   */
  class InsertNestedSetOperation extends NestedSetOperation {
  
      private static final Log log = LogFactory.getLog(InsertNestedSetOperation.class);
  
      long spaceNeeded = 2l;
      long parentThread;
      long newLeft;
      long newRight;
  
      public InsertNestedSetOperation(NestedSetNode node) {
          super(node);
      }
  
      protected void beforeExecution() {
          log.trace("nested set insert of entity: " + nodeEntityName);
  
          if (node.getParentNodeInfo() == null) {
              log.trace("node has no parent, starting new thread");
              // Root node of a tree, new thread
              parentThread = node.getId();
              newLeft = 1l;
              newRight = 2l;
          } else {
              log.trace("node has a parent, appending to existing thread");
              // Child node of a parent
              parentThread = node.getParentNodeInfo().getNsThread();
              newLeft = node.getParentNodeInfo().getNsRight();
              newRight = newLeft + spaceNeeded -1;
          }
  
          log.trace("calculated the thread: " + parentThread + " left: " + newLeft + " right: " + newRight);
      }
  
      protected void executeOnDatabase(StatelessSession ss) {
          log.trace("executing nested set insert on database");
  
          Query updateLeft =
                  ss.createQuery("update " + nodeEntityName + " n set " +
                                 " n.nodeInfo.nsLeft = n.nodeInfo.nsLeft + :spaceNeeded " +
                                 " where n.nodeInfo.nsThread = :thread and n.nodeInfo.nsLeft > :right");
          updateLeft.setParameter("spaceNeeded", spaceNeeded);
          updateLeft.setParameter("thread", parentThread);
          updateLeft.setParameter("right", newLeft);
          int updateLeftCount = updateLeft.executeUpdate();
          log.trace("updated left values of nested set nodes: " + updateLeftCount);
  
          Query updateRight =
                  ss.createQuery("update " + nodeEntityName + " n set " +
                                 " n.nodeInfo.nsRight = n.nodeInfo.nsRight + :spaceNeeded " +
                                 " where n.nodeInfo.nsThread = :thread and n.nodeInfo.nsRight >= :right");
          updateRight.setParameter("spaceNeeded", spaceNeeded);
          updateRight.setParameter("thread", parentThread);
          updateRight.setParameter("right", newLeft);
          int updateRightCount = updateRight.executeUpdate();
          log.trace("updated right values of nested set nodes: " + updateRightCount);
  
          log.trace("updating the newly inserted row with thread, left, and right values");
          /*
              TODO: http://opensource.atlassian.com/projects/hibernate/browse/HHH-1657
          Query updateNode =
                  ss.createQuery("update " + nodeEntityName + " n set " +
                                 " n.nodeInfo.nsLeft = :left, n.nodeInfo.nsRight = :right, n.nodeInfo.nsThread = :thread " +
                                 " where n.id = :id");
           */
          Query updateNode = ss.getNamedQuery("updateNestedSet."+nodeEntityName);
  
          updateNode.setParameter("thread", parentThread);
          updateNode.setParameter("left", newLeft);
          updateNode.setParameter("right", newRight );
          updateNode.setParameter("id", node.getId());
          updateNode.executeUpdate();
      }
  
      protected void executeInMemory(Collection<NestedSetNode> nodesInPersistenceContext) {
          log.trace("updating in memory nodes (flat) in the persistence context: " + nodesInPersistenceContext.size());
  
          for (NestedSetNode n : nodesInPersistenceContext) {
              if (n.getNodeInfo().getNsThread().equals(parentThread) && n.getNodeInfo().getNsLeft() > newLeft) {
                  n.getNodeInfo().setNsLeft(n.getNodeInfo().getNsLeft() + spaceNeeded);
              }
              if (n.getNodeInfo().getNsThread().equals(parentThread) && n.getNodeInfo().getNsRight() >= newLeft) {
                  n.getNodeInfo().setNsRight(n.getNodeInfo().getNsRight() + spaceNeeded);
              }
          }
  
      }
  
      protected void afterExecution() {
          // Set the values of the "read-only" properties
          node.getNodeInfo().setNsThread(parentThread);
          node.getNodeInfo().setNsLeft(newLeft);
          node.getNodeInfo().setNsRight(newRight);
      }
  }
  
  
  
  1.1      date: 2007/12/19 04:29:21;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener/NestedSetPostInsertEventListener.java
  
  Index: NestedSetPostInsertEventListener.java
  ===================================================================
  /*
   * JBoss, Home of Professional Open Source
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.seam.wiki.core.nestedset.listener;
  
  import org.hibernate.ejb.event.EJB3PostInsertEventListener;
  import org.hibernate.event.PostInsertEvent;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.jboss.seam.wiki.core.nestedset.NestedSetNode;
  
  /**
   * Executes the nested set tree traversal after a node was inserted.
   *
   * @author Christian Bauer
   */
  public class NestedSetPostInsertEventListener extends EJB3PostInsertEventListener {
  
      private static final Log log = LogFactory.getLog(NestedSetPostInsertEventListener.class);
  
      public void onPostInsert(PostInsertEvent event) {
          super.onPostInsert(event);
  
          if ( NestedSetNode.class.isAssignableFrom(event.getEntity().getClass())) {
              log.debug("executing nested set insert operation, recalculating the tree");
              new InsertNestedSetOperation( (NestedSetNode)event.getEntity() ).execute(event.getSession());
          }
      }
  
  }
  
  
  1.1      date: 2007/12/19 04:29:21;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener/NestedSetOperation.java
  
  Index: NestedSetOperation.java
  ===================================================================
  /*
   * JBoss, Home of Professional Open Source
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.seam.wiki.core.nestedset.listener;
  
  import org.hibernate.Session;
  import org.hibernate.StatelessSession;
  import org.hibernate.event.EventSource;
  import org.hibernate.impl.SessionFactoryImpl;
  import org.hibernate.util.LazyIterator;
  import org.jboss.seam.wiki.core.nestedset.NestedSetNode;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import java.util.Collection;
  import java.util.HashSet;
  import java.util.Iterator;
  
  /**
   * The contract of a nested set operation sequence as executed in a Hibernate event listener.
   * <p>
   * Guarantees that first the database tree nodes are updated, then the in-memory nodes
   * currently managed by the persistence context.
   * </p>
   * To access the database, an operation uses a <tt>StatelessSession</tt> of Hibernate, and
   * it obtains a JDBC connection using the Hibernate connection provider. If run in an
   * application server with JTS/JTA, the <tt>getConnection()</tt> method
   * returns the same connection handle that is already used inside the current transaction.
   * This means we run on the same connection and transaction as the rest of the Hibernate flush event
   * that executes the <tt>NestedSetOperation</tt>. However, if you run this outside of a managed
   * environment, a new JDBC connection might be obtained from the JDBC connection pool.
   * In that case, you should enable auto-commit mode in your Hibernate configuration. Or,
   * if you want the database tree updates to be atomic and isolated (a good idea), you can
   * override the <tt>beforeExecution()</tt> and <tt>afterExecution()</tt> methods and begin
   * and commit a database transaction manually. Note that this still would be outside the
   * initial connection and transaction, and therefore not be atomic with the overall tree
   * manipulation. This can be improved as soon as Hibernate implements a new contract
   * for the deprecated <tt>Session#connection()</tt> method.
   * </p>
   *
   * TODO: We should lock the tables!
   *
   * @author Christian Bauer
   */
  public class NestedSetOperation {
  
      private static final Log log = LogFactory.getLog(NestedSetOperation.class);
  
      protected NestedSetNode node;
      protected String nodeEntityName;
  
      public NestedSetOperation(NestedSetNode node) {
          this.node = node;
          this.nodeEntityName = node.getClass().getName();
      }
  
      // The main sequence of the operation, override to implement your operation
  
      protected void beforeExecution() {}
      protected void executeOnDatabase(StatelessSession session) {}
      protected void executeInMemory(Collection<NestedSetNode> inMemoryState) {}
      protected void afterExecution() {}
  
      // The procedure that executes the sequence of the operation
  
      public void execute(EventSource session) {
          StatelessSession ss = null;
          Connection jdbcConnection = null;
          try {
              jdbcConnection = getConnection(session);
              log.trace("opening new session on existing connection");
              ss = session.getSessionFactory().openStatelessSession(jdbcConnection);
  
              beforeExecution();
              executeOnDatabase(ss);
  
              // Find all NestedSetNode instances in the persistence context
              Collection<NestedSetNode> nodesInPersistenceContext = new HashSet<NestedSetNode>();
              Iterator contextIterator = new LazyIterator( session.getPersistenceContext().getEntitiesByKey() );
              while (contextIterator.hasNext()) {
                  Object o = contextIterator.next();
                  if (NestedSetNode.class.isAssignableFrom(o.getClass())) nodesInPersistenceContext.add((NestedSetNode)o);
              }
  
              executeInMemory(nodesInPersistenceContext);
              afterExecution();
  
          } catch (Exception ex) {
              log.error(ex);
              throw new RuntimeException(ex);
          } finally {
              if (ss != null) {
                  try {
                      log.trace("closing session and connection");
                      jdbcConnection.close();
                      ss.close();
                  } catch(SQLException ex) {
                      log.error("could not close connection or session", ex);
                  }
              }
          }
      }
  
      protected Connection getConnection(Session session) throws Exception {
          // We do not use session.connection() because it conflicts with Hibernates aggressive collection release
          return ((SessionFactoryImpl)session.getSessionFactory()).getConnectionProvider().getConnection();
      }
  
  }
  
  
  
  1.1      date: 2007/12/19 04:29:21;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener/NestedSetPostDeleteEventListener.java
  
  Index: NestedSetPostDeleteEventListener.java
  ===================================================================
  /*
   * JBoss, Home of Professional Open Source
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.seam.wiki.core.nestedset.listener;
  
  import org.hibernate.ejb.event.EJB3PostDeleteEventListener;
  import org.hibernate.event.PostDeleteEvent;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.jboss.seam.wiki.core.nestedset.NestedSetNode;
  
  /**
   * Executes the nested set tree traversal after a node was deleted.
   *
   * @author Christian Bauer
   */
  public class NestedSetPostDeleteEventListener extends EJB3PostDeleteEventListener {
  
      private static final Log log = LogFactory.getLog(NestedSetPostDeleteEventListener.class);
  
      public void onPostDelete(PostDeleteEvent event) {
          super.onPostDelete(event);
  
          if ( NestedSetNode.class.isAssignableFrom(event.getEntity().getClass())) {
              log.debug("executing nested set delete operation, recalculating the tree");
              new DeleteNestedSetOperation( (NestedSetNode)event.getEntity() ).execute(event.getSession());
          }
      }
  
  }
  
  
  
  1.1      date: 2007/12/19 04:29:21;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener/package.html
  
  Index: package.html
  ===================================================================
  <html>
  <head></head>
  <body>
  
  <p>
      Hibernate event listeners for transparent in-database nested set tree updates.
  </p>
  
  </body>
  </html>
  
  
  1.1      date: 2007/12/19 04:29:21;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/nestedset/listener/DeleteNestedSetOperation.java
  
  Index: DeleteNestedSetOperation.java
  ===================================================================
  /*
   * JBoss, Home of Professional Open Source
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.seam.wiki.core.nestedset.listener;
  
  import org.hibernate.Query;
  import org.hibernate.StatelessSession;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.jboss.seam.wiki.core.nestedset.NestedSetNode;
  
  import java.util.Collection;
  
  /**
   * Moves the values of all nodes on the right side of a deleted node.
   *
   * @author Christian Bauer
   */
  class DeleteNestedSetOperation extends NestedSetOperation {
  
      private static final Log log = LogFactory.getLog(DeleteNestedSetOperation.class);
  
      long databaseMoveOffset;
  
      public DeleteNestedSetOperation(NestedSetNode node) {
          super(node);
      }
  
      protected void beforeExecution() {
          databaseMoveOffset = node.getNodeInfo().getNsRight() - node.getNodeInfo().getNsLeft() + 1;
          log.trace("calculated database offset: " + databaseMoveOffset);
      }
  
      protected void executeOnDatabase(StatelessSession ss) {
  
          Query updateLeft =
                  ss.createQuery("update " + nodeEntityName + " n set " +
                                 " n.nodeInfo.nsLeft = n.nodeInfo.nsLeft - :offset " +
                                 " where n.nodeInfo.nsThread = :thread and n.nodeInfo.nsLeft > :right");
          updateLeft.setParameter("offset", databaseMoveOffset);
          updateLeft.setParameter("thread", node.getNodeInfo().getNsThread());
          updateLeft.setParameter("right", node.getNodeInfo().getNsRight());
          int updateLeftCount = updateLeft.executeUpdate();
          log.trace("updated left values of nested set nodes: " + updateLeftCount);
  
          Query updateRight =
                  ss.createQuery("update " + nodeEntityName + " n set " +
                                 " n.nodeInfo.nsRight = n.nodeInfo.nsRight - :offset " +
                                 " where n.nodeInfo.nsThread = :thread and n.nodeInfo.nsRight > :right");
          updateRight.setParameter("offset", databaseMoveOffset);
          updateRight.setParameter("thread", node.getNodeInfo().getNsThread());
          updateRight.setParameter("right", node.getNodeInfo().getNsRight());
          int updateRightCount = updateRight.executeUpdate();
          log.trace("updated right values of nested set nodes: " + updateRightCount);
      }
  
      protected void executeInMemory(Collection<NestedSetNode> nodesInPersistenceContext) {
          log.trace("updating in memory nodes (flat) in the persistence context: " + nodesInPersistenceContext.size());
  
          for (NestedSetNode n: nodesInPersistenceContext) {
  
              if (n.getNodeInfo().getNsThread().equals(node.getNodeInfo().getNsThread())
                  && n.getNodeInfo().getNsRight() > node.getNodeInfo().getNsRight()) {
  
                  n.getNodeInfo().setNsRight(n.getNodeInfo().getNsRight() - databaseMoveOffset);
              }
  
              if (n.getNodeInfo().getNsThread().equals(node.getNodeInfo().getNsThread())
                  && n.getNodeInfo().getNsLeft() > node.getNodeInfo().getNsRight()) {
  
                  n.getNodeInfo().setNsLeft(n.getNodeInfo().getNsLeft() - databaseMoveOffset);
              }
          }
      }
  }
  
  
  



More information about the jboss-cvs-commits mailing list