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 bm = (BeanMap) bc; Map actualMap = bm.getActualMap(); @@ -57,10 +56,9 @@ public class BeanMapHelp extends BaseCollectionHelp { } } - static class Adder implements BeanCollectionAdd { + static final class Adder implements BeanCollectionAdd { private final BeanProperty beanProperty; - private final Map map; Adder(BeanProperty beanProperty, Map map) { @@ -76,13 +74,17 @@ public class BeanMapHelp extends BaseCollectionHelp { } @Override - public BeanCollection createEmptyNoParent() { + public final Object createEmptyReference() { + return Collections.EMPTY_MAP; + } + + @Override + public final BeanCollection createEmptyNoParent() { return new BeanMap<>(); } @Override - public BeanCollection createEmpty(EntityBean ownerBean) { - + public final BeanCollection createEmpty(EntityBean ownerBean) { BeanMap beanMap = new BeanMap<>(loader, ownerBean, propertyName); if (many != null) { beanMap.setModifyListening(many.modifyListenMode()); @@ -92,7 +94,6 @@ public class BeanMapHelp extends BaseCollectionHelp { @Override public void add(BeanCollection collection, EntityBean bean, boolean withCheck) { - if (bean == null) { ((BeanMap) collection).internalPutNull(); } else { @@ -104,8 +105,7 @@ public class BeanMapHelp extends BaseCollectionHelp { @Override @SuppressWarnings({"unchecked", "rawtypes"}) - public BeanCollection createReference(EntityBean parentBean) { - + public final BeanCollection createReference(EntityBean parentBean) { BeanMap beanMap = new BeanMap(loader, parentBean, propertyName); if (many != null) { beanMap.setModifyListening(many.modifyListenMode()); @@ -114,14 +114,13 @@ public class BeanMapHelp extends BaseCollectionHelp { } @Override - public void refresh(SpiEbeanServer server, Query query, Transaction t, EntityBean parentBean) { + public final void refresh(SpiEbeanServer server, Query query, Transaction t, EntityBean parentBean) { BeanMap newBeanMap = (BeanMap) server.findMap(query, t); refresh(newBeanMap, parentBean); } @Override - public void refresh(BeanCollection bc, EntityBean parentBean) { - + public final void refresh(BeanCollection bc, EntityBean parentBean) { BeanMap newBeanMap = (BeanMap) bc; Map current = (Map) many.getValue(parentBean); @@ -143,8 +142,7 @@ public class BeanMapHelp 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 { Map map; if (collection instanceof BeanCollection) { BeanMap bc = (BeanMap) collection; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelpElement.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelpElement.java index 1c6fb7931..3d5936c79 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelpElement.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelpElement.java @@ -15,7 +15,6 @@ public final class BeanMapHelpElement extends BeanMapHelp { public void add(BeanCollection collection, EntityBean bean, boolean withCheck) { Object key = bean._ebean_getField(0); Object val = bean._ebean_getField(1); - BeanMap map = ((BeanMap) collection); if (withCheck) { map.internalPutWithCheck(key, val); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 4e6f2d577..81a5f4632 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -601,6 +601,11 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST return mapKey; } + @Override + public void createEmptyReference(EntityBean localBean) { + setValue(localBean, help.createEmptyReference()); + } + @Override public BeanCollection createReference(EntityBean localBean, boolean forceNewReference) { return forceNewReference ? createReference(localBean) : createReferenceIfNull(localBean); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java index fd27bac16..1f8512f9c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.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.LinkedHashSet; import java.util.Set; @@ -33,7 +34,7 @@ public class BeanSetHelp extends BaseCollectionHelp { } @Override - public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { + public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { if (bc instanceof BeanSet) { BeanSet beanSet = (BeanSet) bc; if (beanSet.getActualSet() == null) { @@ -46,12 +47,17 @@ public class BeanSetHelp extends BaseCollectionHelp { } @Override - public BeanCollection createEmptyNoParent() { + public final Object createEmptyReference() { + return Collections.EMPTY_SET; + } + + @Override + public final BeanCollection createEmptyNoParent() { return new BeanSet<>(); } @Override - public BeanCollection createEmpty(EntityBean ownerBean) { + public final BeanCollection createEmpty(EntityBean ownerBean) { BeanSet beanSet = new BeanSet<>(loader, ownerBean, propertyName); if (many != null) { beanSet.setModifyListening(many.modifyListenMode()); @@ -60,20 +66,20 @@ public class BeanSetHelp extends BaseCollectionHelp { } @Override - public BeanCollection createReference(EntityBean parentBean) { + public final BeanCollection createReference(EntityBean parentBean) { BeanSet beanSet = new BeanSet<>(loader, parentBean, propertyName); beanSet.setModifyListening(many.modifyListenMode()); return beanSet; } @Override - public void refresh(SpiEbeanServer server, Query query, Transaction t, EntityBean parentBean) { + public final void refresh(SpiEbeanServer server, Query query, Transaction t, EntityBean parentBean) { BeanSet newBeanSet = (BeanSet) server.findSet(query, t); refresh(newBeanSet, parentBean); } @Override - public void refresh(BeanCollection bc, EntityBean parentBean) { + public final void refresh(BeanCollection bc, EntityBean parentBean) { BeanSet newBeanSet = (BeanSet) bc; Set current = (Set) many.getValue(parentBean); newBeanSet.setModifyListening(many.modifyListenMode()); @@ -94,7 +100,7 @@ public class BeanSetHelp 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 { Set set; if (collection instanceof BeanCollection) { BeanSet bc = (BeanSet) collection; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java index 2f6566bff..c62278480 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; +import io.ebean.bean.InterceptReadWrite; final class ElementEntityBean implements EntityBean { @@ -13,7 +14,7 @@ final class ElementEntityBean implements EntityBean { ElementEntityBean(String[] properties) { this.properties = properties; - this.intercept = new EntityBeanIntercept(this); + this.intercept = new InterceptReadWrite(this); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java index c827a255e..a7102af8c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java @@ -33,6 +33,11 @@ public interface STreePropertyAssocMany extends STreePropertyAssoc { */ BeanCollection createReference(EntityBean localBean, boolean forceNewReference); + /** + * Populate the collection for read-only disabled lazy loading (aka Java Collections non mutable empty collection). + */ + void createEmptyReference(EntityBean localBean); + /** * Return true if the property has a join table. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeType.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeType.java index 88e3b851a..12e14abbf 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeType.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeType.java @@ -79,6 +79,11 @@ public interface STreeType { */ EntityBean createEntityBean(); + /** + * Create a new entity bean instance with option for read only optimisation. + */ + EntityBean createEntityBean2(boolean readOnlyNoIntercept); + /** * Put the entity bean into the persistence context. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 672e5acff..faf4a2a5a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -50,6 +50,7 @@ public final class SqlTreeBuilder { */ private final boolean rawNoId; private final boolean disableLazyLoad; + private final boolean readOnly; private final SpiQuery.TemporalMode temporalMode; private SqlTreeNode rootNode; private boolean sqlDistinct; @@ -63,6 +64,7 @@ public final class SqlTreeBuilder { this.desc = request.descriptor(); this.rawNoId = rawNoId; this.disableLazyLoad = request.query().isDisableLazyLoading(); + this.readOnly = Boolean.TRUE.equals(request.query().isReadOnly()); this.query = null; this.subQuery = false; this.distinctOnPlatform = false; @@ -88,6 +90,7 @@ public final class SqlTreeBuilder { this.query = request.query(); this.temporalMode = SpiQuery.TemporalMode.of(query); this.disableLazyLoad = query.isDisableLazyLoading(); + this.readOnly = Boolean.TRUE.equals(query.isReadOnly()); this.subQuery = Type.SQ_EXISTS == query.getType() || Type.SQ_IN == query.getType() || Type.ID_LIST == query.getType() @@ -309,13 +312,13 @@ public final class SqlTreeBuilder { if (baseTable == null) { baseTable = desc.baseTable(temporalMode); } - return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, temporalMode, disableLazyLoad, sqlDistinct, baseTable); + return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, temporalMode, disableLazyLoad, readOnly, sqlDistinct, baseTable); } else if (prop instanceof STreePropertyAssocMany) { - return new SqlTreeNodeManyRoot(prefix, (STreePropertyAssocMany) prop, props, myList, withId(), temporalMode, disableLazyLoad); + return new SqlTreeNodeManyRoot(prefix, (STreePropertyAssocMany) prop, props, myList, withId(), temporalMode, disableLazyLoad, readOnly); } else { - return new SqlTreeNodeBean(prefix, prop, props, myList, withId(), temporalMode, disableLazyLoad); + return new SqlTreeNodeBean(prefix, prop, props, myList, withId(), temporalMode, disableLazyLoad, readOnly); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java index aa3cf9d1f..664c5e24f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java @@ -28,6 +28,7 @@ class SqlTreeLoadBean implements SqlTreeLoad { final boolean readId; private final boolean readIdNormal; private final boolean disableLazyLoad; + private final boolean readOnlyNoIntercept; private final InheritInfo inheritInfo; final String prefix; private final Map pathMap; @@ -49,6 +50,7 @@ class SqlTreeLoadBean implements SqlTreeLoad { this.readId = node.readId; this.readIdNormal = readId && !temporalVersions; this.disableLazyLoad = node.disableLazyLoad; + this.readOnlyNoIntercept = disableLazyLoad && node.readOnly; this.partialObject = node.partialObject; this.properties = node.properties; this.pathMap = node.pathMap; @@ -145,7 +147,7 @@ class SqlTreeLoadBean implements SqlTreeLoad { void initBeanType() throws SQLException { localDesc = desc; - localBean = desc.createEntityBean(); + localBean = desc.createEntityBean2(readOnlyNoIntercept); localIdBinder = idBinder; } @@ -282,14 +284,18 @@ class SqlTreeLoadBean implements SqlTreeLoad { boolean forceNewReference = queryMode == Mode.REFRESH_BEAN; for (STreePropertyAssocMany many : localDesc.propsMany()) { if (many != fetchedMany) { - // create a proxy for the many (deferred fetching) - BeanCollection ref = many.createReference(localBean, forceNewReference); - if (ref != null) { - if (disableLazyLoad) { - ref.setDisableLazyLoad(true); - } - if (!ref.isRegisteredWithLoadContext()) { - ctx.register(many.asMany(), ref); + if (readOnlyNoIntercept) { + many.createEmptyReference(localBean); + } else { + // create a proxy for the many (deferred fetching) + BeanCollection ref = many.createReference(localBean, forceNewReference); + if (ref != null) { + if (disableLazyLoad) { + ref.setDisableLazyLoad(true); + } + if (!ref.isRegisteredWithLoadContext()) { + ctx.register(many.asMany(), ref); + } } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java index 621820862..a09e7ad41 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -38,6 +38,7 @@ class SqlTreeNodeBean implements SqlTreeNode { final boolean readId; final boolean readIdNormal; final boolean disableLazyLoad; + final boolean readOnly; final InheritInfo inheritInfo; final String prefix; final Map pathMap; @@ -57,16 +58,16 @@ class SqlTreeNodeBean implements SqlTreeNode { * Construct for leaf node. */ SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, SqlTreeProperties props, - List myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { - this(prefix, beanProp, beanProp.target(), props, myChildren, withId, null, temporalMode, disableLazyLoad); + List myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean readOnly) { + this(prefix, beanProp, beanProp.target(), props, myChildren, withId, null, temporalMode, disableLazyLoad, readOnly); } /** * Construct for root node. */ SqlTreeNodeBean(STreeType desc, SqlTreeProperties props, List myList, boolean withId, - STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { - this(null, null, desc, props, myList, withId, many, temporalMode, disableLazyLoad); + STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean readOnly) { + this(null, null, desc, props, myList, withId, many, temporalMode, disableLazyLoad, readOnly); } /** @@ -74,7 +75,7 @@ class SqlTreeNodeBean implements SqlTreeNode { */ private SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, STreeType desc, SqlTreeProperties props, List myChildren, boolean withId, STreePropertyAssocMany lazyLoadParent, - SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { + SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean readOnly) { this.lazyLoadParent = lazyLoadParent; this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.idBinder(); this.prefix = prefix; @@ -91,6 +92,7 @@ class SqlTreeNodeBean implements SqlTreeNode { this.readId = !aggregationRoot && withId && desc.hasId(); this.readIdNormal = readId && !temporalVersions; this.disableLazyLoad = disableLazyLoad || !readIdNormal || desc.isRawSqlBased(); + this.readOnly = readOnly; this.partialObject = props.isPartialObject(); this.properties = props.getProps(); this.children = myChildren == null ? Collections.emptyList() : myChildren; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index a8b323533..3d3f18292 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -10,8 +10,8 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { final STreePropertyAssocMany manyProp; SqlTreeNodeManyRoot(String prefix, STreePropertyAssocMany prop, SqlTreeProperties props, List myList, - boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { - super(prefix, prop, props, myList, withId, temporalMode, disableLazyLoad); + boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean readOnly) { + super(prefix, prop, props, myList, withId, temporalMode, disableLazyLoad, readOnly); this.manyProp = prop; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java index be58c1938..13ee00b93 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -20,9 +20,9 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean { * Specify for SqlSelect to include an Id property or not. */ SqlTreeNodeRoot(STreeType desc, SqlTreeProperties props, List myList, boolean withId, TableJoin includeJoin, - STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean sqlDistinct, String baseTable) { + STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean readOnly, boolean sqlDistinct, String baseTable) { - super(desc, props, myList, withId, many, temporalMode, disableLazyLoad); + super(desc, props, myList, withId, many, temporalMode, disableLazyLoad, readOnly); this.includeJoin = includeJoin; this.sqlDistinct = sqlDistinct; this.baseTable = baseTable; diff --git a/ebean-core/src/main/resources/META-INF/ebean-version.mf b/ebean-core/src/main/resources/META-INF/ebean-version.mf index 2eb6e1e8e..d95d9c73c 100644 --- a/ebean-core/src/main/resources/META-INF/ebean-version.mf +++ b/ebean-core/src/main/resources/META-INF/ebean-version.mf @@ -1 +1 @@ -ebean-version: 133 +ebean-version: 141 diff --git a/ebean-core/src/test/resources/ebean.mf b/ebean-core/src/test/resources/ebean.mf new file mode 100644 index 000000000..78e35a60f --- /dev/null +++ b/ebean-core/src/test/resources/ebean.mf @@ -0,0 +1 @@ +synthetic: false diff --git a/ebean-externalmapping-xml/src/test/resources/META-INF/ebean-version.mf b/ebean-externalmapping-xml/src/test/resources/META-INF/ebean-version.mf new file mode 100644 index 000000000..d95d9c73c --- /dev/null +++ b/ebean-externalmapping-xml/src/test/resources/META-INF/ebean-version.mf @@ -0,0 +1 @@ +ebean-version: 141 diff --git a/ebean-externalmapping-xml/src/test/resources/ebean.mf b/ebean-externalmapping-xml/src/test/resources/ebean.mf new file mode 100644 index 000000000..78e35a60f --- /dev/null +++ b/ebean-externalmapping-xml/src/test/resources/ebean.mf @@ -0,0 +1 @@ +synthetic: false diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryOrderById.java b/ebean-test/src/test/java/org/tests/query/TestQueryOrderById.java index 5f3ace76a..fb7c002d6 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryOrderById.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryOrderById.java @@ -1,15 +1,25 @@ package org.tests.query; +import io.ebean.bean.EntityBean; +import io.ebean.bean.EntityBeanIntercept; +import io.ebean.bean.InterceptReadOnly; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.Query; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; +import org.tests.model.basic.ResetBasicData; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryOrderById extends BaseTestCase { @Test public void orderById_default_expectNotOrderById() { + ResetBasicData.reset(); Query query = DB.find(Customer.class) .select("id,name") @@ -17,12 +27,20 @@ public class TestQueryOrderById extends BaseTestCase { .setFirstRow(1) .setMaxRows(5); - query.findList(); + query.setReadOnly(true).setDisableLazyLoading(true); + List list = query.findList(); if (isSqlServer() || isDb2()) { assertSql(query).isEqualTo("select t0.id, t0.name from o_customer t0 order by t0.id offset 1 rows fetch next 5 rows only"); } else if (!isOracle()) { assertSql(query).isEqualTo("select t0.id, t0.name from o_customer t0 order by t0.id limit 5 offset 1"); } + + assertThat(list).isNotEmpty(); + Customer customer = list.get(0); + EntityBeanIntercept intercept = ((EntityBean) customer)._ebean_getIntercept(); + assertThat(intercept).isInstanceOf(InterceptReadOnly.class); + assertThat(customer.getOrders()).isSameAs(Collections.EMPTY_LIST); + assertThat(customer.getContacts()).isSameAs(Collections.EMPTY_LIST); } @Test diff --git a/tests/test-kotlin/src/test/resources/META-INF/ebean-version.mf b/tests/test-kotlin/src/test/resources/META-INF/ebean-version.mf new file mode 100644 index 000000000..d95d9c73c --- /dev/null +++ b/tests/test-kotlin/src/test/resources/META-INF/ebean-version.mf @@ -0,0 +1 @@ +ebean-version: 141