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