/* * eGov suite of products aim to improve the internal efficiency,transparency, * accountability and the service delivery of the government organizations. * * Copyright (C) <2015> eGovernments Foundation * * The updated version of eGov suite of products as by eGovernments Foundation * is available at http://www.egovernments.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/ or * http://www.gnu.org/licenses/gpl.html . * * In addition to the terms of the GPL license to be adhered to in using this * program, the following additional terms are to be complied with: * * 1) All versions of this program, verbatim or modified must carry this * Legal Notice. * * 2) Any misrepresentation of the origin of the material is prohibited. It * is required that all modified versions of this material be marked in * reasonable ways as different from the original version. * * 3) This license does not grant any rights to any user of the program * with regards to rights under trademark law for use of the trade names * or trademarks of eGovernments Foundation. * * In case of any queries, you can reach eGovernments Foundation at contact@egovernments.org. */ package org.egov.infra.config.persistence.event.listener; import org.egov.infra.admin.master.entity.User; import org.egov.infra.config.core.ApplicationThreadLocals; import org.egov.infra.persistence.entity.AbstractAuditable; import org.egov.infra.persistence.entity.Auditable; import org.egov.infstr.models.BaseModel; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.PreUpdateEvent; import org.hibernate.event.spi.PreUpdateEventListener; import org.hibernate.event.spi.SaveOrUpdateEvent; import org.hibernate.event.spi.SaveOrUpdateEventListener; import java.util.Date; /** * This Event listener class sets the audit properties createdBy, createdDate modifiedBy and modifiedDate. It does this by hooking * to the pre-update and pre-insert events. The pre-update event was chosen instead of save-update events for setting modified * properties to fix a bug where even for object reads the Hibernate objects were getting updated. However, the save-update event * is still required for new objects (inserts) as Hibernate checks for not-null constraints before the pre-update event is fired. * Due to auto flushing strategy that we use, every transaction commit causes a session.flush to be called. Flush calls cascade of * collections or properties which fires the save-update event if cascade value is anything other than Cascade.NONE. * * @author sahinab */ public class HibernateEventListener implements SaveOrUpdateEventListener, PreUpdateEventListener { private static final long serialVersionUID = 1L; /** * Sets the modifiedBy and modifiedDate properties on objects that inherit from {@link org.egov.infstr.models.BaseModel}. * event.getState() is used to get the list of properties for the object as these are the properties that Hibernate generates * the UPDATE statement for. A separate session is used to get the User object to ensure that the object thus obtained is * flushed from within the event. @ return false to continue the processing */ @Override public boolean onPreUpdate(final PreUpdateEvent event) { final Object entity = event.getEntity(); if (entity instanceof BaseModel) updateAuditableProperties(event); else if (entity instanceof Auditable) updateAuditable(event); return false; } /** * For new objects that are created, this event is used to set the audit properties. This is done here instead of the * pre-insert event because Hibernate checks for not-null constraints before the pre-update and pre-insert are fired. */ @Override public void onSaveOrUpdate(final SaveOrUpdateEvent event) throws HibernateException { final EventSource session = event.getSession(); final Object object = event.getObject(); if (object instanceof BaseModel && !session.getPersistenceContext().reassociateIfUninitializedProxy(object)) { // only update the entity if it has been changed final Date currentDate = new Date(); final User usr = (User) session.load(User.class, ApplicationThreadLocals.getUserId()); final BaseModel entity = (BaseModel) session.getPersistenceContext().unproxyAndReassociate(object); if (entity.getCreatedBy() == null) { entity.setCreatedDate(currentDate); entity.setCreatedBy(usr); entity.setModifiedBy(usr); entity.setModifiedDate(currentDate); } } else if (object instanceof Auditable && !session.getPersistenceContext().reassociateIfUninitializedProxy(object)) { final User usr = (User) session.load(User.class, ApplicationThreadLocals.getUserId()); final AbstractAuditable entity = (AbstractAuditable) session.getPersistenceContext().unproxyAndReassociate(object); if (entity.getCreatedBy() == null) { final Date currentDate = new Date(); entity.setCreatedDate(currentDate); entity.setCreatedBy(usr); entity.setLastModifiedBy(usr); entity.setLastModifiedDate(currentDate); } } } /** * When reading an object from within the PreInsert or PreUpdate event handlers, a different session has to be used from the * one in which the event was fired. This is to make sure that the objects loaded here are flushed. Otherwise it results in * collection was not processed by flush() Assertion failures * * @param session * @return */ private User getUserObjectFromWithinEventListener(final EventSource session) { // Since we are already in the flush logic of our current session, // get the user object from a different session final SessionFactory factory = session.getFactory(); final Session session2 = factory.openSession(); final User usr = (User) session2.load(User.class, ApplicationThreadLocals.getUserId()); session2.flush(); session2.close(); return usr; } private void updateAuditableProperties(final PreUpdateEvent event) { int i = 0; for (final String propName : event.getPersister().getPropertyNames()) { if ("modifiedDate".equals(propName)) event.getState()[i] = new Date(); if ("modifiedBy".equals(propName)) event.getState()[i] = getUserObjectFromWithinEventListener(event.getSession()); i++; } } private void updateAuditable(final PreUpdateEvent event) { int i = 0; for (final String propName : event.getPersister().getPropertyNames()) { if ("lastModifiedDate".equals(propName)) event.getState()[i] = new Date(); if ("lastModifiedBy".equals(propName)) event.getState()[i] = getUserObjectFromWithinEventListener(event.getSession()); i++; } } }