mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
* Bug in Changelog: It takes the first non null value as oldValue and does not detect if oldValue == newValue * CHANGE: EntityBeanIntercept uses bitflags instead of separate boolean arrays - new flag 'origValueSet' introduced - getDirtyValues checks, if values are really different.
1119 lines
29 KiB
Java
1119 lines
29 KiB
Java
package io.ebean.bean;
|
|
|
|
import io.ebean.Ebean;
|
|
import io.ebean.ValuePair;
|
|
|
|
import javax.persistence.EntityNotFoundException;
|
|
import javax.persistence.PersistenceException;
|
|
import java.io.Serializable;
|
|
import java.math.BigDecimal;
|
|
import java.net.URL;
|
|
import java.util.Arrays;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* This is the object added to every entity bean using byte code enhancement.
|
|
* <p>
|
|
* This provides the mechanisms to support deferred fetching of reference beans
|
|
* and oldValues generation for concurrency checking.
|
|
* </p>
|
|
*/
|
|
public final class EntityBeanIntercept implements Serializable {
|
|
|
|
private static final long serialVersionUID = -3664031775464862649L;
|
|
|
|
private static final int STATE_NEW = 0;
|
|
private static final int STATE_REFERENCE = 1;
|
|
private static final int STATE_LOADED = 2;
|
|
|
|
private transient NodeUsageCollector nodeUsageCollector;
|
|
|
|
private transient PersistenceContext persistenceContext;
|
|
|
|
private transient BeanLoader beanLoader;
|
|
|
|
private transient PreGetterCallback preGetterCallback;
|
|
|
|
private String ebeanServerName;
|
|
|
|
/**
|
|
* The actual entity bean that 'owns' this intercept.
|
|
*/
|
|
private final EntityBean owner;
|
|
|
|
private EntityBean embeddedOwner;
|
|
private int embeddedOwnerIndex;
|
|
|
|
/**
|
|
* One of NEW, REF, UPD.
|
|
*/
|
|
private int state;
|
|
|
|
private boolean forceUpdate;
|
|
|
|
private boolean readOnly;
|
|
|
|
private boolean dirty;
|
|
|
|
/**
|
|
* Flag set to disable lazy loading - typically for SQL "report" type entity beans.
|
|
*/
|
|
private boolean disableLazyLoad;
|
|
|
|
/**
|
|
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
|
|
*/
|
|
private boolean lazyLoadFailure;
|
|
|
|
/**
|
|
* Used when a bean is partially filled.
|
|
*/
|
|
private static final byte FLAG_LOADED_PROP = 1;
|
|
|
|
/**
|
|
* Set of changed properties.
|
|
*/
|
|
private static final byte FLAG_CHANGED_PROP = 2;
|
|
|
|
/**
|
|
* 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 static final byte FLAG_EMBEDDED_DIRTY = 4;
|
|
|
|
/**
|
|
* 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 static final byte FLAG_ORIG_VALUE_SET = 8;
|
|
|
|
private final byte[] flags;
|
|
|
|
private boolean fullyLoadedBean;
|
|
|
|
private Object[] origValues;
|
|
|
|
private int lazyLoadProperty = -1;
|
|
|
|
private Object ownerId;
|
|
private int sortOrder;
|
|
|
|
/**
|
|
* Create a intercept with a given entity.
|
|
* <p>
|
|
* Refer to agent ProxyConstructor.
|
|
* </p>
|
|
*/
|
|
public EntityBeanIntercept(Object ownerBean) {
|
|
this.owner = (EntityBean) ownerBean;
|
|
this.flags = new byte[owner._ebean_getPropertyNames().length];
|
|
}
|
|
|
|
/**
|
|
* Return the 'owning' entity bean.
|
|
*/
|
|
public EntityBean getOwner() {
|
|
return owner;
|
|
}
|
|
|
|
/**
|
|
* Return the persistenceContext.
|
|
*/
|
|
public PersistenceContext getPersistenceContext() {
|
|
return persistenceContext;
|
|
}
|
|
|
|
/**
|
|
* Set the persistenceContext.
|
|
*/
|
|
public void setPersistenceContext(PersistenceContext persistenceContext) {
|
|
this.persistenceContext = persistenceContext;
|
|
}
|
|
|
|
/**
|
|
* Turn on profile collection.
|
|
*/
|
|
public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
|
|
this.nodeUsageCollector = usageCollector;
|
|
}
|
|
|
|
/**
|
|
* Return the ownerId (IdClass).
|
|
*/
|
|
public Object getOwnerId() {
|
|
return ownerId;
|
|
}
|
|
|
|
/**
|
|
* Set the ownerId (IdClass).
|
|
*/
|
|
public void setOwnerId(Object ownerId) {
|
|
this.ownerId = ownerId;
|
|
}
|
|
|
|
/**
|
|
* Return the owning bean for an embedded bean.
|
|
*/
|
|
public Object getEmbeddedOwner() {
|
|
return embeddedOwner;
|
|
}
|
|
|
|
/**
|
|
* Return the property index (for the parent) of this embedded bean.
|
|
*/
|
|
public int getEmbeddedOwnerIndex() {
|
|
return embeddedOwnerIndex;
|
|
}
|
|
|
|
/**
|
|
* Clear the getter callback.
|
|
*/
|
|
public void clearGetterCallback() {
|
|
this.preGetterCallback = null;
|
|
}
|
|
|
|
/**
|
|
* Register the callback to be triggered when getter is called.
|
|
* This is used primarily to automatically flush the JDBC batch.
|
|
*/
|
|
public void registerGetterCallback(PreGetterCallback getterCallback) {
|
|
this.preGetterCallback = getterCallback;
|
|
}
|
|
|
|
/**
|
|
* Set the embedded beans owning bean.
|
|
*/
|
|
public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
|
|
this.embeddedOwner = parentBean;
|
|
this.embeddedOwnerIndex = embeddedOwnerIndex;
|
|
}
|
|
|
|
/**
|
|
* Set the BeanLoader with PersistenceContext.
|
|
*/
|
|
public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
|
|
this.beanLoader = beanLoader;
|
|
this.persistenceContext = ctx;
|
|
this.ebeanServerName = beanLoader.getName();
|
|
}
|
|
|
|
/**
|
|
* Set the BeanLoader.
|
|
*/
|
|
public void setBeanLoader(BeanLoader beanLoader) {
|
|
this.beanLoader = beanLoader;
|
|
this.ebeanServerName = beanLoader.getName();
|
|
}
|
|
|
|
public boolean isFullyLoadedBean() {
|
|
return fullyLoadedBean;
|
|
}
|
|
|
|
public void setFullyLoadedBean(boolean fullyLoadedBean) {
|
|
this.fullyLoadedBean = fullyLoadedBean;
|
|
}
|
|
|
|
/**
|
|
* Check each property to see if the bean is partially loaded.
|
|
*/
|
|
public boolean isPartial() {
|
|
for (byte flag : flags) {
|
|
if ((flag & FLAG_LOADED_PROP) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return true if this bean has been directly modified (it has oldValues) or
|
|
* if any embedded beans are either new or dirty (and hence need saving).
|
|
*/
|
|
public boolean isDirty() {
|
|
return dirty;
|
|
}
|
|
|
|
/**
|
|
* Called by an embedded bean onto its owner.
|
|
*/
|
|
public void setEmbeddedDirty(int embeddedProperty) {
|
|
this.dirty = true;
|
|
setEmbeddedPropertyDirty(embeddedProperty);
|
|
}
|
|
|
|
public void setDirty(boolean dirty) {
|
|
this.dirty = dirty;
|
|
}
|
|
|
|
/**
|
|
* Return true if this entity bean is new and not yet saved.
|
|
*/
|
|
public boolean isNew() {
|
|
return state == STATE_NEW;
|
|
}
|
|
|
|
/**
|
|
* Return true if the entity bean is new or dirty (and should be saved).
|
|
*/
|
|
public boolean isNewOrDirty() {
|
|
return isNew() || isDirty();
|
|
}
|
|
|
|
/**
|
|
* Return true if only the Id property has been loaded.
|
|
*/
|
|
public boolean hasIdOnly(int idIndex) {
|
|
for (int i = 0; i < flags.length; i++) {
|
|
if (i == idIndex) {
|
|
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
|
|
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return true if the entity is a reference.
|
|
*/
|
|
public boolean isReference() {
|
|
return state == STATE_REFERENCE;
|
|
}
|
|
|
|
/**
|
|
* Set this as a reference object.
|
|
*/
|
|
public void setReference(int idPos) {
|
|
state = STATE_REFERENCE;
|
|
if (idPos > -1) {
|
|
// For cases where properties are set on constructor
|
|
// set every non Id property to unloaded (for lazy loading)
|
|
for (int i = 0; i < flags.length; i++) {
|
|
if (i != idPos) {
|
|
flags[i] &= ~FLAG_LOADED_PROP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if the bean should be treated as readOnly. If a setter method
|
|
* is called when it is readOnly an Exception is thrown.
|
|
*/
|
|
public boolean isReadOnly() {
|
|
return readOnly;
|
|
}
|
|
|
|
/**
|
|
* Set the readOnly status. If readOnly then calls to setter methods through
|
|
* an exception.
|
|
*/
|
|
public void setReadOnly(boolean readOnly) {
|
|
this.readOnly = readOnly;
|
|
}
|
|
|
|
/**
|
|
* Set the bean to be updated when persisted (for merge).
|
|
*/
|
|
public void setForceUpdate(boolean forceUpdate) {
|
|
this.forceUpdate = forceUpdate;
|
|
}
|
|
|
|
/**
|
|
* Return true if the entity should be updated.
|
|
*/
|
|
public boolean isUpdate() {
|
|
return forceUpdate || state == STATE_LOADED;
|
|
}
|
|
|
|
/**
|
|
* Return true if the entity has been loaded.
|
|
*/
|
|
public boolean isLoaded() {
|
|
return state == STATE_LOADED;
|
|
}
|
|
|
|
/**
|
|
* Set the bean into NEW state.
|
|
*/
|
|
public void setNew() {
|
|
this.state = STATE_NEW;
|
|
}
|
|
|
|
/**
|
|
* Set the loaded state to true.
|
|
* <p>
|
|
* Calls to setter methods after the bean is loaded can result in 'Old Values'
|
|
* being created to support ConcurrencyMode.ALL
|
|
* </p>
|
|
* <p>
|
|
* Worth noting that this is also set after a insert/update. By doing so it
|
|
* 'resets' the bean for making further changes and saving again.
|
|
* </p>
|
|
*/
|
|
public void setLoaded() {
|
|
this.state = STATE_LOADED;
|
|
this.owner._ebean_setEmbeddedLoaded();
|
|
this.lazyLoadProperty = -1;
|
|
this.origValues = null;
|
|
for (int i = 0; i < flags.length; i++) {
|
|
flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
|
|
}
|
|
this.dirty = false;
|
|
}
|
|
|
|
/**
|
|
* When finished loading for lazy or refresh on an already partially populated
|
|
* bean.
|
|
*/
|
|
public void setLoadedLazy() {
|
|
this.state = STATE_LOADED;
|
|
this.lazyLoadProperty = -1;
|
|
}
|
|
|
|
/**
|
|
* Set lazy load failure flag.
|
|
*/
|
|
public void setLazyLoadFailure(Object ownerId) {
|
|
this.lazyLoadFailure = true;
|
|
this.ownerId = ownerId;
|
|
}
|
|
|
|
/**
|
|
* Return true if the bean is marked as having failed lazy loading.
|
|
*/
|
|
public boolean isLazyLoadFailure() {
|
|
return lazyLoadFailure;
|
|
}
|
|
|
|
/**
|
|
* Return true if lazy loading is disabled.
|
|
*/
|
|
public boolean isDisableLazyLoad() {
|
|
return disableLazyLoad;
|
|
}
|
|
|
|
/**
|
|
* Set true to turn off lazy loading.
|
|
* <p>
|
|
* Typically used to disable lazy loading on SQL based report beans.
|
|
* </p>
|
|
*/
|
|
public void setDisableLazyLoad(boolean disableLazyLoad) {
|
|
this.disableLazyLoad = disableLazyLoad;
|
|
}
|
|
|
|
/**
|
|
* Set the loaded status for the embedded bean.
|
|
*/
|
|
public void setEmbeddedLoaded(Object embeddedBean) {
|
|
if (embeddedBean instanceof EntityBean) {
|
|
EntityBean eb = (EntityBean) embeddedBean;
|
|
eb._ebean_getIntercept().setLoaded();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if the embedded bean is new or dirty and hence needs saving.
|
|
*/
|
|
public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
|
|
|
|
if (embeddedBean == null) {
|
|
// if it was previously set then the owning bean would
|
|
// have oldValues containing the previous embedded bean
|
|
return false;
|
|
}
|
|
if (embeddedBean instanceof EntityBean) {
|
|
return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty();
|
|
|
|
} else {
|
|
// non-enhanced so must assume it is new and needs to be saved
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Return the property name for the given property.
|
|
*/
|
|
public String getProperty(int propertyIndex) {
|
|
if (propertyIndex == -1) {
|
|
return null;
|
|
}
|
|
return owner._ebean_getPropertyName(propertyIndex);
|
|
}
|
|
|
|
/**
|
|
* Return the number of properties.s
|
|
*/
|
|
public int getPropertyLength() {
|
|
return owner._ebean_getPropertyNames().length;
|
|
}
|
|
|
|
/**
|
|
* Set the loaded state of the property given it's name.
|
|
*/
|
|
public void setPropertyLoaded(String propertyName, boolean loaded) {
|
|
int position = findProperty(propertyName);
|
|
if (position == -1) {
|
|
throw new IllegalArgumentException("Property " + propertyName + " not found");
|
|
}
|
|
if (loaded) {
|
|
flags[position] |= FLAG_LOADED_PROP;
|
|
} else {
|
|
flags[position] &= ~FLAG_LOADED_PROP;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the property to be treated as unloaded. Used for properties initialised in default
|
|
* constructor.
|
|
*/
|
|
public void setPropertyUnloaded(int propertyIndex) {
|
|
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
|
|
}
|
|
|
|
/**
|
|
* Set the property to be loaded.
|
|
*/
|
|
public void setLoadedProperty(int propertyIndex) {
|
|
flags[propertyIndex] |= FLAG_LOADED_PROP;
|
|
}
|
|
|
|
/**
|
|
* Set all properties to be loaded (post insert).
|
|
*/
|
|
public void setLoadedPropertyAll() {
|
|
for (int i = 0; i < flags.length; i++) {
|
|
flags[i] |= FLAG_LOADED_PROP;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if the property is loaded.
|
|
*/
|
|
public boolean isLoadedProperty(int propertyIndex) {
|
|
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
|
|
}
|
|
|
|
/**
|
|
* Return true if the property is considered changed.
|
|
*/
|
|
public boolean isChangedProperty(int propertyIndex) {
|
|
return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
|
|
}
|
|
|
|
/**
|
|
* 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 (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
|
|
}
|
|
|
|
/**
|
|
* Explicitly mark a property as having been changed.
|
|
*/
|
|
public void markPropertyAsChanged(int propertyIndex) {
|
|
setChangedProperty(propertyIndex);
|
|
setDirty(true);
|
|
}
|
|
|
|
public void setChangedProperty(int propertyIndex) {
|
|
flags[propertyIndex] |= FLAG_CHANGED_PROP;
|
|
}
|
|
|
|
/**
|
|
* Set that an embedded bean has had one of its properties changed.
|
|
*/
|
|
private void setEmbeddedPropertyDirty(int propertyIndex) {
|
|
flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
|
|
}
|
|
|
|
private void setOriginalValue(int propertyIndex, Object value) {
|
|
if (origValues == null) {
|
|
origValues = new Object[owner._ebean_getPropertyNames().length];
|
|
}
|
|
if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
|
|
flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
|
|
origValues[propertyIndex] = value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set old value but force it to be set regardless if it already has a value.
|
|
*/
|
|
private void setOriginalValueForce(int propertyIndex, Object value) {
|
|
if (origValues == null) {
|
|
origValues = new Object[owner._ebean_getPropertyNames().length];
|
|
}
|
|
origValues[propertyIndex] = value;
|
|
}
|
|
|
|
/**
|
|
* For forced update on a 'New' bean set all the loaded properties to changed.
|
|
*/
|
|
public void setNewBeanForUpdate() {
|
|
|
|
for (int i = 0; i < flags.length; i++) {
|
|
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
|
flags[i] |= FLAG_CHANGED_PROP;
|
|
}
|
|
}
|
|
setDirty(true);
|
|
}
|
|
|
|
/**
|
|
* Return the set of property names for a partially loaded bean.
|
|
*/
|
|
public Set<String> getLoadedPropertyNames() {
|
|
if (fullyLoadedBean) {
|
|
return null;
|
|
}
|
|
Set<String> props = new LinkedHashSet<>();
|
|
for (int i = 0; i < flags.length; i++) {
|
|
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
|
props.add(getProperty(i));
|
|
}
|
|
}
|
|
return props;
|
|
}
|
|
|
|
/**
|
|
* Return the array of flags indicating the dirty properties.
|
|
*/
|
|
public boolean[] getDirtyProperties() {
|
|
int len = getPropertyLength();
|
|
boolean[] dirties = new boolean[len];
|
|
for (int i = 0; i < len; i++) {
|
|
// this, or an embedded property has been changed - recurse
|
|
dirties[i] = (flags[i] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
|
|
}
|
|
return dirties;
|
|
}
|
|
|
|
/**
|
|
* Return the set of dirty properties.
|
|
*/
|
|
public Set<String> getDirtyPropertyNames() {
|
|
Set<String> props = new LinkedHashSet<>();
|
|
addDirtyPropertyNames(props, null);
|
|
return props;
|
|
}
|
|
|
|
/**
|
|
* Recursively add dirty properties.
|
|
*/
|
|
public void addDirtyPropertyNames(Set<String> props, String prefix) {
|
|
int len = getPropertyLength();
|
|
for (int i = 0; i < len; i++) {
|
|
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
|
// the property has been changed on this bean
|
|
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
|
|
props.add(propName);
|
|
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
|
// an embedded property has been changed - recurse
|
|
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
|
embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i) + ".");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if any of the given property names are dirty.
|
|
*/
|
|
public boolean hasDirtyProperty(Set<String> propertyNames) {
|
|
|
|
String[] names = owner._ebean_getPropertyNames();
|
|
int len = getPropertyLength();
|
|
for (int i = 0; i < len; i++) {
|
|
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
|
// the property has been changed on this bean
|
|
if (propertyNames.contains(names[i])) {
|
|
return true;
|
|
}
|
|
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
|
if (propertyNames.contains(names[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return a map of dirty properties with their new and old values.
|
|
*/
|
|
public Map<String, ValuePair> getDirtyValues() {
|
|
Map<String, ValuePair> dirtyValues = new LinkedHashMap<>();
|
|
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 ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
|
// 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);
|
|
if (!areEqual(oldVal, newVal)) {
|
|
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
|
|
}
|
|
|
|
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
|
// an embedded property has been changed - recurse
|
|
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
|
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively add dirty properties.
|
|
*/
|
|
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
|
|
int len = getPropertyLength();
|
|
for (int i = 0; i < len; i++) {
|
|
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
|
// the property has been changed on this bean
|
|
Object newVal = owner._ebean_getField(i);
|
|
Object oldVal = getOrigValue(i);
|
|
if (!areEqual(oldVal, newVal)) {
|
|
visitor.visit(i, newVal, oldVal);
|
|
}
|
|
|
|
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
|
// an embedded property has been changed - recurse
|
|
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
|
visitor.visitPush(i);
|
|
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor);
|
|
visitor.visitPop();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a dirty property hash taking into account embedded beans.
|
|
*/
|
|
public StringBuilder getDirtyPropertyKey() {
|
|
StringBuilder sb = new StringBuilder();
|
|
addDirtyPropertyKey(sb);
|
|
return sb;
|
|
}
|
|
|
|
/**
|
|
* Add and return a dirty property hash recursing into embedded beans.
|
|
*/
|
|
private void addDirtyPropertyKey(StringBuilder sb) {
|
|
if (sortOrder > 0) {
|
|
sb.append("s,");
|
|
}
|
|
int len = getPropertyLength();
|
|
for (int i = 0; i < len; i++) {
|
|
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
|
sb.append(i).append(',');
|
|
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
|
// an embedded property has been changed - recurse
|
|
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
|
sb.append(i).append('[');
|
|
embeddedBean._ebean_getIntercept().addDirtyPropertyKey(sb);
|
|
sb.append(']');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a loaded property hash.
|
|
*/
|
|
public StringBuilder getLoadedPropertyKey() {
|
|
StringBuilder sb = new StringBuilder();
|
|
int len = getPropertyLength();
|
|
for (int i = 0; i < len; i++) {
|
|
if (isLoadedProperty(i)) {
|
|
sb.append(i).append(',');
|
|
}
|
|
}
|
|
return sb;
|
|
}
|
|
|
|
public boolean[] getLoaded() {
|
|
boolean[] ret= new boolean[flags.length];
|
|
for (int i = 0; i < ret.length; i++) {
|
|
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Return the index of the property that triggered the lazy load.
|
|
*/
|
|
public int getLazyLoadPropertyIndex() {
|
|
return lazyLoadProperty;
|
|
}
|
|
|
|
/**
|
|
* Return the property that triggered the lazy load.
|
|
*/
|
|
public String getLazyLoadProperty() {
|
|
return getProperty(lazyLoadProperty);
|
|
}
|
|
|
|
/**
|
|
* Load the bean when it is a reference.
|
|
*/
|
|
protected void loadBean(int loadProperty) {
|
|
|
|
synchronized (this) {
|
|
if (beanLoader == null) {
|
|
BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName);
|
|
if (serverLoader == null) {
|
|
throw new PersistenceException("Server [" + ebeanServerName + "] was not found?");
|
|
}
|
|
|
|
// For stand alone reference bean or after deserialisation lazy load
|
|
// using the ebeanServer. Synchronise only on the bean.
|
|
loadBeanInternal(loadProperty, serverLoader);
|
|
return;
|
|
}
|
|
}
|
|
|
|
synchronized (beanLoader) {
|
|
// Lazy loading using LoadBeanContext which supports batch loading
|
|
// Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
|
|
loadBeanInternal(loadProperty, beanLoader);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoke the lazy loading. This method is synchronised externally.
|
|
*/
|
|
private void loadBeanInternal(int loadProperty, BeanLoader loader) {
|
|
|
|
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
|
|
// race condition where multiple threads calling preGetter concurrently
|
|
return;
|
|
}
|
|
|
|
if (lazyLoadFailure) {
|
|
// failed when batch lazy loaded by another bean in the batch
|
|
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
|
|
}
|
|
|
|
if (lazyLoadProperty == -1) {
|
|
|
|
lazyLoadProperty = loadProperty;
|
|
|
|
if (nodeUsageCollector != null) {
|
|
nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty));
|
|
}
|
|
|
|
loader.loadBean(this);
|
|
|
|
if (lazyLoadFailure) {
|
|
// failed when lazy loading this bean
|
|
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
|
|
}
|
|
|
|
// bean should be loaded and intercepting now. setLoaded() has
|
|
// been called by the lazy loading mechanism
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to check if two objects are equal.
|
|
*/
|
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
protected boolean areEqual(Object obj1, Object obj2) {
|
|
if (obj1 == null) {
|
|
return (obj2 == null);
|
|
}
|
|
if (obj2 == null) {
|
|
return false;
|
|
}
|
|
if (obj1 == obj2) {
|
|
return true;
|
|
}
|
|
if (obj1 instanceof BigDecimal) {
|
|
// Use comparable for BigDecimal as equals
|
|
// uses scale in comparison...
|
|
if (obj2 instanceof BigDecimal) {
|
|
Comparable com1 = (Comparable) obj1;
|
|
return (com1.compareTo(obj2) == 0);
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
if (obj1 instanceof URL) {
|
|
// use the string format to determine if dirty
|
|
return obj1.toString().equals(obj2.toString());
|
|
}
|
|
return obj1.equals(obj2);
|
|
}
|
|
|
|
/**
|
|
* Called when a BeanCollection is initialised automatically.
|
|
*/
|
|
public void initialisedMany(int propertyIndex) {
|
|
flags[propertyIndex] |= FLAG_LOADED_PROP;
|
|
}
|
|
|
|
private void preGetterCallback(int propertyIndex) {
|
|
if (preGetterCallback != null) {
|
|
preGetterCallback.preGetterTrigger(propertyIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called prior to Id property getter.
|
|
*/
|
|
public void preGetId() {
|
|
preGetterCallback(-1);
|
|
}
|
|
|
|
/**
|
|
* Method that is called prior to a getter method on the actual entity.
|
|
*/
|
|
public void preGetter(int propertyIndex) {
|
|
preGetterCallback(propertyIndex);
|
|
if (state == STATE_NEW || disableLazyLoad) {
|
|
return;
|
|
}
|
|
if (!isLoadedProperty(propertyIndex)) {
|
|
loadBean(propertyIndex);
|
|
}
|
|
if (nodeUsageCollector != null) {
|
|
nodeUsageCollector.addUsed(getProperty(propertyIndex));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* OneToMany and ManyToMany don't have any interception so just check for
|
|
* PropertyChangeSupport.
|
|
*/
|
|
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
|
|
|
|
if (readOnly) {
|
|
throw new IllegalStateException("This bean is readOnly");
|
|
}
|
|
|
|
setLoadedProperty(propertyIndex);
|
|
}
|
|
|
|
private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
|
|
|
|
if (readOnly) {
|
|
throw new IllegalStateException("This bean is readOnly");
|
|
}
|
|
setChangedProperty(propertyIndex);
|
|
|
|
if (setDirtyState) {
|
|
setOriginalValue(propertyIndex, origValue);
|
|
setDirtyStatus();
|
|
}
|
|
}
|
|
|
|
private void setDirtyStatus() {
|
|
if (!dirty) {
|
|
dirty = true;
|
|
if (embeddedOwner != null) {
|
|
// Cascade dirty state from Embedded bean to parent bean
|
|
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
|
|
}
|
|
if (nodeUsageCollector != null) {
|
|
nodeUsageCollector.setModified();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check to see if the values are not equal. If they are not equal then create
|
|
* the old values for use with ConcurrencyMode.ALL.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (!areEqual(oldValue, newValue)) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Check for primitive boolean.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (oldValue != newValue) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for primitive int.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (oldValue != newValue) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* long.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (oldValue != newValue) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* double.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (Double.compare(oldValue, newValue) != 0) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* float.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (Float.compare(oldValue, newValue) != 0) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* short.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (oldValue != newValue) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* char.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (oldValue != newValue) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* byte.
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (oldValue != newValue) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* char[].
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (!Arrays.equals(oldValue, newValue)) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* byte[].
|
|
*/
|
|
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
|
|
|
|
if (state == STATE_NEW) {
|
|
setLoadedProperty(propertyIndex);
|
|
} else if (!Arrays.equals(oldValue, newValue)) {
|
|
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Explicitly set an old value with force (the old value is forced even it is already set).
|
|
*/
|
|
public void setOldValue(int propertyIndex, Object oldValue) {
|
|
setChangedProperty(propertyIndex);
|
|
setOriginalValueForce(propertyIndex, oldValue);
|
|
setDirtyStatus();
|
|
}
|
|
|
|
/**
|
|
* Return the sort order value for an order column.
|
|
*/
|
|
public int getSortOrder() {
|
|
return sortOrder;
|
|
}
|
|
|
|
/**
|
|
* Set the sort order value for an order column.
|
|
*/
|
|
public void setSortOrder(int sortOrder) {
|
|
this.sortOrder = sortOrder;
|
|
}
|
|
}
|