All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebean.bean.EntityBeanIntercept Maven / Gradle / Ivy

The newest version!
package com.avaje.ebean.bean;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;

import com.avaje.ebean.Ebean;

/**
 * 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 = -3664031775464862648L; private transient NodeUsageCollector nodeUsageCollector; private transient PropertyChangeSupport pcs; private transient PersistenceContext persistenceContext; private transient BeanLoader beanLoader; private int beanLoaderIndex; private String ebeanServerName; /** * The actual entity bean that 'owns' this intercept. */ private EntityBean owner; /** * The parent bean by relationship (1-1 or 1-M). */ private Object parentBean; /** * true if the bean properties have been loaded. false if it is a reference * bean (will lazy load etc). */ private volatile boolean loaded; /** * 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; /** * Set true when loaded or reference. Used to bypass interception when created * by user code. */ private boolean intercepting; /** * The state of the Bean (DEFAULT,UDPATE,READONLY,SHARED). */ private boolean readOnly; /** * The bean as it was before it was modified. Null if no non-transient setters * have been called. */ private Object oldValues; /** * Used when a bean is partially filled. */ private volatile Set loadedProps; /** * Set of changed properties. */ private HashSet changedProps; private String lazyLoadProperty; /** * Create a intercept with a given entity. *

* Refer to agent ProxyConstructor. *

*/ public EntityBeanIntercept(Object owner) { this.owner = (EntityBean) owner; } /** * Copy the internal state of the intercept to another intercept. */ public void copyStateTo(EntityBeanIntercept dest) { dest.loadedProps = loadedProps; dest.ebeanServerName = ebeanServerName; if (loaded) { dest.setLoaded(); } } /** * Return the 'owning' entity bean. */ public EntityBean getOwner() { return owner; } public String toString() { if (!loaded) { return "Reference..."; } return "OldValues: " + oldValues; } /** * Return the persistenceContext. */ public PersistenceContext getPersistenceContext() { return persistenceContext; } /** * Set the persistenceContext. */ public void setPersistenceContext(PersistenceContext persistenceContext) { this.persistenceContext = persistenceContext; } /** * Add a property change listener for this entity bean. */ public void addPropertyChangeListener(PropertyChangeListener listener) { if (pcs == null) { pcs = new PropertyChangeSupport(owner); } pcs.addPropertyChangeListener(listener); } /** * Add a property change listener for this entity bean for a specific * property. */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { if (pcs == null) { pcs = new PropertyChangeSupport(owner); } pcs.addPropertyChangeListener(propertyName, listener); } /** * Remove a property change listener for this entity bean. */ public void removePropertyChangeListener(PropertyChangeListener listener) { if (pcs != null) { pcs.removePropertyChangeListener(listener); } } /** * Remove a property change listener for this entity bean for a specific * property. */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { if (pcs != null) { pcs.removePropertyChangeListener(propertyName, listener); } } /** * Turn on profile collection. */ public void setNodeUsageCollector(NodeUsageCollector usageCollector) { this.nodeUsageCollector = usageCollector; } /** * Return the parent bean (by relationship). */ public Object getParentBean() { return parentBean; } /** * Special case for a OneToOne, Set the parent bean (by relationship). This is * the owner of a 1-1. */ public void setParentBean(Object parentBean) { this.parentBean = parentBean; } /** * Return the index position for batch loading via BeanLoader. */ public int getBeanLoaderIndex() { return beanLoaderIndex; } /** * Set Lazy Loading by ebeanServerName. *

* This is for reference beans created by themselves. *

*/ public void setBeanLoaderByServerName(String ebeanServerName) { this.beanLoaderIndex = 0; this.beanLoader = null; this.ebeanServerName = ebeanServerName; } /** * Set the BeanLoader for general lazy loading. */ public void setBeanLoader(int index, BeanLoader beanLoader, PersistenceContext ctx) { this.beanLoaderIndex = index; this.beanLoader = beanLoader; this.persistenceContext = ctx; this.ebeanServerName = beanLoader.getName(); } /** * 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 (oldValues != null) { return true; } // need to check all the embedded beans return owner._ebean_isEmbeddedNewOrDirty(); } /** * Return true if this entity bean is new and not yet saved. */ public boolean isNew() { return !intercepting && !loaded; } /** * Return true if the entity bean is new or dirty (and should be saved). */ public boolean isNewOrDirty() { return isNew() || isDirty(); } /** * Return true if the entity is a reference. */ public boolean isReference() { return intercepting && !loaded; } /** * Set this as a reference object. */ public void setReference() { this.loaded = false; this.intercepting = true; } /** * Return the old values used for ConcurrencyMode.ALL. */ public Object getOldValues() { return oldValues; } /** * Return true if the bean should be treated as readOnly. If a setter method * is called when it is readOnly an Exception is thrown. */ public boolean isReadOnly() { return readOnly; } /** * Set the readOnly status. If readOnly then calls to setter methods through * an exception. */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } /** * Return true if the bean currently has interception on. *

* With interception on the bean will invoke lazy loading and dirty checking. *

*/ public boolean isIntercepting() { return intercepting; } /** * Turn interception off or on. *

* This is to support custom serialisation mechanisms that just read all the * properties on the bean. *

* */ public void setIntercepting(boolean intercepting) { this.intercepting = intercepting; } /** * Return true if the entity has been loaded. */ public boolean isLoaded() { return loaded; } /** * Set the loaded state to true. *

* Calls to setter methods after the bean is loaded can result in 'Old Values' * being created to support ConcurrencyMode.ALL *

*

* 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.loaded = true; this.oldValues = null; this.intercepting = true; this.owner._ebean_setEmbeddedLoaded(); this.lazyLoadProperty = null; this.changedProps = null; } /** * When finished loading for lazy or refresh on an already partially populated * bean. */ public void setLoadedLazy() { this.loaded = true; this.intercepting = true; this.lazyLoadProperty = null; } /** * Mark this bean as having failed lazy loading due to the underlying row * being deleted. *

* We mark the bean this way rather than immediately fail as we might be batch * lazy loading and this bean might not be used by the client code at all. * Instead we will fail as soon as the client code tries to use this bean. *

*/ public void setLazyLoadFailure() { this.lazyLoadFailure = true; } /** * Return true if the bean is marked as having failed lazy loading. */ public boolean isLazyLoadFailure() { return lazyLoadFailure; } /** * Return true if lazy loading is disabled. */ public boolean isDisableLazyLoad() { return disableLazyLoad; } /** * Set true to turn off lazy loading. *

* Typically used to disable lazy loading on SQL based report beans. *

*/ public void setDisableLazyLoad(boolean disableLazyLoad) { this.disableLazyLoad = disableLazyLoad; } /** * Set the loaded status for the embedded bean. */ public void setEmbeddedLoaded(Object embeddedBean) { if (embeddedBean instanceof EntityBean) { EntityBean eb = (EntityBean) embeddedBean; eb._ebean_getIntercept().setLoaded(); } } /** * Return true if the embedded bean is new or dirty and hence needs saving. */ public boolean isEmbeddedNewOrDirty(Object embeddedBean) { if (embeddedBean == null) { // if it was previously set then the owning bean would // have oldValues containing the previous embedded bean return false; } if (embeddedBean instanceof EntityBean) { return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty(); } else { // non-enhanced so must assume it is new and needs to be saved return true; } } /** * Set the property names for a partially loaded bean. * * @param loadedPropertyNames * the names of the loaded properties */ public void setLoadedProps(Set loadedPropertyNames) { this.loadedProps = loadedPropertyNames; } /** * Return the set of property names for a partially loaded bean. */ public Set getLoadedProps() { return loadedProps; } /** * Return the set of property names for changed properties. */ public Set getChangedProps() { return changedProps; } /** * Return the property read or write that triggered the lazy load. */ public String getLazyLoadProperty() { return lazyLoadProperty; } /** * Load the bean when it is a reference. */ protected void loadBean(String loadProperty) { synchronized (this) { if (beanLoader == null) { BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName); if (serverLoader == null) { throw new PersistenceException("Server [" + ebeanServerName + "] was not found?"); } // For stand alone reference bean or after deserialisation lazy load // using the ebeanServer. Synchronise only on the bean. loadBeanInternal(loadProperty, serverLoader); return; } } synchronized (beanLoader) { // Lazy loading using LoadBeanContext which supports batch loading // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree') loadBeanInternal(loadProperty, beanLoader); } } /** * Invoke the lazy loading. This method is synchronised externally. */ private void loadBeanInternal(String loadProperty, BeanLoader loader) { if (loaded && (loadedProps == null || loadedProps.contains(loadProperty))) { // race condition where multiple threads calling preGetter concurrently return; } if (disableLazyLoad) { loaded = true; return; } if (lazyLoadFailure) { // failed when batch lazy loaded by another bean in the batch throw new EntityNotFoundException("Bean has been deleted - lazy loading failed"); } if (lazyLoadProperty == null) { lazyLoadProperty = loadProperty; if (nodeUsageCollector != null) { nodeUsageCollector.setLoadProperty(lazyLoadProperty); } loader.loadBean(this); if (lazyLoadFailure) { // failed when lazy loading this bean throw new EntityNotFoundException("Bean has been deleted - lazy loading failed"); } // bean should be loaded and intercepting now. setLoaded() has // been called by the lazy loading mechanism } } /** * Create a copy of the bean as it is now. This is the original or 'old * values' prior to any modification. This is used to perform concurrency * testing. */ protected void createOldValues() { oldValues = owner._ebean_createCopy(); if (nodeUsageCollector != null) { nodeUsageCollector.setModified(); } } /** * This is ONLY used for subclass entity beans. *

* This is not used when entity bean classes are enhanced via javaagent or ant * etc - only when a subclass is generated. *

* Returns a Serializable instance that is either the 'byte code generated' * object or a 'Vanilla' copy of this bean depending on * SerializeControl.isVanillaBeans(). */ public Object writeReplaceIntercept() throws ObjectStreamException { if (!SerializeControl.isVanillaBeans()) { return owner; } // creates a plain vanilla object and // copies the values from the owner return owner._ebean_createCopy(); } /** * Helper method to check if two objects are equal. */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected boolean areEqual(Object obj1, Object obj2) { if (obj1 == null) { return (obj2 == null); } if (obj2 == null) { return false; } if (obj1 == obj2) { return true; } if (obj1 instanceof BigDecimal) { // Use comparable for BigDecimal as equals // uses scale in comparison... if (obj2 instanceof BigDecimal) { Comparable com1 = (Comparable) obj1; return (com1.compareTo(obj2) == 0); } else { return false; } } if (obj1 instanceof URL) { // use the string format to determine if dirty return obj1.toString().equals(obj2.toString()); } return obj1.equals(obj2); } /** * Method that is called prior to a getter method on the actual entity. *

* This checks if the bean is a reference and should be loaded. *

*/ public void preGetter(String propertyName) { if (!intercepting) { return; } if (!loaded) { loadBean(propertyName); } else if (loadedProps != null && !loadedProps.contains(propertyName)) { loadBean(propertyName); } if (nodeUsageCollector != null && loaded) { nodeUsageCollector.addUsed(propertyName); } } /** * Called for "enhancement" postSetter processing. This is around a PUTFIELD * so no need to check the newValue afterwards. */ public void postSetter(PropertyChangeEvent event) { if (pcs != null && event != null) { pcs.firePropertyChange(event); } } /** * Called for "subclassed" postSetter processing. Here the newValue has to be * re-fetched (and passed into this method) in case there is code inside the * setter that further mutates the value. */ public void postSetter(PropertyChangeEvent event, Object newValue) { if (pcs != null && event != null) { if (newValue != null && newValue.equals(event.getNewValue())) { pcs.firePropertyChange(event); } else { pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue); } } } /** * OneToMany and ManyToMany don't have any interception so just check for * PropertyChangeSupport. */ public PropertyChangeEvent preSetterMany(boolean interceptField, String propertyName, Object oldValue, Object newValue) { // skip setter interception on many's if (pcs != null) { return new PropertyChangeEvent(owner, propertyName, oldValue, newValue); } else { return null; } } private final void addDirty(String propertyName) { if (!intercepting) { return; } if (readOnly) { throw new IllegalStateException("This bean is readOnly"); } if (loaded) { if (oldValues == null) { // first time this bean is being made dirty createOldValues(); } if (changedProps == null) { changedProps = new HashSet(); } changedProps.add(propertyName); } } /** * 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 PropertyChangeEvent preSetter(boolean intercept, String propertyName, Object oldValue, Object newValue) { boolean changed = !areEqual(oldValue, newValue); if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, oldValue, newValue); } return null; } /** * Check for primitive boolean. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, boolean oldValue, boolean newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); } return null; } /** * Check for primitive int. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, int oldValue, int newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue)); } return null; } /** * long. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, long oldValue, long newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Long.valueOf(oldValue), Long.valueOf(newValue)); } return null; } /** * double. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, double oldValue, double newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Double.valueOf(oldValue), Double.valueOf(newValue)); } return null; } /** * float. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, float oldValue, float newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Float.valueOf(oldValue), Float.valueOf(newValue)); } return null; } /** * short. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, short oldValue, short newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Short.valueOf(oldValue), Short.valueOf(newValue)); } return null; } /** * char. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, char oldValue, char newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Character.valueOf(oldValue), Character.valueOf(newValue)); } return null; } /** * char. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, byte oldValue, byte newValue) { boolean changed = oldValue != newValue; if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, Byte.valueOf(oldValue), Byte.valueOf(newValue)); } return null; } /** * char[]. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, char[] oldValue, char[] newValue) { boolean changed = !areEqualChars(oldValue, newValue); if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, oldValue, newValue); } return null; } /** * byte[]. */ public PropertyChangeEvent preSetter(boolean intercept, String propertyName, byte[] oldValue, byte[] newValue) { boolean changed = !areEqualBytes(oldValue, newValue); if (intercept && changed) { addDirty(propertyName); } if (changed && pcs != null) { return new PropertyChangeEvent(owner, propertyName, oldValue, newValue); } return null; } private static boolean areEqualBytes(byte[] b1, byte[] b2) { if (b1 == null) { return (b2 == null); } else if (b2 == null) { return false; } else if (b1 == b2) { return true; } else if (b1.length != b2.length) { return false; } for (int i = 0; i < b1.length; i++) { if (b1[i] != b2[i]) { return false; } } return true; } private static boolean areEqualChars(char[] b1, char[] b2) { if (b1 == null) { return (b2 == null); } else if (b2 == null) { return false; } else if (b1 == b2) { return true; } else if (b1.length != b2.length) { return false; } for (int i = 0; i < b1.length; i++) { if (b1[i] != b2[i]) { return false; } } return true; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy