diff options
Diffstat (limited to 'hibernate/org.eclipse.emf.teneo.hibernate/src/org/eclipse/emf/teneo/hibernate/auditing/AuditProcessHandler.java')
-rwxr-xr-x | hibernate/org.eclipse.emf.teneo.hibernate/src/org/eclipse/emf/teneo/hibernate/auditing/AuditProcessHandler.java | 252 |
1 files changed, 162 insertions, 90 deletions
diff --git a/hibernate/org.eclipse.emf.teneo.hibernate/src/org/eclipse/emf/teneo/hibernate/auditing/AuditProcessHandler.java b/hibernate/org.eclipse.emf.teneo.hibernate/src/org/eclipse/emf/teneo/hibernate/auditing/AuditProcessHandler.java index ebb59b8f3..03d0793a6 100755 --- a/hibernate/org.eclipse.emf.teneo.hibernate/src/org/eclipse/emf/teneo/hibernate/auditing/AuditProcessHandler.java +++ b/hibernate/org.eclipse.emf.teneo.hibernate/src/org/eclipse/emf/teneo/hibernate/auditing/AuditProcessHandler.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; @@ -37,6 +39,7 @@ import org.eclipse.emf.teneo.hibernate.auditing.model.teneoauditing.Teneoauditin import org.eclipse.emf.teneo.hibernate.auditing.model.teneoauditing.TeneoauditingPackage; import org.eclipse.emf.teneo.util.StoreUtil; import org.hibernate.FlushMode; +import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; @@ -46,6 +49,8 @@ import org.hibernate.action.spi.AfterTransactionCompletionProcess; import org.hibernate.action.spi.BeforeTransactionCompletionProcess; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.spi.EventSource; +import org.hibernate.event.spi.FlushEvent; +import org.hibernate.event.spi.FlushEventListener; import org.hibernate.event.spi.PostDeleteEvent; import org.hibernate.event.spi.PostDeleteEventListener; import org.hibernate.event.spi.PostInsertEvent; @@ -61,11 +66,16 @@ import org.hibernate.event.spi.PostUpdateEventListener; * @author <a href="mailto:mtaal@elver.org">Martin Taal</a> */ public class AuditProcessHandler implements AfterTransactionCompletionProcess, - BeforeTransactionCompletionProcess, PostDeleteEventListener, PostInsertEventListener, - PostUpdateEventListener, ExtensionPoint { + BeforeTransactionCompletionProcess, FlushEventListener, PostDeleteEventListener, + PostInsertEventListener, PostUpdateEventListener, ExtensionPoint { + + /** The logger */ + private static Log log = LogFactory.getLog(AuditProcessHandler.class); public static final long DEFAULT_END_TIMESTAMP = -1; + public static final long HIGH_NUMBER = 1000000; + private static ThreadLocal<String> currentUserName = new ThreadLocal<String>(); private static ThreadLocal<String> currentComment = new ThreadLocal<String>(); @@ -87,7 +97,7 @@ public class AuditProcessHandler implements AfterTransactionCompletionProcess, private long pruneTime = 0; private long pruneInterval = 1000; private List<String> auditEntityNames = null; - + private ThreadLocal<Boolean> inAuditWorkInSession = new ThreadLocal<Boolean>(); private AuditHandler auditHandler = null; private void addToAuditWorkQueue(EventSource session, TeneoAuditKind auditKind, Object entity) { @@ -126,7 +136,7 @@ public class AuditProcessHandler implements AfterTransactionCompletionProcess, auditWorks = new ArrayList<AuditProcessHandler.AuditWork>(); workQueue.put(session.getTransaction(), auditWorks); - // let the handler be called at the end of the transaction + // let the handler also be called at the end of the transaction // to do the transaction work session.getActionQueue().registerProcess((AfterTransactionCompletionProcess) this); session.getActionQueue().registerProcess((BeforeTransactionCompletionProcess) this); @@ -141,6 +151,13 @@ public class AuditProcessHandler implements AfterTransactionCompletionProcess, } } + // note that if there is an existing audit work it can mean + // that the dirty checking on an entity is broken. + // this because the flush in doAuditWorkInSession will cause + // again a dirty check/flag + // but hibernate is quite lenient with this. + // the consequence is holes in the version numbers + // check if there is already an add if (existingAuditWork != null && (existingAuditWork.getAuditKind() == TeneoAuditKind.ADD && auditWork.getAuditKind() == TeneoAuditKind.UPDATE)) { @@ -154,7 +171,10 @@ public class AuditProcessHandler implements AfterTransactionCompletionProcess, // happens if the id has been set manually auditWorks.remove(existingAuditWork); } else { - auditWorks.remove(existingAuditWork); + + if (existingAuditWork != null) { + auditWorks.remove(existingAuditWork); + } // and add the new one auditWorks.add(auditWork); @@ -190,6 +210,31 @@ public class AuditProcessHandler implements AfterTransactionCompletionProcess, } @Override + public void onFlush(FlushEvent event) throws HibernateException { + if (inAuditWorkInSession.get() != null && inAuditWorkInSession.get()) { + // audit work in session does flush + if (workQueue.get(event.getSession().getTransaction()) != null + && !workQueue.get(event.getSession().getTransaction()).isEmpty()) { + final StringBuilder sb = new StringBuilder(); + for (AuditWork auditWork : workQueue.get(event.getSession().getTransaction())) { + sb.append("\n" + auditWork); + } + // if this if is true then probably dirty checking is not correctly done + log.error("The audit work handling resulted in additional audit entries, " + + "this points to an error in dirty checking of properties (false dirties), audit entries: " + + sb); + } + return; + } + + final List<AuditWork> auditWorks = getRemoveQueue((Session) event.getSession(), false); + if (auditWorks == null || auditWorks.isEmpty()) { + return; + } + doAuditWorkInSession((Session) event.getSession(), auditWorks); + } + + @Override public void doAfterTransactionCompletion(boolean success, SessionImplementor session) { if (!success) { return; @@ -229,105 +274,126 @@ public class AuditProcessHandler implements AfterTransactionCompletionProcess, return System.currentTimeMillis(); } - protected void doAuditWorkInSession(Session session, List<AuditWork> auditWorks) { - final long commitTime = getCommitTime(); + protected synchronized void doAuditWorkInSession(Session session, List<AuditWork> auditWorks) { + inAuditWorkInSession.set(true); + try { + final long commitTime = getCommitTime(); - final List<Object> toSaveEntries = new ArrayList<Object>(); + final List<Object> toSaveEntries = new ArrayList<Object>(); - final TeneoAuditCommitInfo commitInfo = TeneoauditingFactory.eINSTANCE - .createTeneoAuditCommitInfo(); + final TeneoAuditCommitInfo commitInfo = TeneoauditingFactory.eINSTANCE + .createTeneoAuditCommitInfo(); - if (currentUserName.get() != null) { - commitInfo.setUser(currentUserName.get()); - } + if (currentUserName.get() != null) { + commitInfo.setUser(currentUserName.get()); + } - commitInfo.setCommitTime(commitTime); + commitInfo.setCommitTime(commitTime); - if (currentComment.get() != null) { - if (currentComment.get().length() > 2000) { - commitInfo.setComment(currentComment.get().substring(0, 2000)); - } else { - commitInfo.setComment(currentComment.get()); + if (currentComment.get() != null) { + if (currentComment.get().length() > 2000) { + commitInfo.setComment(currentComment.get().substring(0, 2000)); + } else { + commitInfo.setComment(currentComment.get()); + } } - } - toSaveEntries.add(commitInfo); + toSaveEntries.add(commitInfo); + + EClass lastEClass = null; + EClass auditEntryEClass = null; + for (AuditWork auditWork : auditWorks) { + final Object object = auditWork.getEntity(); + final EClass eClass = auditHandler.getEClass(object); + if (lastEClass != eClass) { + auditEntryEClass = auditHandler.getAuditingModelElement(eClass); + lastEClass = eClass; + } + final String auditEntryEntityName = HbUtil.getEntityName(auditEntryEClass); + + // create the auditEntry + final TeneoAuditEntry auditEntry = (TeneoAuditEntry) auditEntryEClass.getEPackage() + .getEFactoryInstance().create(auditEntryEClass); + auditEntry.setTeneo_audit_kind(auditWork.getAuditKind()); + auditEntry.setTeneo_commit_info(commitInfo); + auditEntry.setTeneo_end(DEFAULT_END_TIMESTAMP); + auditEntry.setTeneo_start(commitTime); + auditEntry + .setTeneo_object_id(auditHandler.entityToIdString(session, auditWork.getEntity())); + auditEntry.setTeneo_object_version(auditWork.getVersion()); + + setContainerInfo(session, auditEntry, auditWork.getEntity()); + + auditHandler.copyContentToAuditEntry(session, auditWork.getEntity(), auditEntry, + auditWork.getAuditKind() != TeneoAuditKind.DELETE); + + // note also do a query in case of ADD as an object can + // be removed and then re-added, restore its link + // to the history then + // get info from the previous entry + // Note: apparently hibernate has a query plan cache, so + // it does not give a performance benefit to use a namedquery + // at least not that much.. + // http://stackoverflow.com/questions/3578711/jpa-caching-queries + final Query infoQuery = session + .createQuery("select teneo_start, teneo_object_version, teneo_audit_kind from " + + auditEntryEntityName + " e where teneo_object_id=:objectId and teneo_end=" + + AuditProcessHandler.DEFAULT_END_TIMESTAMP); + infoQuery.setMaxResults(1); + infoQuery.setString("objectId", auditEntry.getTeneo_object_id()); + + @SuppressWarnings("unchecked") + final List<Object> list = infoQuery.list(); + if (!list.isEmpty()) { + final Object[] values = (Object[]) list.get(0); + final Long startTime = (Long) values[0]; + final Long version = (Long) values[1]; + + // the HIGH_NUMBER check is a pragmatic way ignore versioning using timestamps + if (performVersionCheck() && version > 0 && auditWork.getVersion() > 0 + && auditWork.getVersion() < HIGH_NUMBER && auditWork.getVersion() != (version + 1)) { + throw new IllegalStateException( + "Version numbers should incrsement by 1, previous version: " + version + + " new version " + auditWork.getVersion()); + } + + auditEntry.setTeneo_previous_start(startTime); + + updateEndTime(session, auditEntryEntityName, auditEntry.getTeneo_object_id(), + commitTime - 1, false); + updateEndTimeDerivedObjects(session, auditEntryEClass, auditEntry.getTeneo_object_id(), + commitTime - 1); + } + // save later to not flush in the loop + toSaveEntries.add(auditEntry); + setCommitInfoInReferencedObjects(auditEntry, toSaveEntries); + } + // do flush here to ensure that the update is done before + // the save, this to prevent unique constraint failures + session.flush(); - EClass lastEClass = null; - EClass auditEntryEClass = null; - for (AuditWork auditWork : auditWorks) { - final Object object = auditWork.getEntity(); - final EClass eClass = auditHandler.getEClass(object); - if (lastEClass != eClass) { - auditEntryEClass = auditHandler.getAuditingModelElement(eClass); - lastEClass = eClass; + for (Object o : toSaveEntries) { + session.save(HbUtil.getEntityName(auditHandler.getEClass(o)), o); } - final String auditEntryEntityName = HbUtil.getEntityName(auditEntryEClass); - - // create the auditEntry - final TeneoAuditEntry auditEntry = (TeneoAuditEntry) auditEntryEClass.getEPackage() - .getEFactoryInstance().create(auditEntryEClass); - auditEntry.setTeneo_audit_kind(auditWork.getAuditKind()); - auditEntry.setTeneo_commit_info(commitInfo); - auditEntry.setTeneo_end(DEFAULT_END_TIMESTAMP); - auditEntry.setTeneo_start(commitTime); - auditEntry.setTeneo_object_id(auditHandler.entityToIdString(session, auditWork.getEntity())); - auditEntry.setTeneo_object_version(auditWork.getVersion()); - - setContainerInfo(session, auditEntry, auditWork.getEntity()); - - auditHandler.copyContentToAuditEntry(session, auditWork.getEntity(), auditEntry, - auditWork.getAuditKind() != TeneoAuditKind.DELETE); - - // note also do a query in case of ADD as an object can - // be removed and then re-added, restore its link - // to the history then - // get info from the previous entry - // Note: apparently hibernate has a query plan cache, so - // it does not give a performance benefit to use a namedquery - // at least not that much.. - // http://stackoverflow.com/questions/3578711/jpa-caching-queries - final Query infoQuery = session.createQuery("select teneo_start from " + auditEntryEntityName - + " e where teneo_object_id=:objectId and teneo_end=" - + AuditProcessHandler.DEFAULT_END_TIMESTAMP); - infoQuery.setMaxResults(1); - infoQuery.setString("objectId", auditEntry.getTeneo_object_id()); - - @SuppressWarnings("unchecked") - final List<Object> list = infoQuery.list(); - if (!list.isEmpty()) { - final Long startTime = (Long) list.get(0); - - auditEntry.setTeneo_previous_start(startTime); - - updateEndTime(session, auditEntryEntityName, auditEntry.getTeneo_object_id(), - commitTime - 1, false); - updateEndTimeDerivedObjects(session, auditEntryEClass, auditEntry.getTeneo_object_id(), - commitTime - 1); + // and flush the saved stuff + session.flush(); + + // clear the cache from these audit objects + for (Object object : toSaveEntries) { + session.evict(object); } - // save later to not flush in the loop - toSaveEntries.add(auditEntry); - setCommitInfoInReferencedObjects(auditEntry, toSaveEntries); - } - // do flush here to ensure that the update is done before - // the save, this to prevent unique constraint failures - session.flush(); - for (Object o : toSaveEntries) { - session.save(HbUtil.getEntityName(auditHandler.getEClass(o)), o); - } - // and flush the saved stuff - session.flush(); + // clear the work queue + getRemoveQueue(session, false); - // clear the cache from these audit objects - for (Object object : toSaveEntries) { - session.evict(object); + pruneCounter++; + } finally { + inAuditWorkInSession.set(null); } + } - // clear the work queue - getRemoveQueue(session, false); - - pruneCounter++; + protected boolean performVersionCheck() { + return false; } protected void setContainerInfo(Session session, TeneoAuditEntry auditEntry, Object entity) { @@ -516,6 +582,12 @@ public class AuditProcessHandler implements AfterTransactionCompletionProcess, this.version = version; } + @Override + public String toString() { + // TODO Auto-generated method stub + return "Audit kind " + auditKind + " Entity " + entity + " version " + version; + } + } /** |