001package io.ebean.bean;
002
003import io.ebean.DB;
004import io.ebean.Database;
005import io.ebean.ValuePair;
006
007import javax.persistence.EntityNotFoundException;
008import javax.persistence.PersistenceException;
009import java.io.Serializable;
010import java.math.BigDecimal;
011import java.net.URL;
012import java.util.Arrays;
013import java.util.LinkedHashMap;
014import java.util.LinkedHashSet;
015import java.util.Map;
016import java.util.Set;
017import java.util.concurrent.locks.Lock;
018import java.util.concurrent.locks.ReentrantLock;
019
020/**
021 * This is the object added to every entity bean using byte code enhancement.
022 * <p>
023 * This provides the mechanisms to support deferred fetching of reference beans
024 * and oldValues generation for concurrency checking.
025 * </p>
026 */
027public final class EntityBeanIntercept implements Serializable {
028
029  private static final long serialVersionUID = -3664031775464862649L;
030
031  private static final int STATE_NEW = 0;
032  private static final int STATE_REFERENCE = 1;
033  private static final int STATE_LOADED = 2;
034
035  private transient final ReentrantLock lock = new ReentrantLock();
036
037  private transient NodeUsageCollector nodeUsageCollector;
038
039  private transient PersistenceContext persistenceContext;
040
041  private transient BeanLoader beanLoader;
042
043  private transient PreGetterCallback preGetterCallback;
044
045  private String ebeanServerName;
046
047  private boolean deletedFromCollection;
048
049  /**
050   * The actual entity bean that 'owns' this intercept.
051   */
052  private final EntityBean owner;
053
054  private EntityBean embeddedOwner;
055  private int embeddedOwnerIndex;
056
057  /**
058   * One of NEW, REF, UPD.
059   */
060  private int state;
061
062  private boolean forceUpdate;
063
064  private boolean readOnly;
065
066  private boolean dirty;
067
068  /**
069   * Flag set to disable lazy loading - typically for SQL "report" type entity beans.
070   */
071  private boolean disableLazyLoad;
072
073  /**
074   * Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
075   */
076  private boolean lazyLoadFailure;
077
078  /**
079   * Used when a bean is partially filled.
080   */
081  private static final byte FLAG_LOADED_PROP = 1;
082
083  /**
084   * Set of changed properties.
085   */
086  private static final byte FLAG_CHANGED_PROP = 2;
087
088  /**
089   * Flags indicating if a property is a dirty embedded bean. Used to distinguish
090   * between an embedded bean being completely overwritten and one of its
091   * embedded properties being made dirty.
092   */
093  private static final byte FLAG_EMBEDDED_DIRTY = 4;
094
095  /**
096   * Flags indicating if a property is a dirty embedded bean. Used to distinguish
097   * between an embedded bean being completely overwritten and one of its
098   * embedded properties being made dirty.
099   */
100  private static final byte FLAG_ORIG_VALUE_SET = 8;
101
102  private final byte[] flags;
103
104  private boolean fullyLoadedBean;
105  private boolean loadedFromCache;
106  private Object[] origValues;
107  private Exception[] loadErrors;
108  private int lazyLoadProperty = -1;
109  private Object ownerId;
110  private int sortOrder;
111
112  /**
113   * Create a intercept with a given entity.
114   */
115  public EntityBeanIntercept(Object ownerBean) {
116    this.owner = (EntityBean) ownerBean;
117    this.flags = new byte[owner._ebean_getPropertyNames().length];
118  }
119
120  /**
121   * EXPERIMENTAL - Constructor only for use by serialization frameworks.
122   */
123  public EntityBeanIntercept() {
124    this.owner = null;
125    this.flags = null;
126  }
127
128  /**
129   * Return the 'owning' entity bean.
130   */
131  public EntityBean getOwner() {
132    return owner;
133  }
134
135  /**
136   * Return the persistenceContext.
137   */
138  public PersistenceContext getPersistenceContext() {
139    return persistenceContext;
140  }
141
142  /**
143   * Set the persistenceContext.
144   */
145  public void setPersistenceContext(PersistenceContext persistenceContext) {
146    this.persistenceContext = persistenceContext;
147  }
148
149  /**
150   * Turn on profile collection.
151   */
152  public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
153    this.nodeUsageCollector = usageCollector;
154  }
155
156  /**
157   * Return the ownerId (IdClass).
158   */
159  public Object getOwnerId() {
160    return ownerId;
161  }
162
163  /**
164   * Set the ownerId (IdClass).
165   */
166  public void setOwnerId(Object ownerId) {
167    this.ownerId = ownerId;
168  }
169
170  /**
171   * Return the owning bean for an embedded bean.
172   */
173  public Object getEmbeddedOwner() {
174    return embeddedOwner;
175  }
176
177  /**
178   * Return the property index (for the parent) of this embedded bean.
179   */
180  public int getEmbeddedOwnerIndex() {
181    return embeddedOwnerIndex;
182  }
183
184  /**
185   * Clear the getter callback.
186   */
187  public void clearGetterCallback() {
188    this.preGetterCallback = null;
189  }
190
191  /**
192   * Register the callback to be triggered when getter is called.
193   * This is used primarily to automatically flush the JDBC batch.
194   */
195  public void registerGetterCallback(PreGetterCallback getterCallback) {
196    this.preGetterCallback = getterCallback;
197  }
198
199  /**
200   * Set the embedded beans owning bean.
201   */
202  public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
203    this.embeddedOwner = parentBean;
204    this.embeddedOwnerIndex = embeddedOwnerIndex;
205  }
206
207  /**
208   * Set the BeanLoader with PersistenceContext.
209   */
210  public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
211    this.beanLoader = beanLoader;
212    this.persistenceContext = ctx;
213    this.ebeanServerName = beanLoader.getName();
214  }
215
216  /**
217   * Set the BeanLoader.
218   */
219  public void setBeanLoader(BeanLoader beanLoader) {
220    this.beanLoader = beanLoader;
221    this.ebeanServerName = beanLoader.getName();
222  }
223
224  public boolean isFullyLoadedBean() {
225    return fullyLoadedBean;
226  }
227
228  public void setFullyLoadedBean(boolean fullyLoadedBean) {
229    this.fullyLoadedBean = fullyLoadedBean;
230  }
231
232  /**
233   * Check each property to see if the bean is partially loaded.
234   */
235  public boolean isPartial() {
236    for (byte flag : flags) {
237      if ((flag & FLAG_LOADED_PROP) == 0) {
238        return true;
239      }
240    }
241    return false;
242  }
243
244  /**
245   * Return true if this bean has been directly modified (it has oldValues) or
246   * if any embedded beans are either new or dirty (and hence need saving).
247   */
248  public boolean isDirty() {
249    return dirty;
250  }
251
252  /**
253   * Called by an embedded bean onto its owner.
254   */
255  public void setEmbeddedDirty(int embeddedProperty) {
256    this.dirty = true;
257    setEmbeddedPropertyDirty(embeddedProperty);
258  }
259
260  public void setDirty(boolean dirty) {
261    this.dirty = dirty;
262  }
263
264  /**
265   * Return true if this entity bean is new and not yet saved.
266   */
267  public boolean isNew() {
268    return state == STATE_NEW;
269  }
270
271  /**
272   * Return true if the entity bean is new or dirty (and should be saved).
273   */
274  public boolean isNewOrDirty() {
275    return isNew() || isDirty();
276  }
277
278  /**
279   * Return true if only the Id property has been loaded.
280   */
281  public boolean hasIdOnly(int idIndex) {
282    for (int i = 0; i < flags.length; i++) {
283      if (i == idIndex) {
284        if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
285      } else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
286        return false;
287      }
288    }
289    return true;
290  }
291
292  /**
293   * Return true if the entity is a reference.
294   */
295  public boolean isReference() {
296    return state == STATE_REFERENCE;
297  }
298
299  /**
300   * Set this as a reference object.
301   */
302  public void setReference(int idPos) {
303    state = STATE_REFERENCE;
304    if (idPos > -1) {
305      // For cases where properties are set on constructor
306      // set every non Id property to unloaded (for lazy loading)
307      for (int i = 0; i < flags.length; i++) {
308        if (i != idPos) {
309          flags[i] &= ~FLAG_LOADED_PROP;
310        }
311      }
312    }
313  }
314
315  /**
316   * Set true when the bean has been loaded from L2 bean cache.
317   * The effect of this is that we should skip the cache if there
318   * is subsequent lazy loading (bean cache partially populated).
319   */
320  public void setLoadedFromCache(boolean loadedFromCache) {
321    this.loadedFromCache = loadedFromCache;
322  }
323
324  /**
325   * Return true if this bean was loaded from L2 bean cache.
326   */
327  public boolean isLoadedFromCache() {
328    return loadedFromCache;
329  }
330
331  /**
332   * Return true if the bean should be treated as readOnly. If a setter method
333   * is called when it is readOnly an Exception is thrown.
334   */
335  public boolean isReadOnly() {
336    return readOnly;
337  }
338
339  /**
340   * Set the readOnly status. If readOnly then calls to setter methods through
341   * an exception.
342   */
343  public void setReadOnly(boolean readOnly) {
344    this.readOnly = readOnly;
345  }
346
347  /**
348   * Set the bean to be updated when persisted (for merge).
349   */
350  public void setForceUpdate(boolean forceUpdate) {
351    this.forceUpdate = forceUpdate;
352  }
353
354  /**
355   * Return true if the entity should be updated.
356   */
357  public boolean isUpdate() {
358    return forceUpdate || state == STATE_LOADED;
359  }
360
361  /**
362   * Return true if the entity has been loaded.
363   */
364  public boolean isLoaded() {
365    return state == STATE_LOADED;
366  }
367
368  /**
369   * Set the bean into NEW state.
370   */
371  public void setNew() {
372    this.state = STATE_NEW;
373  }
374
375  /**
376   * Set the loaded state to true.
377   * <p>
378   * Calls to setter methods after the bean is loaded can result in
379   * 'Old Values' being created.
380   * <p>
381   * Worth noting that this is also set after a insert/update. By doing so it
382   * 'resets' the bean for making further changes and saving again.
383   */
384  public void setLoaded() {
385    this.state = STATE_LOADED;
386    this.owner._ebean_setEmbeddedLoaded();
387    this.lazyLoadProperty = -1;
388    this.origValues = null;
389    for (int i = 0; i < flags.length; i++) {
390      flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
391    }
392    this.dirty = false;
393  }
394
395  /**
396   * When finished loading for lazy or refresh on an already partially populated bean.
397   */
398  public void setLoadedLazy() {
399    this.state = STATE_LOADED;
400    this.lazyLoadProperty = -1;
401  }
402
403  /**
404   * Set lazy load failure flag.
405   */
406  public void setLazyLoadFailure(Object ownerId) {
407    this.lazyLoadFailure = true;
408    this.ownerId = ownerId;
409  }
410
411  /**
412   * Return true if the bean is marked as having failed lazy loading.
413   */
414  public boolean isLazyLoadFailure() {
415    return lazyLoadFailure;
416  }
417
418  /**
419   * Return true if lazy loading is disabled.
420   */
421  public boolean isDisableLazyLoad() {
422    return disableLazyLoad;
423  }
424
425  /**
426   * Set true to turn off lazy loading.
427   */
428  public void setDisableLazyLoad(boolean disableLazyLoad) {
429    this.disableLazyLoad = disableLazyLoad;
430  }
431
432  /**
433   * Set the loaded status for the embedded bean.
434   */
435  public void setEmbeddedLoaded(Object embeddedBean) {
436    if (embeddedBean instanceof EntityBean) {
437      EntityBean eb = (EntityBean) embeddedBean;
438      eb._ebean_getIntercept().setLoaded();
439    }
440  }
441
442  /**
443   * Return true if the embedded bean is new or dirty and hence needs saving.
444   */
445  public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
446    if (embeddedBean == null) {
447      // if it was previously set then the owning bean would
448      // have oldValues containing the previous embedded bean
449      return false;
450    }
451    if (embeddedBean instanceof EntityBean) {
452      return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty();
453    } else {
454      // non-enhanced so must assume it is new and needs to be saved
455      return true;
456    }
457  }
458
459  /**
460   * Return the original value that was changed via an update.
461   */
462  public Object getOrigValue(int propertyIndex) {
463    if (origValues == null) {
464      return null;
465    }
466    return origValues[propertyIndex];
467  }
468
469  /**
470   * Finds the index position of a given property. Returns -1 if the
471   * property can not be found.
472   */
473  public int findProperty(String propertyName) {
474    String[] names = owner._ebean_getPropertyNames();
475    for (int i = 0; i < names.length; i++) {
476      if (names[i].equals(propertyName)) {
477        return i;
478      }
479    }
480    return -1;
481  }
482
483  /**
484   * Return the property name for the given property.
485   */
486  public String getProperty(int propertyIndex) {
487    if (propertyIndex == -1) {
488      return null;
489    }
490    return owner._ebean_getPropertyName(propertyIndex);
491  }
492
493  /**
494   * Return the number of properties.
495   */
496  public int getPropertyLength() {
497    return owner._ebean_getPropertyNames().length;
498  }
499
500  /**
501   * Set the loaded state of the property given it's name.
502   */
503  public void setPropertyLoaded(String propertyName, boolean loaded) {
504    int position = findProperty(propertyName);
505    if (position == -1) {
506      throw new IllegalArgumentException("Property " + propertyName + " not found");
507    }
508    if (loaded) {
509      flags[position] |= FLAG_LOADED_PROP;
510    } else {
511      flags[position] &= ~FLAG_LOADED_PROP;
512    }
513  }
514
515  /**
516   * Set the property to be treated as unloaded. Used for properties initialised in default constructor.
517   */
518  public void setPropertyUnloaded(int propertyIndex) {
519    flags[propertyIndex] &= ~FLAG_LOADED_PROP;
520  }
521
522  /**
523   * Set the property to be loaded.
524   */
525  public void setLoadedProperty(int propertyIndex) {
526    flags[propertyIndex] |= FLAG_LOADED_PROP;
527  }
528
529  /**
530   * Set all properties to be loaded (post insert).
531   */
532  public void setLoadedPropertyAll() {
533    for (int i = 0; i < flags.length; i++) {
534      flags[i] |= FLAG_LOADED_PROP;
535    }
536  }
537
538  /**
539   * Return true if the property is loaded.
540   */
541  public boolean isLoadedProperty(int propertyIndex) {
542    return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
543  }
544
545  /**
546   * Return true if the property is considered changed.
547   */
548  public boolean isChangedProperty(int propertyIndex) {
549    return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
550  }
551
552  /**
553   * Return true if the property was changed or if it is embedded and one of its
554   * embedded properties is dirty.
555   */
556  public boolean isDirtyProperty(int propertyIndex) {
557    return (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
558  }
559
560  /**
561   * Explicitly mark a property as having been changed.
562   */
563  public void markPropertyAsChanged(int propertyIndex) {
564    setChangedProperty(propertyIndex);
565    setDirty(true);
566  }
567
568  public void setChangedProperty(int propertyIndex) {
569    flags[propertyIndex] |= FLAG_CHANGED_PROP;
570  }
571
572  /**
573   * Set that an embedded bean has had one of its properties changed.
574   */
575  private void setEmbeddedPropertyDirty(int propertyIndex) {
576    flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
577  }
578
579  private void setOriginalValue(int propertyIndex, Object value) {
580    if (origValues == null) {
581      origValues = new Object[owner._ebean_getPropertyNames().length];
582    }
583    if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
584      flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
585      origValues[propertyIndex] = value;
586    }
587  }
588
589  /**
590   * Set old value but force it to be set regardless if it already has a value.
591   */
592  private void setOriginalValueForce(int propertyIndex, Object value) {
593    if (origValues == null) {
594      origValues = new Object[owner._ebean_getPropertyNames().length];
595    }
596    origValues[propertyIndex] = value;
597  }
598
599  /**
600   * For forced update on a 'New' bean set all the loaded properties to changed.
601   */
602  public void setNewBeanForUpdate() {
603    for (int i = 0; i < flags.length; i++) {
604      if ((flags[i] & FLAG_LOADED_PROP) != 0) {
605        flags[i] |= FLAG_CHANGED_PROP;
606      }
607    }
608    setDirty(true);
609  }
610
611  /**
612   * Return the set of property names for a partially loaded bean.
613   */
614  public Set<String> getLoadedPropertyNames() {
615    if (fullyLoadedBean) {
616      return null;
617    }
618    Set<String> props = new LinkedHashSet<>();
619    for (int i = 0; i < flags.length; i++) {
620      if ((flags[i] & FLAG_LOADED_PROP) != 0) {
621        props.add(getProperty(i));
622      }
623    }
624    return props;
625  }
626
627  /**
628   * Return the array of flags indicating the dirty properties.
629   */
630  public boolean[] getDirtyProperties() {
631    int len = getPropertyLength();
632    boolean[] dirties = new boolean[len];
633    for (int i = 0; i < len; i++) {
634      // this, or an embedded property has been changed - recurse
635      dirties[i] = (flags[i] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
636    }
637    return dirties;
638  }
639
640  /**
641   * Return the set of dirty properties.
642   */
643  public Set<String> getDirtyPropertyNames() {
644    Set<String> props = new LinkedHashSet<>();
645    addDirtyPropertyNames(props, null);
646    return props;
647  }
648
649  /**
650   * Recursively add dirty properties.
651   */
652  public void addDirtyPropertyNames(Set<String> props, String prefix) {
653    int len = getPropertyLength();
654    for (int i = 0; i < len; i++) {
655      if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
656        // the property has been changed on this bean
657        props.add((prefix == null ? getProperty(i) : prefix + getProperty(i)));
658      } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
659        // an embedded property has been changed - recurse
660        EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
661        embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i) + ".");
662      }
663    }
664  }
665
666  /**
667   * Return true if any of the given property names are dirty.
668   */
669  public boolean hasDirtyProperty(Set<String> propertyNames) {
670    String[] names = owner._ebean_getPropertyNames();
671    int len = getPropertyLength();
672    for (int i = 0; i < len; i++) {
673      if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
674        if (propertyNames.contains(names[i])) {
675          return true;
676        }
677      } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
678        if (propertyNames.contains(names[i])) {
679          return true;
680        }
681      }
682    }
683    return false;
684  }
685
686  /**
687   * Return a map of dirty properties with their new and old values.
688   */
689  public Map<String, ValuePair> getDirtyValues() {
690    Map<String, ValuePair> dirtyValues = new LinkedHashMap<>();
691    addDirtyPropertyValues(dirtyValues, null);
692    return dirtyValues;
693  }
694
695  /**
696   * Recursively add dirty properties.
697   */
698  public void addDirtyPropertyValues(Map<String, ValuePair> dirtyValues, String prefix) {
699    int len = getPropertyLength();
700    for (int i = 0; i < len; i++) {
701      if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
702        // the property has been changed on this bean
703        String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
704        Object newVal = owner._ebean_getField(i);
705        Object oldVal = getOrigValue(i);
706        if (notEqual(oldVal, newVal)) {
707          dirtyValues.put(propName, new ValuePair(newVal, oldVal));
708        }
709      } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
710        // an embedded property has been changed - recurse
711        EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
712        embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
713      }
714    }
715  }
716
717  /**
718   * Recursively add dirty properties.
719   */
720  public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
721    int len = getPropertyLength();
722    for (int i = 0; i < len; i++) {
723      if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
724        // the property has been changed on this bean
725        Object newVal = owner._ebean_getField(i);
726        Object oldVal = getOrigValue(i);
727        if (notEqual(oldVal, newVal)) {
728          visitor.visit(i, newVal, oldVal);
729        }
730      } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
731        // an embedded property has been changed - recurse
732        EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
733        visitor.visitPush(i);
734        embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor);
735        visitor.visitPop();
736      }
737    }
738  }
739
740  /**
741   * Return a dirty property hash taking into account embedded beans.
742   */
743  public StringBuilder getDirtyPropertyKey() {
744    StringBuilder sb = new StringBuilder();
745    addDirtyPropertyKey(sb);
746    return sb;
747  }
748
749  /**
750   * Add and return a dirty property hash.
751   */
752  private void addDirtyPropertyKey(StringBuilder sb) {
753    if (sortOrder > 0) {
754      sb.append("s,");
755    }
756    int len = getPropertyLength();
757    for (int i = 0; i < len; i++) {
758      if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
759        sb.append(i).append(',');
760      } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
761        // an embedded property has been changed - recurse
762        EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
763        sb.append(i).append('[');
764        embeddedBean._ebean_getIntercept().addDirtyPropertyKey(sb);
765        sb.append(']');
766      }
767    }
768  }
769
770  /**
771   * Return a loaded property hash.
772   */
773  public StringBuilder getLoadedPropertyKey() {
774    StringBuilder sb = new StringBuilder();
775    int len = getPropertyLength();
776    for (int i = 0; i < len; i++) {
777      if (isLoadedProperty(i)) {
778        sb.append(i).append(',');
779      }
780    }
781    return sb;
782  }
783
784  public boolean[] getLoaded() {
785    boolean[] ret = new boolean[flags.length];
786    for (int i = 0; i < ret.length; i++) {
787      ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
788    }
789    return ret;
790  }
791
792  /**
793   * Return the index of the property that triggered the lazy load.
794   */
795  public int getLazyLoadPropertyIndex() {
796    return lazyLoadProperty;
797  }
798
799  /**
800   * Return the property that triggered the lazy load.
801   */
802  public String getLazyLoadProperty() {
803    return getProperty(lazyLoadProperty);
804  }
805
806  /**
807   * Load the bean when it is a reference.
808   */
809  protected void loadBean(int loadProperty) {
810    lock.lock();
811    try {
812      if (beanLoader == null) {
813        final Database database = DB.byName(ebeanServerName);
814        if (database == null) {
815          throw new PersistenceException("Database [" + ebeanServerName + "] was not found?");
816        }
817        // For stand alone reference bean or after deserialisation lazy load
818        // using the ebeanServer. Synchronise only on the bean.
819        loadBeanInternal(loadProperty, database.getPluginApi().beanLoader());
820        return;
821      }
822    } finally {
823      lock.unlock();
824    }
825    final Lock lock = beanLoader.lock();
826    try {
827      // Lazy loading using LoadBeanContext which supports batch loading
828      // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
829      loadBeanInternal(loadProperty, beanLoader);
830    } finally {
831      lock.unlock();
832    }
833  }
834
835  /**
836   * Invoke the lazy loading. This method is synchronised externally.
837   */
838  private void loadBeanInternal(int loadProperty, BeanLoader loader) {
839    if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
840      // race condition where multiple threads calling preGetter concurrently
841      return;
842    }
843    if (lazyLoadFailure) {
844      // failed when batch lazy loaded by another bean in the batch
845      throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
846    }
847    if (lazyLoadProperty == -1) {
848      lazyLoadProperty = loadProperty;
849      if (nodeUsageCollector != null) {
850        nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty));
851      }
852      loader.loadBean(this);
853      if (lazyLoadFailure) {
854        // failed when lazy loading this bean
855        throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
856      }
857      // bean should be loaded and intercepting now. setLoaded() has
858      // been called by the lazy loading mechanism
859    }
860  }
861
862  /**
863   * Helper method to check if two objects are equal.
864   */
865  @SuppressWarnings({"unchecked", "rawtypes"})
866  protected static boolean notEqual(Object obj1, Object obj2) {
867    if (obj1 == null) {
868      return (obj2 != null);
869    }
870    if (obj2 == null) {
871      return true;
872    }
873    if (obj1 == obj2) {
874      return false;
875    }
876    if (obj1 instanceof BigDecimal) {
877      // Use comparable for BigDecimal as equals
878      // uses scale in comparison...
879      if (obj2 instanceof BigDecimal) {
880        Comparable com1 = (Comparable) obj1;
881        return (com1.compareTo(obj2) != 0);
882      } else {
883        return true;
884      }
885    }
886    if (obj1 instanceof URL) {
887      // use the string format to determine if dirty
888      return !obj1.toString().equals(obj2.toString());
889    }
890    return !obj1.equals(obj2);
891  }
892
893  /**
894   * Called when a BeanCollection is initialised automatically.
895   */
896  public void initialisedMany(int propertyIndex) {
897    flags[propertyIndex] |= FLAG_LOADED_PROP;
898  }
899
900  private void preGetterCallback(int propertyIndex) {
901    PreGetterCallback preGetterCallback = this.preGetterCallback;
902    if (preGetterCallback != null) {
903      preGetterCallback.preGetterTrigger(propertyIndex);
904    }
905  }
906
907  /**
908   * Called prior to Id property getter.
909   */
910  public void preGetId() {
911    preGetterCallback(-1);
912  }
913
914  /**
915   * Method that is called prior to a getter method on the actual entity.
916   */
917  public void preGetter(int propertyIndex) {
918    preGetterCallback(propertyIndex);
919    if (state == STATE_NEW || disableLazyLoad) {
920      return;
921    }
922    if (!isLoadedProperty(propertyIndex)) {
923      loadBean(propertyIndex);
924    }
925    if (nodeUsageCollector != null) {
926      nodeUsageCollector.addUsed(getProperty(propertyIndex));
927    }
928  }
929
930  /**
931   * OneToMany and ManyToMany only set loaded state.
932   */
933  public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
934    if (state == STATE_NEW) {
935      setLoadedProperty(propertyIndex);
936    } else {
937      if (readOnly) {
938        throw new IllegalStateException("This bean is readOnly");
939      }
940      setChangedProperty(propertyIndex);
941    }
942  }
943
944  private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
945    if (readOnly) {
946      throw new IllegalStateException("This bean is readOnly");
947    }
948    setChangedProperty(propertyIndex);
949    if (setDirtyState) {
950      setOriginalValue(propertyIndex, origValue);
951      setDirtyStatus();
952    }
953  }
954
955  private void setDirtyStatus() {
956    if (!dirty) {
957      dirty = true;
958      if (embeddedOwner != null) {
959        // Cascade dirty state from Embedded bean to parent bean
960        embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
961      }
962      if (nodeUsageCollector != null) {
963        nodeUsageCollector.setModified();
964      }
965    }
966  }
967
968  /**
969   * Check to see if the values are not equal. If they are not equal then create
970   * the old values for use with ConcurrencyMode.ALL.
971   */
972  public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
973    if (state == STATE_NEW) {
974      setLoadedProperty(propertyIndex);
975    } else if (notEqual(oldValue, newValue)) {
976      setChangedPropertyValue(propertyIndex, intercept, oldValue);
977    }
978  }
979
980
981  /**
982   * Check for primitive boolean.
983   */
984  public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
985    if (state == STATE_NEW) {
986      setLoadedProperty(propertyIndex);
987    } else if (oldValue != newValue) {
988      setChangedPropertyValue(propertyIndex, intercept, oldValue);
989    }
990  }
991
992  /**
993   * Check for primitive int.
994   */
995  public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
996    if (state == STATE_NEW) {
997      setLoadedProperty(propertyIndex);
998    } else if (oldValue != newValue) {
999      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1000    }
1001  }
1002
1003  /**
1004   * long.
1005   */
1006  public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
1007    if (state == STATE_NEW) {
1008      setLoadedProperty(propertyIndex);
1009    } else if (oldValue != newValue) {
1010      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1011    }
1012  }
1013
1014  /**
1015   * double.
1016   */
1017  public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
1018    if (state == STATE_NEW) {
1019      setLoadedProperty(propertyIndex);
1020    } else if (Double.compare(oldValue, newValue) != 0) {
1021      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1022    }
1023  }
1024
1025  /**
1026   * float.
1027   */
1028  public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
1029    if (state == STATE_NEW) {
1030      setLoadedProperty(propertyIndex);
1031    } else if (Float.compare(oldValue, newValue) != 0) {
1032      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1033    }
1034  }
1035
1036  /**
1037   * short.
1038   */
1039  public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
1040    if (state == STATE_NEW) {
1041      setLoadedProperty(propertyIndex);
1042    } else if (oldValue != newValue) {
1043      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1044    }
1045  }
1046
1047  /**
1048   * char.
1049   */
1050  public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
1051    if (state == STATE_NEW) {
1052      setLoadedProperty(propertyIndex);
1053    } else if (oldValue != newValue) {
1054      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1055    }
1056  }
1057
1058  /**
1059   * byte.
1060   */
1061  public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
1062    if (state == STATE_NEW) {
1063      setLoadedProperty(propertyIndex);
1064    } else if (oldValue != newValue) {
1065      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1066    }
1067  }
1068
1069  /**
1070   * char[].
1071   */
1072  public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
1073    if (state == STATE_NEW) {
1074      setLoadedProperty(propertyIndex);
1075    } else if (!Arrays.equals(oldValue, newValue)) {
1076      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1077    }
1078  }
1079
1080  /**
1081   * byte[].
1082   */
1083  public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
1084    if (state == STATE_NEW) {
1085      setLoadedProperty(propertyIndex);
1086    } else if (!Arrays.equals(oldValue, newValue)) {
1087      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1088    }
1089  }
1090
1091  /**
1092   * Explicitly set an old value with force (the old value is forced even it is already set).
1093   */
1094  public void setOldValue(int propertyIndex, Object oldValue) {
1095    setChangedProperty(propertyIndex);
1096    setOriginalValueForce(propertyIndex, oldValue);
1097    setDirtyStatus();
1098  }
1099
1100  /**
1101   * Return the sort order value for an order column.
1102   */
1103  public int getSortOrder() {
1104    return sortOrder;
1105  }
1106
1107  /**
1108   * Set the sort order value for an order column.
1109   */
1110  public void setSortOrder(int sortOrder) {
1111    this.sortOrder = sortOrder;
1112  }
1113
1114  /**
1115   * Set if the entity was deleted from a BeanCollection.
1116   */
1117  public void setDeletedFromCollection(final boolean deletedFromCollection) {
1118    this.deletedFromCollection = deletedFromCollection;
1119  }
1120
1121  public boolean isOrphanDelete() {
1122    return deletedFromCollection && !isNew();
1123  }
1124
1125  /**
1126   * Set the load error that happened on this property.
1127   */
1128  public void setLoadError(int propertyIndex, Exception t) {
1129    if (loadErrors == null) {
1130      loadErrors = new Exception[owner._ebean_getPropertyNames().length];
1131    }
1132    loadErrors[propertyIndex] = t;
1133    flags[propertyIndex] |= FLAG_LOADED_PROP;
1134  }
1135
1136  /**
1137   * Returns the loadErrors.
1138   */
1139  public Map<String, Exception> getLoadErrors() {
1140    if (loadErrors == null) {
1141      return null;
1142    }
1143    Map<String, Exception> ret = null;
1144    int len = getPropertyLength();
1145    for (int i = 0; i < len; i++) {
1146      Exception loadError = loadErrors[i];
1147      if (loadError != null) {
1148        if (ret == null) {
1149          ret = new LinkedHashMap<>();
1150        }
1151        ret.put(getProperty(i), loadError);
1152      }
1153    }
1154    return ret;
1155  }
1156}