/** * 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.ArrayList; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ArrayUtils; import org.hibernate.EmptyInterceptor; import org.hibernate.type.Type; import org.openmrs.Retireable; import org.openmrs.Voidable; import org.openmrs.api.UnchangeableObjectException; import org.openmrs.util.OpenmrsUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Superclass for all Interceptors that would like to ensure that changes to immutable entities of * specific types don't get persisted to the database, more granularity of the immutable properties * is also supported so as to allow editing some properties while not for others * * <pre> * <b>NOTE:</b> Subclasses MUST not make any changes to the persistent object because they get * called last, if they make any changes other interceptors would never know about them. * </pre> * * @since 1.10 */ public abstract class ImmutableEntityInterceptor extends EmptyInterceptor { private static final Logger log = LoggerFactory.getLogger(ImmutableEntityInterceptor.class); /** * Returns the class handled by the interceptor */ protected abstract Class<?> getSupportedType(); /** * Subclasses can override this to return fields that are allowed to be edited, returning null * or an empty array implies the entity is immutable * * @return an array of properties */ protected String[] getMutablePropertyNames() { return null; } /** * Subclasses can override this to specify whether voided or retired items are mutable * * @return true if voided or retired objects are mutable otherwise false means they are * immutable */ protected boolean ignoreVoidedOrRetiredObjects() { return false; } /** * @see org.hibernate.EmptyInterceptor#onFlushDirty(Object, java.io.Serializable, Object[], * Object[], String[], org.hibernate.type.Type[]) * @should fail if an entity has a changed property * @should pass if an entity has changes for an allowed mutable property * @should pass if the edited object is voided or retired and ignore is set to true * @should fail if the edited object is voided or retired and ignore is set to false */ @Override public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { if (getSupportedType().isAssignableFrom(entity.getClass())) { List<String> changedProperties = null; for (int i = 0; i < propertyNames.length; i++) { String property = propertyNames[i]; if (ArrayUtils.contains(getMutablePropertyNames(), property)) { continue; } boolean isVoidedOrRetired = false; if (Voidable.class.isAssignableFrom(entity.getClass())) { isVoidedOrRetired = ((Voidable) entity).getVoided(); } else if (Retireable.class.isAssignableFrom(entity.getClass())) { isVoidedOrRetired = ((Retireable) entity).getRetired(); } if (isVoidedOrRetired && ignoreVoidedOrRetiredObjects()) { continue; } Object previousValue = (previousState != null) ? previousState[i] : null; Object currentValue = (currentState != null) ? currentState[i] : null; if (!OpenmrsUtil.nullSafeEquals(currentValue, previousValue)) { if (changedProperties == null) { changedProperties = new ArrayList<String>(); } changedProperties.add(property); } } if (CollectionUtils.isNotEmpty(changedProperties)) { if (log.isDebugEnabled()) { log.debug("The following fields cannot be changed for " + getSupportedType() + ":" + changedProperties); } throw new UnchangeableObjectException("editing.fields.not.allowed", new Object[] { changedProperties, getSupportedType().getSimpleName() }); } } return false; } }