#88 - v4 - Breaking API Change - BeanPersistRequest.getOldValues() replaced with ... Map<String,ValuePair> getUpdatedValues()

This commit is contained in:
Rob Bygrave
2014-04-20 22:52:02 +12:00
parent fc628d336d
commit 2344f57f84
13 changed files with 377 additions and 86 deletions
@@ -1,6 +1,7 @@
package com.avaje.ebean;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
/**
@@ -46,6 +47,11 @@ public interface BeanState {
*/
public Set<String> getChangedProps();
/**
* Return a map of the updated properties and their new and old values.
*/
public Map<String,ValuePair> getDirtyValues();
/**
* Return true if the bean is readOnly.
* <p>
+26 -10
View File
@@ -5,30 +5,46 @@ package com.avaje.ebean;
*/
public class ValuePair {
final Object value1;
private final Object newValue;
final Object value2;
private final Object oldValue;
public ValuePair(Object value1, Object value2) {
this.value1 = value1;
this.value2 = value2;
public ValuePair(Object newValue, Object oldValue) {
this.newValue = newValue;
this.oldValue = oldValue;
}
/**
* Return the first value.
* Return the new value.
*/
public Object getNewValue() {
return newValue;
}
/**
* Return the old value.
*/
public Object getOldValue() {
return oldValue;
}
/**
* Return the new value.
*/
@Deprecated
public Object getValue1() {
return value1;
return newValue;
}
/**
* Return the second value.
* Return the old value.
*/
@Deprecated
public Object getValue2() {
return value2;
return oldValue;
}
public String toString() {
return value1 + "," + value2;
return newValue + "," + oldValue;
}
}
@@ -6,13 +6,16 @@ import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.ValuePair;
/**
* This is the object added to every entity bean using byte code enhancement.
@@ -82,6 +85,13 @@ public final class EntityBeanIntercept implements Serializable {
* Set of changed properties.
*/
private boolean[] changedProps;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distingush
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private boolean[] embeddedDirty;
private Object[] origValues;
@@ -227,9 +237,12 @@ public final class EntityBeanIntercept implements Serializable {
return dirty;
}
/**
* Called by an embedded bean onto its owner.
*/
public void setEmbeddedDirty(int embeddedProperty) {
this.dirty = true;
setChangedProperty(embeddedProperty);
setEmbeddedPropertyDirty(embeddedProperty);
}
public void setDirty(boolean dirty) {
@@ -382,6 +395,30 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Return the original value that was changed via an update.
*/
public Object getOrigValue(int propertyIndex) {
if (origValues == null) {
return null;
}
return origValues[propertyIndex];
}
/**
* Finds the index position of a given property. Returns -1 if the property
* can not be found.
*/
public int findProperty(String propertyName) {
String[] names = owner._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(propertyName)) {
return i;
}
}
return -1;
}
public String getProperty(int propertyIndex) {
if (propertyIndex == -1) {
return null;
@@ -405,6 +442,15 @@ public final class EntityBeanIntercept implements Serializable {
return (changedProps != null && changedProps[propertyIndex]);
}
/**
* Return true if the property was changed or if it is embedded and one of its
* embedded properties is dirty.
*/
public boolean isDirtyProperty(int propertyIndex) {
return (changedProps != null && changedProps[propertyIndex]
|| embeddedDirty != null && embeddedDirty[propertyIndex]);
}
/**
* Explicitly mark a property as having been changed.
*/
@@ -419,6 +465,16 @@ public final class EntityBeanIntercept implements Serializable {
}
changedProps[propertyIndex] = true;
}
/**
* Set that an embedded bean has had one of its properties changed.
*/
private void setEmbeddedPropertyDirty(int propertyIndex) {
if (embeddedDirty == null) {
embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
}
embeddedDirty[propertyIndex] = true;
}
private void setOriginalValue(int propertyIndex, Object value) {
if (origValues == null) {
@@ -457,29 +513,88 @@ public final class EntityBeanIntercept implements Serializable {
}
return props;
}
public Set<String> getChangedPropertyNames() {
/**
* Return the set of dirty properties.
*/
public Set<String> getDirtyPropertyNames() {
Set<String> props = new LinkedHashSet<String>();
if (changedProps != null) {
for (int i=0; i<changedProps.length; i++) {
if (changedProps[i]) {
props.add(getProperty(i));
}
}
}
addDirtyPropertyNames(props, null);
return props;
}
public int getChangedPropertiesHash() {
int h = 1;
if (changedProps != null) {
for (int i=0; i<changedProps.length; i++) {
if (changedProps[i]) {
h = h * 31 + (i+1);
}
/**
* Recursively add dirty properties.
*/
public void addDirtyPropertyNames(Set<String> props, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
props.add(propName);
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i)+".");
}
}
return h;
}
/**
* Return a map of dirty properties with their new and old values.
*/
public Map<String,ValuePair> getDirtyValues() {
Map<String,ValuePair> dirtyValues = new LinkedHashMap<String, ValuePair>();
addDirtyPropertyValues(dirtyValues, null);
return dirtyValues;
}
/**
* Recursively add dirty properties.
*/
public void addDirtyPropertyValues(Map<String,ValuePair> dirtyValues, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i)+".");
}
}
}
/**
* Return a dirty property hash taking into account embedded beans.
*/
public int getDirtyPropertyHash() {
return addDirtyPropertyHash(37);
}
/**
* Add and return a dirty property hash recursing into embedded beans.
*/
public int addDirtyPropertyHash(int hash) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
// the property has been changed on this bean
hash = hash * 31 + (i+1);
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
hash = hash * 31 + embeddedBean._ebean_getIntercept().addDirtyPropertyHash(hash);
}
}
return hash;
}
/**
@@ -693,7 +808,7 @@ public final class EntityBeanIntercept implements Serializable {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!areEqual(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, newValue);
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
@@ -2,6 +2,8 @@ package com.avaje.ebean.event;
import java.util.Set;
import com.avaje.ebean.config.ServerConfig;
/**
* Listens for committed bean events.
* <p>
@@ -31,8 +33,9 @@ import java.util.Set;
* </p>
* <p>
* A BeanPersistListener is either found automatically via class path search or
* can be added programmatically via ServerConfiguration.addEntity().
* can be added programmatically via {@link ServerConfig#add(BeanPersistListener)}.
* </p>
* @see ServerConfig#add(BeanPersistListener)
*/
public interface BeanPersistListener<T> {
@@ -52,9 +55,9 @@ public interface BeanPersistListener<T> {
* @param bean
* The bean that was updated.
* @param updatedProperties
* the properties on the bean that where updated
* The properties that were modified by this update.
*/
public boolean updated(T bean);//, Set<String> updatedProperties);
public boolean updated(T bean, Set<String> updatedProperties);
/**
* Notified that a bean has been deleted locally. Return true if you want the
@@ -1,7 +1,11 @@
package com.avaje.ebean.event;
import java.util.Map;
import java.util.Set;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.ValuePair;
/**
* Holds the information available for a bean persist (insert, update or
@@ -22,29 +26,25 @@ public interface BeanPersistRequest<T> {
*/
public Transaction getTransaction();
// /**
// * For an update or delete of a partially populated bean this is the set of
// * loaded properties and otherwise returns null.
// */
// public Set<String> getLoadedProperties();
//
// /**
// * For an update this is the set of properties that where updated.
// */
// public Set<String> getUpdatedProperties();
/**
* For an update or delete of a partially populated bean this is the set of
* loaded properties and otherwise returns null.
*/
public Set<String> getLoadedProperties();
/**
* For an update this is the set of properties that where updated.
*/
public Set<String> getUpdatedProperties();
/**
* Returns the bean being inserted updated or deleted.
*/
public T getBean();
// /**
// * Returns a bean containing the original values prior to the bean being
// * modified.
// * <p>
// * This is for updates only.
// * </p>
// */
// public T getOldValues();
/**
* Returns a map of the properties that have changed and their new and old values.
*/
public Map<String,ValuePair> getUpdatedValues();
}
@@ -1,9 +1,11 @@
package com.avaje.ebeaninternal.server.core;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
import com.avaje.ebean.BeanState;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
@@ -12,9 +14,9 @@ import com.avaje.ebean.bean.EntityBeanIntercept;
*/
public class DefaultBeanState implements BeanState {
final EntityBean entityBean;
private final EntityBean entityBean;
final EntityBeanIntercept intercept;
private final EntityBeanIntercept intercept;
public DefaultBeanState(EntityBean entityBean){
this.entityBean = entityBean;
@@ -42,7 +44,11 @@ public class DefaultBeanState implements BeanState {
}
public Set<String> getChangedProps() {
return intercept.getChangedPropertyNames();
return intercept.getDirtyPropertyNames();
}
public Map<String,ValuePair> getDirtyValues() {
return intercept.getDirtyValues();
}
public boolean isReadOnly() {
@@ -69,5 +75,4 @@ public class DefaultBeanState implements BeanState {
intercept.setReference();
}
}
@@ -5,7 +5,9 @@ import java.util.Map;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
/**
@@ -30,17 +32,16 @@ public class DiffHelp {
*/
public Map<String, ValuePair> diff(Object a, Object b, BeanDescriptor<?> desc) {
boolean oldValues = false;
Map<String, ValuePair> map = new LinkedHashMap<String, ValuePair>();
if (b == null) {
// get the old values from a
if (a instanceof EntityBean) {
EntityBean eb = (EntityBean) a;
b = null;//FIXME eb._ebean_getIntercept().getOldValues();
oldValues = true;
return ((EntityBean) a)._ebean_getIntercept().getDirtyValues();
}
return map;
}
Map<String, ValuePair> map = new LinkedHashMap<String, ValuePair>();
// if (b == null) {
// return map;
@@ -70,8 +71,7 @@ public class DiffHelp {
* determined to be different as is added to the map.
* </p>
*/
private void diffEmbedded(Object a, Object b, BeanDescriptor<?> desc, Map<String, ValuePair> map,
boolean oldValues) {
private void diffEmbedded(Object a, Object b, BeanDescriptor<?> desc, Map<String, ValuePair> map, boolean oldValues) {
// BeanPropertyAssocOne<?>[] emb = desc.propertiesEmbedded();
//
@@ -2,9 +2,12 @@ package com.avaje.ebeaninternal.server.core;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.OptimisticLockException;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
@@ -79,6 +82,8 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
private boolean statelessUpdate;
private boolean deleteMissingChildren;
private boolean updateNullProperties;
private final Set<String> dirtyPropertyNames;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr,
SpiTransaction t, PersistExecute persistExecute) {
@@ -89,6 +94,8 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
this.beanManager = mgr;
this.beanDescriptor = mgr.getBeanDescriptor();
this.beanPersistListener = beanDescriptor.getPersistListener();
this.dirtyPropertyNames = (beanPersistListener == null) ? null : intercept.getDirtyPropertyNames();
this.bean = bean;
this.parentBean = parentBean;
@@ -101,9 +108,23 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
// this is ok to not use isNewOrDirty() as used for updates only
this.isDirty = intercept.isDirty();
}
public void setNotNullAsLoaded() {
@Override
public Set<String> getLoadedProperties() {
return intercept.getLoadedPropertyNames();
}
@Override
public Set<String> getUpdatedProperties() {
return intercept.getDirtyPropertyNames();
}
@Override
public Map<String, ValuePair> getUpdatedValues() {
return intercept.getDirtyValues();
}
public void setNotNullAsLoaded() {
BeanProperty[] props = beanDescriptor.propertiesNonMany();
for (int i=0; i< props.length; i++) {
BeanProperty prop = props[i];
@@ -163,7 +184,7 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
return beanPersistListener.inserted(bean);
case UPDATE:
return beanPersistListener.updated(bean);//, getUpdatedProperties());
return beanPersistListener.updated(bean, dirtyPropertyNames);
case DELETE:
return beanPersistListener.deleted(bean);
@@ -558,7 +579,7 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
* update.
*/
public boolean isAddToUpdate(BeanProperty prop) {
return intercept.isChangedProperty(prop.getPropertyIndex());
return intercept.isDirtyProperty(prop.getPropertyIndex());
}
public List<DerivedRelationshipData> getDerivedRelationships() {
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.avaje.ebean.event.BeanPersistListener;
@@ -11,6 +12,7 @@ import com.avaje.ebean.event.BeanPersistListener;
public class ChainedBeanPersistListener<T> implements BeanPersistListener<T> {
private final List<BeanPersistListener<T>> list;
private final BeanPersistListener<T>[] chain;
/**
@@ -109,10 +111,10 @@ public class ChainedBeanPersistListener<T> implements BeanPersistListener<T> {
}
}
public boolean updated(T bean) {
public boolean updated(T bean, Set<String> updatedProperties) {
boolean notifyCluster = false;
for (int i = 0; i < chain.length; i++) {
if (chain[i].updated(bean)) {
if (chain[i].updated(bean, updatedProperties)) {
notifyCluster = true;
}
}
@@ -59,7 +59,7 @@ public class GenerateDmlRequest {
return true;
}
if (changesOnly) {
return ebi.isChangedProperty(prop.getPropertyIndex());
return ebi.isDirtyProperty(prop.getPropertyIndex());
} else {
return ebi.isLoadedProperty(prop.getPropertyIndex());
}
@@ -11,7 +11,6 @@ import com.avaje.ebeaninternal.api.SpiUpdatePlan;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable;
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId;
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList;
@@ -115,20 +114,10 @@ public final class UpdateMeta {
// we can use a cached UpdatePlan for the changed properties
EntityBeanIntercept ebi = persistRequest.getEntityBeanIntercept();
int hash = ebi.getChangedPropertiesHash();
int hash = ebi.getDirtyPropertyHash();
BeanDescriptor<?> beanDescriptor = persistRequest.getBeanDescriptor();
BeanPropertyAssocOne<?>[] propertiesEmbedded = beanDescriptor.propertiesEmbedded();
for (int i=0; i< propertiesEmbedded.length; i++) {
EntityBean embeddedBean = (EntityBean)propertiesEmbedded[i].getValue(persistRequest.getEntityBean());
if (embeddedBean == null) {
hash = hash * 31;
} else {
hash = hash * 31 + embeddedBean._ebean_getIntercept().getChangedPropertiesHash();
}
}
BeanProperty versionProperty = beanDescriptor.getVersionProperty();
if (versionProperty != null) {
if (ebi.isLoadedProperty(versionProperty.getPropertyIndex())) {