/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.api.db.hibernate;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.CallbackException;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.openmrs.Auditable;
import org.openmrs.OpenmrsObject;
import org.openmrs.api.context.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class looks for {@link OpenmrsObject} and {@link Auditable} that are being inserted into the
* database. The creator and dateCreated fields are set when inserting or updating objects and the
* fields are still null. If the class is an update (instead of an insert) then the changedBy and
* dateChanged fields are set to the current user and the current time. <br>
* <br>
* This class replaces the logic that was in the AuditableSaveHandler. It is here so that the
* cascading does NOT happen for dateChanged/changedBy to child OpenmrsObjects (because all handlers
* recurse on lists of OpenmrsObjects.
*
* @since 1.9
*/
public class AuditableInterceptor extends EmptyInterceptor {
private static final Logger log = LoggerFactory.getLogger(AuditableInterceptor.class);
private static final long serialVersionUID = 1L;
/**
* This method is only called when inserting new objects.
* @should return true if dateCreated was null
* @should return true if creator was null
* @should return false if dateCreated and creator was not null
* @should be called when saving OpenmrsObject
* @return true if the object got the dateCreated and creator fields set
* @see org.hibernate.EmptyInterceptor#onSave(java.lang.Object, java.io.Serializable,
* java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
*/
@Override
public boolean onSave(Object entity, Serializable id, Object[] entityCurrentState, String[] propertyNames, Type[] types) {
return setCreatorAndDateCreatedIfNull(entity, entityCurrentState, propertyNames);
}
/**
* This class method is only called when flushing an updated dirty object, not inserting objects
*
* @return true if the object got the changedBy and dateChanged fields set
* @should set the dateChanged field
* @should set the changedBy field
* @should be called when saving an Auditable
* @should not enter into recursion on entity
* @see org.hibernate.EmptyInterceptor#onFlushDirty(java.lang.Object, java.io.Serializable,
* java.lang.Object[], java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
*/
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) throws CallbackException {
boolean objectWasChanged;
objectWasChanged = setCreatorAndDateCreatedIfNull(entity, currentState, propertyNames);
if (entity instanceof Auditable && propertyNames != null) {
if (log.isDebugEnabled()) {
log.debug("Setting changed by fields on " + entity.getClass());
}
Map<String, Object> propertyValues = getPropertyValuesToUpdate();
objectWasChanged = changeProperties(currentState, propertyNames, objectWasChanged, propertyValues, false);
}
return objectWasChanged;
}
/**
* Sets the creator and dateCreated fields to the current user and the current time if they are
* null.
* if is a Person Object, sets the personCreator and personDateCreated fields to the current user and the current time
* if they are null.
*
* @param entity
* @param currentState
* @param propertyNames
* @return true if creator and dateCreated were changed
*/
private boolean setCreatorAndDateCreatedIfNull(Object entity, Object[] currentState, String[] propertyNames) {
boolean objectWasChanged = false;
if (entity instanceof OpenmrsObject) {
if (log.isDebugEnabled()) {
log.debug("Setting creator and dateCreated on " + entity);
}
Map<String, Object> propertyValues = getPropertyValuesToSave();
objectWasChanged = changeProperties(currentState, propertyNames, objectWasChanged, propertyValues, true);
}
return objectWasChanged;
}
private boolean changeProperties(Object[] currentState, String[] propertyNames, boolean objectWasChanged,
Map<String, Object> propertyValues, Boolean setNullOnly) {
for (String property : propertyValues.keySet()) {
if (changePropertyValue(currentState, propertyNames, property, propertyValues.get(property), setNullOnly)) {
objectWasChanged = true;
}
}
return objectWasChanged;
}
private Map<String, Object> getPropertyValuesToSave() {
Map<String, Object> propertyValues = new HashMap<String, Object>();
propertyValues.put("creator", Context.getAuthenticatedUser());
propertyValues.put("dateCreated", new Date());
propertyValues.put("personCreator", Context.getAuthenticatedUser());
propertyValues.put("personDateCreated", new Date());
return propertyValues;
}
private Map<String, Object> getPropertyValuesToUpdate() {
Map<String, Object> propertyValues = new HashMap<String, Object>();
propertyValues.put("changedBy", Context.getAuthenticatedUser());
propertyValues.put("dateChanged", new Date());
propertyValues.put("personChangedBy", Context.getAuthenticatedUser());
propertyValues.put("personDateChanged", new Date());
return propertyValues;
}
/**
* Sets the property to the given value.
*
* @param currentState
* @param propertyNames
* @param propertyToSet
* @param value
* @param setNullOnly
* @return true if the property was changed
*/
private boolean changePropertyValue(Object[] currentState, String[] propertyNames, String propertyToSet, Object value,
boolean setNullOnly) {
int index = Arrays.asList(propertyNames).indexOf(propertyToSet);
if (value == null) {
return false;
}
if (index >= 0 && (currentState[index] == null || !setNullOnly) && !value.equals(currentState[index])) {
currentState[index] = value;
return true;
}
return false;
}
}