diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBean.java b/ebean-api/src/main/java/io/ebean/bean/EntityBean.java
index e12621829..53bfc9ba4 100644
--- a/ebean-api/src/main/java/io/ebean/bean/EntityBean.java
+++ b/ebean-api/src/main/java/io/ebean/bean/EntityBean.java
@@ -34,6 +34,13 @@ public interface EntityBean extends Serializable, ToStringAware {
throw new NotEnhancedException();
}
+ /**
+ * Create and return a new entity bean instance optimised for read only no interception use.
+ */
+ default Object _ebean_newInstanceReadOnly() {
+ throw new NotEnhancedException();
+ }
+
/**
* Generated method that sets the loaded state on all the embedded beans on
* this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o);
diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java
index 833d7cb31..ad8c6fa73 100644
--- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java
+++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java
@@ -1,386 +1,178 @@
package io.ebean.bean;
-import io.ebean.DB;
-import io.ebean.Database;
import io.ebean.ValuePair;
-import javax.persistence.EntityNotFoundException;
-import javax.persistence.PersistenceException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
import java.io.Serializable;
-import java.math.BigDecimal;
-import java.net.URL;
-import java.util.*;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.Map;
+import java.util.Set;
-/**
- * This is the object added to every entity bean using byte code enhancement.
- *
- * This provides the mechanisms to support deferred fetching of reference beans
- * and oldValues generation for concurrency checking.
- *
- */
-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;
-
- /**
- * Used when a bean is partially filled.
- */
- private static final byte FLAG_LOADED_PROP = 1;
- private static final byte FLAG_CHANGED_PROP = 2;
- private static final byte FLAG_CHANGEDLOADED_PROP = 3;
- /**
- * Flags indicating if a property is a dirty embedded bean. Used to distinguish
- * 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 distinguish
- * 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;
-
- /**
- * Flags indicating if the mutable hash is set.
- */
- private static final byte FLAG_MUTABLE_HASH_SET = 16;
-
- private transient final ReentrantLock lock = new ReentrantLock();
- private transient NodeUsageCollector nodeUsageCollector;
- private transient PersistenceContext persistenceContext;
- private transient BeanLoader beanLoader;
- private transient PreGetterCallback preGetterCallback;
-
- private String ebeanServerName;
- private boolean deletedFromCollection;
-
- /**
- * 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;
- private boolean fullyLoadedBean;
- private boolean loadedFromCache;
- private final byte[] flags;
- private Object[] origValues;
- private Exception[] loadErrors;
- private int lazyLoadProperty = -1;
- private Object ownerId;
- private int sortOrder;
-
- /**
- * Holds information of json loaded jackson beans (e.g. the original json or checksum).
- */
- private MutableValueInfo[] mutableInfo;
-
- /**
- * Holds json content determined at point of dirty check.
- * Stored here on dirty check such that we only convert to json once.
- */
- private MutableValueNext[] mutableNext;
-
- /**
- * Create a intercept with a given entity.
- */
- public EntityBeanIntercept(Object ownerBean) {
- this.owner = (EntityBean) ownerBean;
- this.flags = new byte[owner._ebean_getPropertyNames().length];
- }
-
- /**
- * EXPERIMENTAL - Constructor only for use by serialization frameworks.
- */
- public EntityBeanIntercept() {
- this.owner = null;
- this.flags = null;
- }
+public interface EntityBeanIntercept extends Serializable {
/**
* Return the 'owning' entity bean.
*/
- public EntityBean getOwner() {
- return owner;
- }
+ EntityBean getOwner();
/**
* Return the persistenceContext.
*/
- public PersistenceContext getPersistenceContext() {
- return persistenceContext;
- }
+ PersistenceContext getPersistenceContext();
/**
* Set the persistenceContext.
*/
- public void setPersistenceContext(PersistenceContext persistenceContext) {
- this.persistenceContext = persistenceContext;
- }
+ void setPersistenceContext(PersistenceContext persistenceContext);
/**
* Turn on profile collection.
*/
- public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
- this.nodeUsageCollector = usageCollector;
- }
+ void setNodeUsageCollector(NodeUsageCollector usageCollector);
/**
* Return the ownerId (IdClass).
*/
- public Object getOwnerId() {
- return ownerId;
- }
+ Object getOwnerId();
/**
* Set the ownerId (IdClass).
*/
- public void setOwnerId(Object ownerId) {
- this.ownerId = ownerId;
- }
+ void setOwnerId(Object ownerId);
/**
* Return the owning bean for an embedded bean.
*/
- public Object getEmbeddedOwner() {
- return embeddedOwner;
- }
+ Object getEmbeddedOwner();
/**
* Return the property index (for the parent) of this embedded bean.
*/
- public int getEmbeddedOwnerIndex() {
- return embeddedOwnerIndex;
- }
+ int getEmbeddedOwnerIndex();
/**
* Clear the getter callback.
*/
- public void clearGetterCallback() {
- this.preGetterCallback = null;
- }
+ void clearGetterCallback();
/**
* 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;
- }
+ void registerGetterCallback(PreGetterCallback getterCallback);
/**
* Set the embedded beans owning bean.
*/
- public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
- this.embeddedOwner = parentBean;
- this.embeddedOwnerIndex = embeddedOwnerIndex;
- }
+ void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex);
/**
* Set the BeanLoader with PersistenceContext.
*/
- public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
- this.beanLoader = beanLoader;
- this.persistenceContext = ctx;
- this.ebeanServerName = beanLoader.getName();
- }
+ void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx);
/**
* 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;
- }
+ void setBeanLoader(BeanLoader beanLoader);
/**
- * Check each property to see if the bean is partially loaded.
+ * Return true if the bean is fully loaded (not a partial).
*/
- public boolean isPartial() {
- for (byte flag : flags) {
- if ((flag & FLAG_LOADED_PROP) == 0) {
- return true;
- }
- }
- return false;
- }
+ boolean isFullyLoadedBean();
+
+ /**
+ * Set true when the bean is fully loaded (not a partial).
+ */
+ void setFullyLoadedBean(boolean fullyLoadedBean);
+
+ /**
+ * Return true if the bean is partially loaded.
+ */
+ boolean isPartial();
/**
* 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() {
- if (dirty) {
- return true;
- }
- if (mutableInfo != null) {
- for (int i = 0; i < mutableInfo.length; i++) {
- if (mutableInfo[i] != null && !mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
- dirty = true;
- break;
- }
- }
- }
- return dirty;
- }
+ boolean isDirty();
/**
* Called by an embedded bean onto its owner.
*/
- public void setEmbeddedDirty(int embeddedProperty) {
- this.dirty = true;
- setEmbeddedPropertyDirty(embeddedProperty);
- }
+ void setEmbeddedDirty(int embeddedProperty);
- public void setDirty(boolean dirty) {
- this.dirty = dirty;
- }
+ /**
+ * Marks the bean as dirty.
+ */
+ void setDirty(boolean dirty);
/**
* Return true if this entity bean is new and not yet saved.
*/
- public boolean isNew() {
- return state == STATE_NEW;
- }
+ boolean isNew();
/**
* Return true if the entity bean is new or dirty (and should be saved).
*/
- public boolean isNewOrDirty() {
- return isNew() || isDirty();
- }
+ boolean isNewOrDirty();
/**
* 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;
- }
+ boolean hasIdOnly(int idIndex);
/**
* Return true if the entity is a reference.
*/
- public boolean isReference() {
- return state == STATE_REFERENCE;
- }
+ boolean isReference();
/**
* 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;
- }
- }
- }
- }
+ void setReference(int idPos);
/**
* Set true when the bean has been loaded from L2 bean cache.
* The effect of this is that we should skip the cache if there
* is subsequent lazy loading (bean cache partially populated).
*/
- public void setLoadedFromCache(boolean loadedFromCache) {
- this.loadedFromCache = loadedFromCache;
- }
+ void setLoadedFromCache(boolean loadedFromCache);
/**
* Return true if this bean was loaded from L2 bean cache.
*/
- public boolean isLoadedFromCache() {
- return loadedFromCache;
- }
+ boolean isLoadedFromCache();
/**
* 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;
- }
+ boolean isReadOnly();
/**
* Set the readOnly status. If readOnly then calls to setter methods through
* an exception.
*/
- public void setReadOnly(boolean readOnly) {
- this.readOnly = readOnly;
- }
+ void setReadOnly(boolean readOnly);
/**
* Set the bean to be updated when persisted (for merge).
*/
- public void setForceUpdate(boolean forceUpdate) {
- this.forceUpdate = forceUpdate;
- }
+ void setForceUpdate(boolean forceUpdate);
/**
* Return true if the entity should be updated.
*/
- public boolean isUpdate() {
- return forceUpdate || state == STATE_LOADED || state == STATE_REFERENCE;
- }
+ boolean isUpdate();
/**
* Return true if the entity has been loaded.
*/
- public boolean isLoaded() {
- return state == STATE_LOADED;
- }
+ boolean isLoaded();
/**
* Set the bean into NEW state.
*/
- public void setNew() {
- this.state = STATE_NEW;
- }
+ void setNew();
/**
* Set the loaded state to true.
@@ -391,852 +183,328 @@ public final class EntityBeanIntercept implements Serializable {
* 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.
*/
- public void setLoaded() {
- this.state = STATE_LOADED;
- this.owner._ebean_setEmbeddedLoaded();
- this.lazyLoadProperty = -1;
- this.origValues = null;
- // after save, transfer the mutable next values back to mutable info
- if (mutableNext != null) {
- for (int i = 0; i < mutableNext.length; i++) {
- MutableValueNext next = mutableNext[i];
- if (next != null) {
- mutableInfo(i, next.info());
- }
- }
- }
- this.mutableNext = null;
- for (int i = 0; i < flags.length; i++) {
- flags[i] &= ~(FLAG_CHANGED_PROP | FLAG_ORIG_VALUE_SET);
- }
- this.dirty = false;
- }
+ void setLoaded();
/**
* When finished loading for lazy or refresh on an already partially populated bean.
*/
- public void setLoadedLazy() {
- this.state = STATE_LOADED;
- this.lazyLoadProperty = -1;
- }
+ void setLoadedLazy();
/**
* Set lazy load failure flag.
*/
- public void setLazyLoadFailure(Object ownerId) {
- this.lazyLoadFailure = true;
- this.ownerId = ownerId;
- }
+ void setLazyLoadFailure(Object ownerId);
/**
* Return true if the bean is marked as having failed lazy loading.
*/
- public boolean isLazyLoadFailure() {
- return lazyLoadFailure;
- }
+ boolean isLazyLoadFailure();
/**
* Return true if lazy loading is disabled.
*/
- public boolean isDisableLazyLoad() {
- return disableLazyLoad;
- }
+ boolean isDisableLazyLoad();
/**
* Set true to turn off lazy loading.
*/
- public void setDisableLazyLoad(boolean disableLazyLoad) {
- this.disableLazyLoad = disableLazyLoad;
- }
+ void setDisableLazyLoad(boolean 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();
- }
- }
+ void setEmbeddedLoaded(Object embeddedBean);
/**
* 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;
- }
- }
+ boolean isEmbeddedNewOrDirty(Object embeddedBean);
/**
* Return the original value that was changed via an update.
*/
- public Object getOrigValue(int propertyIndex) {
- if ((flags[propertyIndex] & (FLAG_ORIG_VALUE_SET | FLAG_MUTABLE_HASH_SET)) == FLAG_MUTABLE_HASH_SET) {
- // mutable hash set, but not ORIG_VALUE
- setOriginalValue(propertyIndex, mutableInfo[propertyIndex].get());
- }
- if (origValues == null) {
- return null;
- }
- return origValues[propertyIndex];
- }
+ Object getOrigValue(int 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;
- }
+ int findProperty(String propertyName);
/**
* Return the property name for the given property.
*/
- public String getProperty(int propertyIndex) {
- if (propertyIndex == -1) {
- return null;
- }
- return owner._ebean_getPropertyName(propertyIndex);
- }
+ String getProperty(int propertyIndex);
/**
* Return the number of properties.
*/
- public int getPropertyLength() {
- return flags.length;
- }
+ int getPropertyLength();
/**
* 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;
- }
- }
+ void setPropertyLoaded(String propertyName, boolean loaded);
/**
* 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;
- }
+ void setPropertyUnloaded(int propertyIndex);
/**
* Set the property to be loaded.
*/
- public void setLoadedProperty(int propertyIndex) {
- flags[propertyIndex] |= FLAG_LOADED_PROP;
- }
+ void setLoadedProperty(int propertyIndex);
/**
* Set all properties to be loaded (post insert).
*/
- public void setLoadedPropertyAll() {
- for (int i = 0; i < flags.length; i++) {
- flags[i] |= FLAG_LOADED_PROP;
- }
- }
+ void setLoadedPropertyAll();
/**
* Return true if the property is loaded.
*/
- public boolean isLoadedProperty(int propertyIndex) {
- return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
- }
+ boolean isLoadedProperty(int propertyIndex);
/**
* Return true if the property is considered changed.
*/
- public boolean isChangedProperty(int propertyIndex) {
- return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
- }
+ boolean isChangedProperty(int 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 (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
- }
+ boolean isDirtyProperty(int propertyIndex);
/**
* Explicitly mark a property as having been changed.
*/
- public void markPropertyAsChanged(int propertyIndex) {
- setChangedProperty(propertyIndex);
- setDirty(true);
- }
+ void markPropertyAsChanged(int propertyIndex);
- public void setChangedProperty(int propertyIndex) {
- flags[propertyIndex] |= FLAG_CHANGED_PROP;
- }
+ void setChangedProperty(int propertyIndex);
- private void setChangeLoaded(int propertyIndex) {
- flags[propertyIndex] |= FLAG_CHANGEDLOADED_PROP;
- }
+ void setChangeLoaded(int propertyIndex);
/**
* Set that an embedded bean has had one of its properties changed.
*/
- private void setEmbeddedPropertyDirty(int propertyIndex) {
- flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
- }
+ void setEmbeddedPropertyDirty(int propertyIndex);
- private void setOriginalValue(int propertyIndex, Object value) {
- if (origValues == null) {
- origValues = new Object[flags.length];
- }
- if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
- flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
- origValues[propertyIndex] = value;
- }
- }
+ void setOriginalValue(int propertyIndex, Object 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[flags.length];
- }
- origValues[propertyIndex] = value;
- }
+ void setOriginalValueForce(int propertyIndex, Object 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);
- }
+ void setNewBeanForUpdate();
/**
* Return the set of property names for a partially loaded bean.
*/
- public Set getLoadedPropertyNames() {
- if (fullyLoadedBean) {
- return null;
- }
- Set props = new LinkedHashSet<>();
- for (int i = 0; i < flags.length; i++) {
- if ((flags[i] & FLAG_LOADED_PROP) != 0) {
- props.add(getProperty(i));
- }
- }
- return props;
- }
+ Set getLoadedPropertyNames();
/**
* 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;
- }
+ boolean[] getDirtyProperties();
/**
* Return the set of dirty properties.
*/
- public Set getDirtyPropertyNames() {
- Set props = new LinkedHashSet<>();
- addDirtyPropertyNames(props, null);
- return props;
- }
+ Set getDirtyPropertyNames();
/**
* Recursively add dirty properties.
*/
- public void addDirtyPropertyNames(Set props, String prefix) {
- int len = getPropertyLength();
- for (int i = 0; i < len; i++) {
- if (isChangedProp(i)) {
- // the property has been changed on this bean
- props.add((prefix == null ? getProperty(i) : prefix + getProperty(i)));
- } 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) + ".");
- }
- }
- }
+ void addDirtyPropertyNames(Set props, String prefix);
/**
* Return true if any of the given property names are dirty.
*/
- public boolean hasDirtyProperty(Set propertyNames) {
- String[] names = owner._ebean_getPropertyNames();
- int len = getPropertyLength();
- for (int i = 0; i < len; i++) {
- if (isChangedProp(i)) {
- if (propertyNames.contains(names[i])) {
- return true;
- }
- } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
- if (propertyNames.contains(names[i])) {
- return true;
- }
- }
- }
- return false;
- }
+ boolean hasDirtyProperty(Set propertyNames);
/**
* Return a map of dirty properties with their new and old values.
*/
- public Map getDirtyValues() {
- Map dirtyValues = new LinkedHashMap<>();
- addDirtyPropertyValues(dirtyValues, null);
- return dirtyValues;
- }
+ Map getDirtyValues();
/**
* Recursively add dirty properties.
*/
- public void addDirtyPropertyValues(Map dirtyValues, String prefix) {
- int len = getPropertyLength();
- for (int i = 0; i < len; i++) {
- if (isChangedProp(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);
- if (notEqual(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) + ".");
- }
- }
- }
+ void addDirtyPropertyValues(Map dirtyValues, String prefix);
/**
* Recursively add dirty properties.
*/
- public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
- int len = getPropertyLength();
- for (int i = 0; i < len; i++) {
- if (isChangedProp(i)) {
- // the property has been changed on this bean
- Object newVal = owner._ebean_getField(i);
- Object oldVal = getOrigValue(i);
- if (notEqual(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();
- }
- }
- }
+ void addDirtyPropertyValues(BeanDiffVisitor visitor);
/**
* Return a dirty property hash taking into account embedded beans.
*/
- public StringBuilder getDirtyPropertyKey() {
- StringBuilder sb = new StringBuilder();
- addDirtyPropertyKey(sb);
- return sb;
- }
+ StringBuilder getDirtyPropertyKey();
/**
* Add and return a dirty property hash.
*/
- 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) { // we do not check against mutablecontent here.
- 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(']');
- }
- }
- }
+ void addDirtyPropertyKey(StringBuilder sb);
/**
* 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;
- }
+ StringBuilder getLoadedPropertyKey();
- 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;
- }
+ boolean[] getLoaded();
/**
* Return the index of the property that triggered the lazy load.
*/
- public int getLazyLoadPropertyIndex() {
- return lazyLoadProperty;
- }
+ int getLazyLoadPropertyIndex();
/**
* Return the property that triggered the lazy load.
*/
- public String getLazyLoadProperty() {
- return getProperty(lazyLoadProperty);
- }
+ String getLazyLoadProperty();
/**
* Load the bean when it is a reference.
*/
- void loadBean(int loadProperty) {
- lock.lock();
- try {
- if (beanLoader == null) {
- final Database database = DB.byName(ebeanServerName);
- if (database == null) {
- throw new PersistenceException("Database [" + ebeanServerName + "] was not found?");
- }
- // For stand alone reference bean or after deserialisation lazy load
- // using the ebeanServer. Synchronise only on the bean.
- loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
- return;
- }
- } finally {
- lock.unlock();
- }
- final Lock lock = beanLoader.lock();
- try {
- // Lazy loading using LoadBeanContext which supports batch loading
- // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
- loadBeanInternal(loadProperty, beanLoader);
- } finally {
- lock.unlock();
- }
- }
+ void loadBean(int loadProperty);
/**
* 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;
- 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"})
- static boolean notEqual(Object obj1, Object obj2) {
- if (obj1 == null) {
- return (obj2 != null);
- }
- if (obj2 == null) {
- return true;
- }
- if (obj1 == obj2) {
- return false;
- }
- 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 true;
- }
- }
- if (obj1 instanceof URL) {
- // use the string format to determine if dirty
- return !obj1.toString().equals(obj2.toString());
- }
- if (obj1 instanceof File && obj2 instanceof File) {
- File file1 = (File) obj1;
- File file2 = (File) obj2;
- if (file1.exists() && file2.exists() && file1.length() == file2.length()) {
- return notEqualContent(file1, file2);
- }
- }
- return !obj1.equals(obj2);
- }
-
- private static boolean notEqualContent(File file1, File file2) {
- try (InputStream is1 = new FileInputStream(file1); InputStream is2 = new FileInputStream(file2)) {
- byte[] buf1 = new byte[16384];
- byte[] buf2 = new byte[16384];
- int len1;
- int len2;
- while ((len1 = is1.read(buf1)) != -1 && (len2 = is2.read(buf2)) != -1) {
- if (len1 != len2) {
- return true;
- }
- if (!Arrays.equals(buf1, buf2)) {
- // it does not matter, if we compare more than len1/len2 as the remainig
- // bytes in the buffers are either 0 or equals from the prev. loop.
- return true;
- }
- }
- return false;
- } catch (IOException e) {
- return true; // handle them as "not equals"
- }
- }
+ void loadBeanInternal(int loadProperty, BeanLoader loader);
/**
* Called when a BeanCollection is initialised automatically.
*/
- public void initialisedMany(int propertyIndex) {
- flags[propertyIndex] |= FLAG_LOADED_PROP;
- }
+ void initialisedMany(int propertyIndex);
- private void preGetterCallback(int propertyIndex) {
- PreGetterCallback preGetterCallback = this.preGetterCallback;
- if (preGetterCallback != null) {
- preGetterCallback.preGetterTrigger(propertyIndex);
- }
- }
+ void preGetterCallback(int propertyIndex);
/**
* Called prior to Id property getter.
*/
- public void preGetId() {
- preGetterCallback(-1);
- }
+ void preGetId();
/**
* 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));
- }
- }
+ void preGetter(int propertyIndex);
/**
* OneToMany and ManyToMany only set loaded state.
*/
- public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
- if (state == STATE_NEW) {
- setLoadedProperty(propertyIndex);
- } else {
- if (readOnly) {
- throw new IllegalStateException("This bean is readOnly");
- }
- setChangeLoaded(propertyIndex);
- }
- }
+ void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue);
- 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();
- }
- }
+ void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue);
- 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();
- }
- }
- }
+ void setDirtyStatus();
/**
* 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 (notEqual(oldValue, newValue)) {
- setChangedPropertyValue(propertyIndex, intercept, oldValue);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue);
/**
* 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue);
/**
* 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue);
/**
- * long.
+ * Check for primitive 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue);
/**
- * double.
+ * Check for primitive 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue);
/**
- * float.
+ * Check for primitive 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue);
/**
- * short.
+ * Check for primitive 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue);
/**
- * char.
+ * Check for primitive 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue);
/**
- * byte.
+ * Check for primitive 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue);
/**
- * char[].
+ * Check for primitive char array.
*/
- 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue);
/**
- * byte[].
+ * Check for primitive byte array.
*/
- 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);
- }
- }
+ void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue);
/**
* 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();
- }
+ void setOldValue(int propertyIndex, Object oldValue);
/**
* Return the sort order value for an order column.
*/
- public int getSortOrder() {
- return sortOrder;
- }
+ int getSortOrder();
/**
* Set the sort order value for an order column.
*/
- public void setSortOrder(int sortOrder) {
- this.sortOrder = sortOrder;
- }
+ void setSortOrder(int sortOrder);
/**
* Set if the entity was deleted from a BeanCollection.
*/
- public void setDeletedFromCollection(final boolean deletedFromCollection) {
- this.deletedFromCollection = deletedFromCollection;
- }
+ void setDeletedFromCollection(boolean deletedFromCollection);
- public boolean isOrphanDelete() {
- return deletedFromCollection && !isNew();
- }
+ boolean isOrphanDelete();
/**
* Set the load error that happened on this property.
*/
- public void setLoadError(int propertyIndex, Exception t) {
- if (loadErrors == null) {
- loadErrors = new Exception[flags.length];
- }
- loadErrors[propertyIndex] = t;
- flags[propertyIndex] |= FLAG_LOADED_PROP;
- }
+ void setLoadError(int propertyIndex, Exception t);
/**
* Returns the loadErrors.
*/
- public Map getLoadErrors() {
- if (loadErrors == null) {
- return Collections.emptyMap();
- }
- Map ret = null;
- int len = getPropertyLength();
- for (int i = 0; i < len; i++) {
- Exception loadError = loadErrors[i];
- if (loadError != null) {
- if (ret == null) {
- ret = new LinkedHashMap<>();
- }
- ret.put(getProperty(i), loadError);
- }
- }
- return ret;
- }
+ Map getLoadErrors();
- private boolean isChangedProp(int i) {
- if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
- return true;
- } else if (mutableInfo == null || mutableInfo[i] == null || mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
- return false;
- } else {
- // mark for change
- flags[i] |= FLAG_CHANGED_PROP;
- dirty = true; // this makes the bean automatically dirty!
- return true;
- }
- }
+ boolean isChangedProp(int i);
/**
* Return the MutableValueInfo for the given property or null.
*/
- public MutableValueInfo mutableInfo(int propertyIndex) {
- return mutableInfo == null ? null : mutableInfo[propertyIndex];
- }
+ MutableValueInfo mutableInfo(int propertyIndex);
/**
* Set the MutableValueInfo for the given property.
*/
- public void mutableInfo(int propertyIndex, MutableValueInfo info) {
- if (mutableInfo == null) {
- mutableInfo = new MutableValueInfo[flags.length];
- }
- flags[propertyIndex] |= FLAG_MUTABLE_HASH_SET;
- mutableInfo[propertyIndex] = info;
- }
+ void mutableInfo(int propertyIndex, MutableValueInfo info);
/**
* Dirty detection set the next mutable property content and info .
@@ -1245,22 +513,10 @@ public final class EntityBeanIntercept implements Serializable {
* We only want to perform the json serialisation once so storing it here as part of
* dirty detection so that we can get it back to bind in insert or update etc.
*/
- public void mutableNext(int propertyIndex, MutableValueNext next) {
- if (mutableNext == null) {
- mutableNext = new MutableValueNext[flags.length];
- }
- mutableNext[propertyIndex] = next;
- }
+ void mutableNext(int propertyIndex, MutableValueNext next);
/**
* Update the 'next' mutable info returning the content that was obtained via dirty detection.
*/
- public String mutableNext(int propertyIndex) {
- if (mutableNext == null) {
- return null;
- }
- final MutableValueNext next = mutableNext[propertyIndex];
- return next != null ? next.content() : null;
- }
-
+ String mutableNext(int propertyIndex);
}
diff --git a/ebean-api/src/main/java/io/ebean/bean/InterceptReadOnly.java b/ebean-api/src/main/java/io/ebean/bean/InterceptReadOnly.java
new file mode 100644
index 000000000..11f1a5a66
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/bean/InterceptReadOnly.java
@@ -0,0 +1,536 @@
+package io.ebean.bean;
+
+import io.ebean.ValuePair;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class InterceptReadOnly implements EntityBeanIntercept {
+
+ private final EntityBean owner;
+
+ public InterceptReadOnly(Object ownerBean) {
+ this.owner = (EntityBean) ownerBean;
+ }
+
+ @Override
+ public EntityBean getOwner() {
+ return owner;
+ }
+
+ @Override
+ public PersistenceContext getPersistenceContext() {
+ return null;
+ }
+
+ @Override
+ public void setPersistenceContext(PersistenceContext persistenceContext) {
+
+ }
+
+ @Override
+ public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
+
+ }
+
+ @Override
+ public Object getOwnerId() {
+ return null;
+ }
+
+ @Override
+ public void setOwnerId(Object ownerId) {
+
+ }
+
+ @Override
+ public Object getEmbeddedOwner() {
+ return null;
+ }
+
+ @Override
+ public int getEmbeddedOwnerIndex() {
+ return 0;
+ }
+
+ @Override
+ public void clearGetterCallback() {
+
+ }
+
+ @Override
+ public void registerGetterCallback(PreGetterCallback getterCallback) {
+
+ }
+
+ @Override
+ public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
+
+ }
+
+ @Override
+ public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
+
+ }
+
+ @Override
+ public void setBeanLoader(BeanLoader beanLoader) {
+
+ }
+
+ @Override
+ public boolean isFullyLoadedBean() {
+ return false;
+ }
+
+ @Override
+ public void setFullyLoadedBean(boolean fullyLoadedBean) {
+
+ }
+
+ @Override
+ public boolean isPartial() {
+ return false;
+ }
+
+ @Override
+ public boolean isDirty() {
+ return false;
+ }
+
+ @Override
+ public void setEmbeddedDirty(int embeddedProperty) {
+
+ }
+
+ @Override
+ public void setDirty(boolean dirty) {
+
+ }
+
+ @Override
+ public boolean isNew() {
+ return false;
+ }
+
+ @Override
+ public boolean isNewOrDirty() {
+ return false;
+ }
+
+ @Override
+ public boolean hasIdOnly(int idIndex) {
+ return false;
+ }
+
+ @Override
+ public boolean isReference() {
+ return false;
+ }
+
+ @Override
+ public void setReference(int idPos) {
+
+ }
+
+ @Override
+ public void setLoadedFromCache(boolean loadedFromCache) {
+
+ }
+
+ @Override
+ public boolean isLoadedFromCache() {
+ return false;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+
+ }
+
+ @Override
+ public void setForceUpdate(boolean forceUpdate) {
+
+ }
+
+ @Override
+ public boolean isUpdate() {
+ return false;
+ }
+
+ @Override
+ public boolean isLoaded() {
+ return true;
+ }
+
+ @Override
+ public void setNew() {
+
+ }
+
+ @Override
+ public void setLoaded() {
+
+ }
+
+ @Override
+ public void setLoadedLazy() {
+
+ }
+
+ @Override
+ public void setLazyLoadFailure(Object ownerId) {
+
+ }
+
+ @Override
+ public boolean isLazyLoadFailure() {
+ return false;
+ }
+
+ @Override
+ public boolean isDisableLazyLoad() {
+ return false;
+ }
+
+ @Override
+ public void setDisableLazyLoad(boolean disableLazyLoad) {
+
+ }
+
+ @Override
+ public void setEmbeddedLoaded(Object embeddedBean) {
+
+ }
+
+ @Override
+ public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
+ return false;
+ }
+
+ @Override
+ public Object getOrigValue(int propertyIndex) {
+ return null;
+ }
+
+ @Override
+ public int findProperty(String propertyName) {
+ return 0;
+ }
+
+ @Override
+ public String getProperty(int propertyIndex) {
+ return null;
+ }
+
+ @Override
+ public int getPropertyLength() {
+ return 0;
+ }
+
+ @Override
+ public void setPropertyLoaded(String propertyName, boolean loaded) {
+
+ }
+
+ @Override
+ public void setPropertyUnloaded(int propertyIndex) {
+
+ }
+
+ @Override
+ public void setLoadedProperty(int propertyIndex) {
+
+ }
+
+ @Override
+ public void setLoadedPropertyAll() {
+
+ }
+
+ @Override
+ public boolean isLoadedProperty(int propertyIndex) {
+ return false;
+ }
+
+ @Override
+ public boolean isChangedProperty(int propertyIndex) {
+ return false;
+ }
+
+ @Override
+ public boolean isDirtyProperty(int propertyIndex) {
+ return false;
+ }
+
+ @Override
+ public void markPropertyAsChanged(int propertyIndex) {
+
+ }
+
+ @Override
+ public void setChangedProperty(int propertyIndex) {
+
+ }
+
+ @Override
+ public void setChangeLoaded(int propertyIndex) {
+
+ }
+
+ @Override
+ public void setEmbeddedPropertyDirty(int propertyIndex) {
+
+ }
+
+ @Override
+ public void setOriginalValue(int propertyIndex, Object value) {
+
+ }
+
+ @Override
+ public void setOriginalValueForce(int propertyIndex, Object value) {
+
+ }
+
+ @Override
+ public void setNewBeanForUpdate() {
+
+ }
+
+ @Override
+ public Set getLoadedPropertyNames() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean[] getDirtyProperties() {
+ return new boolean[0];
+ }
+
+ @Override
+ public Set getDirtyPropertyNames() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void addDirtyPropertyNames(Set props, String prefix) {
+
+ }
+
+ @Override
+ public boolean hasDirtyProperty(Set propertyNames) {
+ return false;
+ }
+
+ @Override
+ public Map getDirtyValues() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public void addDirtyPropertyValues(Map dirtyValues, String prefix) {
+
+ }
+
+ @Override
+ public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
+
+ }
+
+ @Override
+ public StringBuilder getDirtyPropertyKey() {
+ return null;
+ }
+
+ @Override
+ public void addDirtyPropertyKey(StringBuilder sb) {
+
+ }
+
+ @Override
+ public StringBuilder getLoadedPropertyKey() {
+ return null;
+ }
+
+ @Override
+ public boolean[] getLoaded() {
+ return new boolean[0];
+ }
+
+ @Override
+ public int getLazyLoadPropertyIndex() {
+ return 0;
+ }
+
+ @Override
+ public String getLazyLoadProperty() {
+ return null;
+ }
+
+ @Override
+ public void loadBean(int loadProperty) {
+
+ }
+
+ @Override
+ public void loadBeanInternal(int loadProperty, BeanLoader loader) {
+
+ }
+
+ @Override
+ public void initialisedMany(int propertyIndex) {
+
+ }
+
+ @Override
+ public void preGetterCallback(int propertyIndex) {
+
+ }
+
+ @Override
+ public void preGetId() {
+
+ }
+
+ @Override
+ public void preGetter(int propertyIndex) {
+
+ }
+
+ @Override
+ public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
+
+ }
+
+ @Override
+ public void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
+
+ }
+
+ @Override
+ public void setDirtyStatus() {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
+
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
+
+ }
+
+ @Override
+ public void setOldValue(int propertyIndex, Object oldValue) {
+
+ }
+
+ @Override
+ public int getSortOrder() {
+ return 0;
+ }
+
+ @Override
+ public void setSortOrder(int sortOrder) {
+
+ }
+
+ @Override
+ public void setDeletedFromCollection(boolean deletedFromCollection) {
+
+ }
+
+ @Override
+ public boolean isOrphanDelete() {
+ return false;
+ }
+
+ @Override
+ public void setLoadError(int propertyIndex, Exception t) {
+
+ }
+
+ @Override
+ public Map getLoadErrors() {
+ return null;
+ }
+
+ @Override
+ public boolean isChangedProp(int i) {
+ return false;
+ }
+
+ @Override
+ public MutableValueInfo mutableInfo(int propertyIndex) {
+ return null;
+ }
+
+ @Override
+ public void mutableInfo(int propertyIndex, MutableValueInfo info) {
+
+ }
+
+ @Override
+ public void mutableNext(int propertyIndex, MutableValueNext next) {
+
+ }
+
+ @Override
+ public String mutableNext(int propertyIndex) {
+ return null;
+ }
+}
diff --git a/ebean-api/src/main/java/io/ebean/bean/InterceptReadWrite.java b/ebean-api/src/main/java/io/ebean/bean/InterceptReadWrite.java
new file mode 100644
index 000000000..6cfe56a37
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/bean/InterceptReadWrite.java
@@ -0,0 +1,1072 @@
+package io.ebean.bean;
+
+import io.ebean.DB;
+import io.ebean.Database;
+import io.ebean.ValuePair;
+
+import javax.persistence.EntityNotFoundException;
+import javax.persistence.PersistenceException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * This is the object added to every entity bean using byte code enhancement.
+ *
+ * This provides the mechanisms to support deferred fetching of reference beans
+ * and oldValues generation for concurrency checking.
+ *
+ */
+public final class InterceptReadWrite implements EntityBeanIntercept {
+
+ 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;
+
+ /**
+ * Used when a bean is partially filled.
+ */
+ private static final byte FLAG_LOADED_PROP = 1;
+ private static final byte FLAG_CHANGED_PROP = 2;
+ private static final byte FLAG_CHANGEDLOADED_PROP = 3;
+ /**
+ * Flags indicating if a property is a dirty embedded bean. Used to distinguish
+ * 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 distinguish
+ * 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;
+
+ /**
+ * Flags indicating if the mutable hash is set.
+ */
+ private static final byte FLAG_MUTABLE_HASH_SET = 16;
+
+ private transient final ReentrantLock lock = new ReentrantLock();
+ private transient NodeUsageCollector nodeUsageCollector;
+ private transient PersistenceContext persistenceContext;
+ private transient BeanLoader beanLoader;
+ private transient PreGetterCallback preGetterCallback;
+
+ private String ebeanServerName;
+ private boolean deletedFromCollection;
+
+ /**
+ * 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;
+ private boolean fullyLoadedBean;
+ private boolean loadedFromCache;
+ private final byte[] flags;
+ private Object[] origValues;
+ private Exception[] loadErrors;
+ private int lazyLoadProperty = -1;
+ private Object ownerId;
+ private int sortOrder;
+
+ /**
+ * Holds information of json loaded jackson beans (e.g. the original json or checksum).
+ */
+ private MutableValueInfo[] mutableInfo;
+
+ /**
+ * Holds json content determined at point of dirty check.
+ * Stored here on dirty check such that we only convert to json once.
+ */
+ private MutableValueNext[] mutableNext;
+
+ /**
+ * Create a intercept with a given entity.
+ */
+ public InterceptReadWrite(Object ownerBean) {
+ this.owner = (EntityBean) ownerBean;
+ this.flags = new byte[owner._ebean_getPropertyNames().length];
+ }
+
+ /**
+ * EXPERIMENTAL - Constructor only for use by serialization frameworks.
+ */
+ public InterceptReadWrite() {
+ this.owner = null;
+ this.flags = null;
+ }
+
+ @Override
+ public EntityBean getOwner() {
+ return owner;
+ }
+
+ @Override
+ public PersistenceContext getPersistenceContext() {
+ return persistenceContext;
+ }
+
+ @Override
+ public void setPersistenceContext(PersistenceContext persistenceContext) {
+ this.persistenceContext = persistenceContext;
+ }
+
+ @Override
+ public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
+ this.nodeUsageCollector = usageCollector;
+ }
+
+ @Override
+ public Object getOwnerId() {
+ return ownerId;
+ }
+
+ @Override
+ public void setOwnerId(Object ownerId) {
+ this.ownerId = ownerId;
+ }
+
+ @Override
+ public Object getEmbeddedOwner() {
+ return embeddedOwner;
+ }
+
+ @Override
+ public int getEmbeddedOwnerIndex() {
+ return embeddedOwnerIndex;
+ }
+
+ @Override
+ public void clearGetterCallback() {
+ this.preGetterCallback = null;
+ }
+
+ @Override
+ public void registerGetterCallback(PreGetterCallback getterCallback) {
+ this.preGetterCallback = getterCallback;
+ }
+
+ @Override
+ public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
+ this.embeddedOwner = parentBean;
+ this.embeddedOwnerIndex = embeddedOwnerIndex;
+ }
+
+ @Override
+ public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
+ this.beanLoader = beanLoader;
+ this.persistenceContext = ctx;
+ this.ebeanServerName = beanLoader.getName();
+ }
+
+ @Override
+ public void setBeanLoader(BeanLoader beanLoader) {
+ this.beanLoader = beanLoader;
+ this.ebeanServerName = beanLoader.getName();
+ }
+
+ @Override
+ public boolean isFullyLoadedBean() {
+ return fullyLoadedBean;
+ }
+
+ @Override
+ public void setFullyLoadedBean(boolean fullyLoadedBean) {
+ this.fullyLoadedBean = fullyLoadedBean;
+ }
+
+ @Override
+ public boolean isPartial() {
+ for (byte flag : flags) {
+ if ((flag & FLAG_LOADED_PROP) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isDirty() {
+ if (dirty) {
+ return true;
+ }
+ if (mutableInfo != null) {
+ for (int i = 0; i < mutableInfo.length; i++) {
+ if (mutableInfo[i] != null && !mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
+ dirty = true;
+ break;
+ }
+ }
+ }
+ return dirty;
+ }
+
+ @Override
+ public void setEmbeddedDirty(int embeddedProperty) {
+ this.dirty = true;
+ setEmbeddedPropertyDirty(embeddedProperty);
+ }
+
+ @Override
+ public void setDirty(boolean dirty) {
+ this.dirty = dirty;
+ }
+
+ @Override
+ public boolean isNew() {
+ return state == STATE_NEW;
+ }
+
+ @Override
+ public boolean isNewOrDirty() {
+ return isNew() || isDirty();
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public boolean isReference() {
+ return state == STATE_REFERENCE;
+ }
+
+ @Override
+ 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;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setLoadedFromCache(boolean loadedFromCache) {
+ this.loadedFromCache = loadedFromCache;
+ }
+
+ @Override
+ public boolean isLoadedFromCache() {
+ return loadedFromCache;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
+ @Override
+ public void setForceUpdate(boolean forceUpdate) {
+ this.forceUpdate = forceUpdate;
+ }
+
+ @Override
+ public boolean isUpdate() {
+ return forceUpdate || state == STATE_LOADED || state == STATE_REFERENCE;
+ }
+
+ @Override
+ public boolean isLoaded() {
+ return state == STATE_LOADED;
+ }
+
+ @Override
+ public void setNew() {
+ this.state = STATE_NEW;
+ }
+
+ @Override
+ public void setLoaded() {
+ this.state = STATE_LOADED;
+ this.owner._ebean_setEmbeddedLoaded();
+ this.lazyLoadProperty = -1;
+ this.origValues = null;
+ // after save, transfer the mutable next values back to mutable info
+ if (mutableNext != null) {
+ for (int i = 0; i < mutableNext.length; i++) {
+ MutableValueNext next = mutableNext[i];
+ if (next != null) {
+ mutableInfo(i, next.info());
+ }
+ }
+ }
+ this.mutableNext = null;
+ for (int i = 0; i < flags.length; i++) {
+ flags[i] &= ~(FLAG_CHANGED_PROP | FLAG_ORIG_VALUE_SET);
+ }
+ this.dirty = false;
+ }
+
+ @Override
+ public void setLoadedLazy() {
+ this.state = STATE_LOADED;
+ this.lazyLoadProperty = -1;
+ }
+
+ @Override
+ public void setLazyLoadFailure(Object ownerId) {
+ this.lazyLoadFailure = true;
+ this.ownerId = ownerId;
+ }
+
+ @Override
+ public boolean isLazyLoadFailure() {
+ return lazyLoadFailure;
+ }
+
+ @Override
+ public boolean isDisableLazyLoad() {
+ return disableLazyLoad;
+ }
+
+ @Override
+ public void setDisableLazyLoad(boolean disableLazyLoad) {
+ this.disableLazyLoad = disableLazyLoad;
+ }
+
+ @Override
+ public void setEmbeddedLoaded(Object embeddedBean) {
+ if (embeddedBean instanceof EntityBean) {
+ EntityBean eb = (EntityBean) embeddedBean;
+ eb._ebean_getIntercept().setLoaded();
+ }
+ }
+
+ @Override
+ 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;
+ }
+ }
+
+ @Override
+ public Object getOrigValue(int propertyIndex) {
+ if ((flags[propertyIndex] & (FLAG_ORIG_VALUE_SET | FLAG_MUTABLE_HASH_SET)) == FLAG_MUTABLE_HASH_SET) {
+ // mutable hash set, but not ORIG_VALUE
+ setOriginalValue(propertyIndex, mutableInfo[propertyIndex].get());
+ }
+ if (origValues == null) {
+ return null;
+ }
+ return origValues[propertyIndex];
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public String getProperty(int propertyIndex) {
+ if (propertyIndex == -1) {
+ return null;
+ }
+ return owner._ebean_getPropertyName(propertyIndex);
+ }
+
+ @Override
+ public int getPropertyLength() {
+ return flags.length;
+ }
+
+ @Override
+ 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;
+ }
+ }
+
+ @Override
+ public void setPropertyUnloaded(int propertyIndex) {
+ flags[propertyIndex] &= ~FLAG_LOADED_PROP;
+ }
+
+ @Override
+ public void setLoadedProperty(int propertyIndex) {
+ flags[propertyIndex] |= FLAG_LOADED_PROP;
+ }
+
+ @Override
+ public void setLoadedPropertyAll() {
+ for (int i = 0; i < flags.length; i++) {
+ flags[i] |= FLAG_LOADED_PROP;
+ }
+ }
+
+ @Override
+ public boolean isLoadedProperty(int propertyIndex) {
+ return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
+ }
+
+ @Override
+ public boolean isChangedProperty(int propertyIndex) {
+ return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
+ }
+
+ @Override
+ public boolean isDirtyProperty(int propertyIndex) {
+ return (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
+ }
+
+ @Override
+ public void markPropertyAsChanged(int propertyIndex) {
+ setChangedProperty(propertyIndex);
+ setDirty(true);
+ }
+
+ @Override
+ public void setChangedProperty(int propertyIndex) {
+ flags[propertyIndex] |= FLAG_CHANGED_PROP;
+ }
+
+ @Override
+ public void setChangeLoaded(int propertyIndex) {
+ flags[propertyIndex] |= FLAG_CHANGEDLOADED_PROP;
+ }
+
+ @Override
+ public void setEmbeddedPropertyDirty(int propertyIndex) {
+ flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
+ }
+
+ @Override
+ public void setOriginalValue(int propertyIndex, Object value) {
+ if (origValues == null) {
+ origValues = new Object[flags.length];
+ }
+ if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
+ flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
+ origValues[propertyIndex] = value;
+ }
+ }
+
+ @Override
+ public void setOriginalValueForce(int propertyIndex, Object value) {
+ if (origValues == null) {
+ origValues = new Object[flags.length];
+ }
+ origValues[propertyIndex] = value;
+ }
+
+ @Override
+ 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);
+ }
+
+ @Override
+ public Set getLoadedPropertyNames() {
+ if (fullyLoadedBean) {
+ return null;
+ }
+ Set props = new LinkedHashSet<>();
+ for (int i = 0; i < flags.length; i++) {
+ if ((flags[i] & FLAG_LOADED_PROP) != 0) {
+ props.add(getProperty(i));
+ }
+ }
+ return props;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public Set getDirtyPropertyNames() {
+ Set props = new LinkedHashSet<>();
+ addDirtyPropertyNames(props, null);
+ return props;
+ }
+
+ @Override
+ public void addDirtyPropertyNames(Set props, String prefix) {
+ int len = getPropertyLength();
+ for (int i = 0; i < len; i++) {
+ if (isChangedProp(i)) {
+ // the property has been changed on this bean
+ props.add((prefix == null ? getProperty(i) : prefix + getProperty(i)));
+ } 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) + ".");
+ }
+ }
+ }
+
+ @Override
+ public boolean hasDirtyProperty(Set propertyNames) {
+ String[] names = owner._ebean_getPropertyNames();
+ int len = getPropertyLength();
+ for (int i = 0; i < len; i++) {
+ if (isChangedProp(i)) {
+ if (propertyNames.contains(names[i])) {
+ return true;
+ }
+ } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
+ if (propertyNames.contains(names[i])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Map getDirtyValues() {
+ Map dirtyValues = new LinkedHashMap<>();
+ addDirtyPropertyValues(dirtyValues, null);
+ return dirtyValues;
+ }
+
+ @Override
+ public void addDirtyPropertyValues(Map dirtyValues, String prefix) {
+ int len = getPropertyLength();
+ for (int i = 0; i < len; i++) {
+ if (isChangedProp(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);
+ if (notEqual(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) + ".");
+ }
+ }
+ }
+
+ @Override
+ public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
+ int len = getPropertyLength();
+ for (int i = 0; i < len; i++) {
+ if (isChangedProp(i)) {
+ // the property has been changed on this bean
+ Object newVal = owner._ebean_getField(i);
+ Object oldVal = getOrigValue(i);
+ if (notEqual(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();
+ }
+ }
+ }
+
+ @Override
+ public StringBuilder getDirtyPropertyKey() {
+ StringBuilder sb = new StringBuilder();
+ addDirtyPropertyKey(sb);
+ return sb;
+ }
+
+ @Override
+ public 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) { // we do not check against mutablecontent here.
+ 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(']');
+ }
+ }
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public int getLazyLoadPropertyIndex() {
+ return lazyLoadProperty;
+ }
+
+ @Override
+ public String getLazyLoadProperty() {
+ return getProperty(lazyLoadProperty);
+ }
+
+ @Override
+ public void loadBean(int loadProperty) {
+ lock.lock();
+ try {
+ if (beanLoader == null) {
+ final Database database = DB.byName(ebeanServerName);
+ if (database == null) {
+ throw new PersistenceException("Database [" + ebeanServerName + "] was not found?");
+ }
+ // For stand alone reference bean or after deserialisation lazy load
+ // using the ebeanServer. Synchronise only on the bean.
+ loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
+ return;
+ }
+ } finally {
+ lock.unlock();
+ }
+ final Lock lock = beanLoader.lock();
+ try {
+ // Lazy loading using LoadBeanContext which supports batch loading
+ // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
+ loadBeanInternal(loadProperty, beanLoader);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public 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;
+ 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"})
+ static boolean notEqual(Object obj1, Object obj2) {
+ if (obj1 == null) {
+ return (obj2 != null);
+ }
+ if (obj2 == null) {
+ return true;
+ }
+ if (obj1 == obj2) {
+ return false;
+ }
+ 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 true;
+ }
+ }
+ if (obj1 instanceof URL) {
+ // use the string format to determine if dirty
+ return !obj1.toString().equals(obj2.toString());
+ }
+ if (obj1 instanceof File && obj2 instanceof File) {
+ File file1 = (File) obj1;
+ File file2 = (File) obj2;
+ if (file1.exists() && file2.exists() && file1.length() == file2.length()) {
+ return notEqualContent(file1, file2);
+ }
+ }
+ return !obj1.equals(obj2);
+ }
+
+ private static boolean notEqualContent(File file1, File file2) {
+ try (InputStream is1 = new FileInputStream(file1); InputStream is2 = new FileInputStream(file2)) {
+ byte[] buf1 = new byte[16384];
+ byte[] buf2 = new byte[16384];
+ int len1;
+ int len2;
+ while ((len1 = is1.read(buf1)) != -1 && (len2 = is2.read(buf2)) != -1) {
+ if (len1 != len2) {
+ return true;
+ }
+ if (!Arrays.equals(buf1, buf2)) {
+ // it does not matter, if we compare more than len1/len2 as the remainig
+ // bytes in the buffers are either 0 or equals from the prev. loop.
+ return true;
+ }
+ }
+ return false;
+ } catch (IOException e) {
+ return true; // handle them as "not equals"
+ }
+ }
+
+ @Override
+ public void initialisedMany(int propertyIndex) {
+ flags[propertyIndex] |= FLAG_LOADED_PROP;
+ }
+
+ @Override
+ public void preGetterCallback(int propertyIndex) {
+ PreGetterCallback preGetterCallback = this.preGetterCallback;
+ if (preGetterCallback != null) {
+ preGetterCallback.preGetterTrigger(propertyIndex);
+ }
+ }
+
+ @Override
+ public void preGetId() {
+ preGetterCallback(-1);
+ }
+
+ @Override
+ 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));
+ }
+ }
+
+ @Override
+ public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
+ if (state == STATE_NEW) {
+ setLoadedProperty(propertyIndex);
+ } else {
+ if (readOnly) {
+ throw new IllegalStateException("This bean is readOnly");
+ }
+ setChangeLoaded(propertyIndex);
+ }
+ }
+
+ @Override
+ public 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();
+ }
+ }
+
+ @Override
+ public 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();
+ }
+ }
+ }
+
+ @Override
+ public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
+ if (state == STATE_NEW) {
+ setLoadedProperty(propertyIndex);
+ } else if (notEqual(oldValue, newValue)) {
+ setChangedPropertyValue(propertyIndex, intercept, oldValue);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ public void setOldValue(int propertyIndex, Object oldValue) {
+ setChangedProperty(propertyIndex);
+ setOriginalValueForce(propertyIndex, oldValue);
+ setDirtyStatus();
+ }
+
+ @Override
+ public int getSortOrder() {
+ return sortOrder;
+ }
+
+ @Override
+ public void setSortOrder(int sortOrder) {
+ this.sortOrder = sortOrder;
+ }
+
+ @Override
+ public void setDeletedFromCollection(final boolean deletedFromCollection) {
+ this.deletedFromCollection = deletedFromCollection;
+ }
+
+ @Override
+ public boolean isOrphanDelete() {
+ return deletedFromCollection && !isNew();
+ }
+
+ @Override
+ public void setLoadError(int propertyIndex, Exception t) {
+ if (loadErrors == null) {
+ loadErrors = new Exception[flags.length];
+ }
+ loadErrors[propertyIndex] = t;
+ flags[propertyIndex] |= FLAG_LOADED_PROP;
+ }
+
+ @Override
+ public Map getLoadErrors() {
+ if (loadErrors == null) {
+ return Collections.emptyMap();
+ }
+ Map ret = null;
+ int len = getPropertyLength();
+ for (int i = 0; i < len; i++) {
+ Exception loadError = loadErrors[i];
+ if (loadError != null) {
+ if (ret == null) {
+ ret = new LinkedHashMap<>();
+ }
+ ret.put(getProperty(i), loadError);
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean isChangedProp(int i) {
+ if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
+ return true;
+ } else if (mutableInfo == null || mutableInfo[i] == null || mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
+ return false;
+ } else {
+ // mark for change
+ flags[i] |= FLAG_CHANGED_PROP;
+ dirty = true; // this makes the bean automatically dirty!
+ return true;
+ }
+ }
+
+ @Override
+ public MutableValueInfo mutableInfo(int propertyIndex) {
+ return mutableInfo == null ? null : mutableInfo[propertyIndex];
+ }
+
+ @Override
+ public void mutableInfo(int propertyIndex, MutableValueInfo info) {
+ if (mutableInfo == null) {
+ mutableInfo = new MutableValueInfo[flags.length];
+ }
+ flags[propertyIndex] |= FLAG_MUTABLE_HASH_SET;
+ mutableInfo[propertyIndex] = info;
+ }
+
+ @Override
+ public void mutableNext(int propertyIndex, MutableValueNext next) {
+ if (mutableNext == null) {
+ mutableNext = new MutableValueNext[flags.length];
+ }
+ mutableNext[propertyIndex] = next;
+ }
+
+ @Override
+ public String mutableNext(int propertyIndex) {
+ if (mutableNext == null) {
+ return null;
+ }
+ final MutableValueNext next = mutableNext[propertyIndex];
+ return next != null ? next.content() : null;
+ }
+}
diff --git a/ebean-api/src/main/java/io/ebean/cache/TenantAwareCache.java b/ebean-api/src/main/java/io/ebean/cache/TenantAwareCache.java
index 42e22a950..06db455e7 100644
--- a/ebean-api/src/main/java/io/ebean/cache/TenantAwareCache.java
+++ b/ebean-api/src/main/java/io/ebean/cache/TenantAwareCache.java
@@ -29,9 +29,10 @@ public final class TenantAwareCache implements ServerCache {
/**
* Return the underlying ServerCache that is being delegated to.
*/
+ @SuppressWarnings("unchecked")
@Override
public T unwrap(Class cls) {
- return (T)delegate;
+ return (T) delegate;
}
@Override
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java
index 817f4e1de..502135111 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java
@@ -28,7 +28,7 @@ abstract class BaseCollectionHelp implements BeanCollectionHelp {
}
@Override
- public void setLoader(BeanCollectionLoader loader) {
+ public final void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
}
@@ -43,7 +43,7 @@ abstract class BaseCollectionHelp implements BeanCollectionHelp {
@SuppressWarnings("rawtypes")
@Override
- public Collection underlying(Object value) {
+ public final Collection underlying(Object value) {
if (value instanceof BeanCollection) {
return ((BeanCollection)value).getActualDetails();
} else {
@@ -51,7 +51,7 @@ abstract class BaseCollectionHelp implements BeanCollectionHelp {
}
}
- void jsonWriteCollection(SpiJsonWriter ctx, String name, Collection> list) throws IOException {
+ final void jsonWriteCollection(SpiJsonWriter ctx, String name, Collection> list) throws IOException {
if (!list.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Object bean : list) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java
index 9fda096aa..c8e5c522d 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java
@@ -47,6 +47,11 @@ public interface BeanCollectionHelp extends CQueryCollectionAdd {
*/
BeanCollection createEmpty(EntityBean bean);
+ /**
+ * Create and return an empty 'vanilla' collection that does not support lazy loading.
+ */
+ Object createEmptyReference();
+
/**
* Add a bean to the List Set or Map.
*/
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
index 4974f221c..6951438cf 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -86,7 +86,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType {
private final ConcurrentHashMap elDeployCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap> comparatorCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap dynamicProperty = new ConcurrentHashMap<>();
- private final ConcurrentHashMap> pathMaps = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> pathMaps = new ConcurrentHashMap<>();
private final Map namedRawSql;
private final Map namedQuery;
@@ -1765,6 +1765,14 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType {
return createEntityBean(false);
}
+ @Override
+ public EntityBean createEntityBean2(boolean readOnlyNoIntercept) {
+ if (readOnlyNoIntercept) {
+ return (EntityBean) prototypeEntityBean._ebean_newInstanceReadOnly();
+ }
+ return createEntityBean(false);
+ }
+
/**
* Create an entity bean for JSON marshalling (which differs for the element collection case).
*/
@@ -2879,7 +2887,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType {
for (BeanPropertyAssocMany> many : propertiesManySave) {
if (ebi.isLoadedProperty(many.propertyIndex())) {
final Object value = many.getValue(bean);
- if (value instanceof BeanCollection && ((BeanCollection>)value).hasModifications() || value != null) {
+ if (value instanceof BeanCollection && ((BeanCollection>) value).hasModifications() || value != null) {
return true;
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java
index 5696e9b19..97b92fb9b 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java
@@ -11,6 +11,7 @@ import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -27,8 +28,7 @@ public class BeanListHelp extends BaseCollectionHelp {
}
@Override
- public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
-
+ public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanList>) {
BeanList> bl = (BeanList>) bc;
if (bl.getActualList() == null) {
@@ -42,12 +42,17 @@ public class BeanListHelp extends BaseCollectionHelp {
}
@Override
- public BeanCollection createEmptyNoParent() {
+ public final Object createEmptyReference() {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ public final BeanCollection createEmptyNoParent() {
return new BeanList<>();
}
@Override
- public BeanCollection createEmpty(EntityBean parentBean) {
+ public final BeanCollection createEmpty(EntityBean parentBean) {
BeanList beanList = new BeanList<>(loader, parentBean, propertyName);
if (many != null) {
beanList.setModifyListening(many.modifyListenMode());
@@ -56,29 +61,23 @@ public class BeanListHelp extends BaseCollectionHelp {
}
@Override
- public BeanCollection createReference(EntityBean parentBean) {
-
+ public final BeanCollection createReference(EntityBean parentBean) {
BeanList beanList = new BeanList<>(loader, parentBean, propertyName);
beanList.setModifyListening(many.modifyListenMode());
return beanList;
}
@Override
- public void refresh(SpiEbeanServer server, Query> query, Transaction t, EntityBean parentBean) {
-
+ public final void refresh(SpiEbeanServer server, Query> query, Transaction t, EntityBean parentBean) {
BeanList> newBeanList = (BeanList>) server.findList(query, t);
refresh(newBeanList, parentBean);
}
@Override
- public void refresh(BeanCollection> bc, EntityBean parentBean) {
-
+ public final void refresh(BeanCollection> bc, EntityBean parentBean) {
BeanList> newBeanList = (BeanList>) bc;
-
List> currentList = (List>) many.getValue(parentBean);
-
newBeanList.setModifyListening(many.modifyListenMode());
-
if (currentList == null) {
// the currentList is null? Not really expecting this...
many.setValue(parentBean, newBeanList);
@@ -96,8 +95,7 @@ public class BeanListHelp extends BaseCollectionHelp {
}
@Override
- public void jsonWrite(SpiJsonWriter ctx, String name, Object collection, boolean explicitInclude) throws IOException {
-
+ public final void jsonWrite(SpiJsonWriter ctx, String name, Object collection, boolean explicitInclude) throws IOException {
List> list;
if (collection instanceof BeanCollection>) {
BeanList> beanList = (BeanList>) collection;
@@ -114,7 +112,6 @@ public class BeanListHelp extends BaseCollectionHelp {
} else {
list = (List>) collection;
}
-
jsonWriteCollection(ctx, name, list);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java
index d2cec7d13..896a87267 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java
@@ -10,6 +10,7 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -36,13 +37,11 @@ public class BeanMapHelp extends BaseCollectionHelp {
@Override
@SuppressWarnings("unchecked")
- public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
-
+ public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
if (mapKey == null) {
mapKey = many.mapKey();
}
BeanProperty beanProp = targetDescriptor.beanProperty(mapKey);
-
if (bc instanceof BeanMap, ?>) {
BeanMap