Files
Brainstorm/Foundation/src/com/foundation/common/Entity.java

2727 lines
130 KiB
Java
Raw Normal View History

2014-05-30 10:31:51 -07:00
/*
* Copyright (c) 2002,2009 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.foundation.common;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import com.common.metadata.MetadataContainer;
2014-05-30 10:31:51 -07:00
import com.common.orb.*;
import com.common.util.*;
import com.common.util.json.IJsonContext;
import com.common.util.json.JSONArray;
import com.common.util.json.JSONException;
import com.common.util.json.JSONObject;
import com.common.util.json.JSONString;
2014-05-30 10:31:51 -07:00
import com.common.util.optimized.IIntIterator;
import com.common.util.optimized.IntHashSet;
import com.common.comparison.Comparator;
import com.common.debug.*;
import com.common.exception.MethodNotSupportedException;
import com.common.thread.*;
import com.foundation.attribute.*;
import com.foundation.application.IApplication;
import com.foundation.clone.*;
import com.foundation.event.*;
import com.foundation.event.model.ModelListener;
import com.foundation.exception.TransactionException;
import com.foundation.transaction.ReadSingleTransaction;
import com.foundation.transaction.Transaction;
import com.foundation.transaction.TransactionContextHolder;
import com.foundation.transaction.TransactionErrorInfo;
import com.foundation.transaction.TransactionService;
import com.foundation.transaction.IObjectAccess;
import com.foundation.transaction.TransactionContext;
import com.foundation.metadata.Attribute;
import com.foundation.metadata.CloneContext;
import com.foundation.metadata.Event;
import com.foundation.metadata.LoadAttributesContext;
import com.foundation.metadata.TypeCallbackHandler;
import com.foundation.metadata.MetadataService;
import com.foundation.metadata.ISupportsContainment;
import com.foundation.metadata.TypeMetadata;
import com.foundation.metadata.TypeRepositoryMetadata;
import com.foundation.model.LogicalObjectChangeLog;
import com.foundation.model.VersionedModel;
import com.foundation.util.IKeyExtractor;
import com.foundation.util.ITrackedCollection;
public abstract class Entity implements IEntity, ICloneable {
static final long serialVersionUID = -7739741506125052482L;
public static final byte CONTEXT_TRUSTED = AttributeSupport.CONTEXT_TRUSTED;
public static final byte CONTEXT_UNTRUSTED = AttributeSupport.CONTEXT_UNTRUSTED;
/**
* All attribute values are considered referenced unless they are specified as AO_PART_OF.
* Collections will be considered PART_OF the entity, but their collected values will not.
* Referenced values do not share a common lock, and they must follow certain design rules.
* See the documentation for more information on the reference relationship design rules.
* <p>AO_REFERENCED and AO_PART_OF of may not both be specified.</p>
* @see #AO_PART_OF
*/
public static final int AO_REFERENCED = AttributeSupport.OPTION_REFERENCED;
/**
* Notifies the attribute system that the referenced attribute value to be considered part of this entity.
* If the value is a collection then the collection and all collected values will be considered part of the entity.
* A value that is part of this entity will share the same lock object. This can only be used with certain relationship patterns.
* See the documentation for more information on the part of relationship design rules.
* <p>AO_PART_OF and AO_REFERENCED may not both be specified.</p>
* @see #AO_REFERENCED
*/
public static final int AO_PART_OF = AttributeSupport.OPTION_PART_OF;
/**
* Attributes flagged as lazy lets the attribute system know to call the entity's lazyLoadAttribute method to load the attribute value on demand.
* This flag is very useful when dealing with collections, or when using weak references.
*/
public static final int AO_LAZY = AttributeSupport.OPTION_LAZY;
/**
* An attribute flagged as weak tells the attribute system to wrapper the value in a weak reference.
* If the system garbage collects the value then it will be lazily initialized if the attribute is lazily initialized, otherwise the value will be marked as unassigned (null will be returned from the get method).
*/
public static final int AO_WEAK = AttributeSupport.OPTION_WEAK;
/**
* A collection attribute flagged as mapped indicates that a mapping class must be used when adding and removing values from the collection.
* The foundation code will call the entity's createCollectionMap method to allow customized mapping between entities to occur.
*/
public static final int AO_MAPPED = AttributeSupport.OPTION_MAPPED;
/**
* Whether the attribute value is to not be serialized automatically with the rest of the attributes.
* The attribute value can still be serialized manually by overriding the writeExternal methods defined by Entity and serializing the attribute's value.
*/
public static final int AO_TRANSIENT = AttributeSupport.OPTION_TRANSIENT;
/**
* Whether the attribute value is not to be reflected at all.
* The attribute value can still be initialized with a lazy loader, otherwise it will be null.
*/
public static final int AO_NO_REFLECT = AttributeSupport.OPTION_NO_REFLECT;
/**
* Whether the attribute value is treated like an immutable object reference. The reflections will reference the original value held by the reflected object.
*/
public static final int AO_REFLECT_AS_IMMUTABLE = AttributeSupport.OPTION_REFLECT_AS_IMMUTABLE;
/**
* Whether the attribute value is calculated.
* Calculated attributes register listeners in the calculatedAttributeRegister method and calculate their values when the calculatedAttributeUpdate method is called.
*/
public static final int AO_CALCULATED = AttributeSupport.OPTION_CALCULATED;
/**
* Whether the attribute is to be included in the object's key. If this is the top object in a logical object then the key may be used to identify the logical object between multiple instances of the logical object on multiple processes. If this is part of a logical object the key may be used to identify the object within a collection.
*/
public static final int AO_KEY = AttributeSupport.OPTION_KEY;
/** Indicates no model controller should manage this model directly. */
public static final int AO_MANAGEMENT_TYPE_NONE = TypeMetadata.MANAGEMENT_TYPE_NONE;
/** Indicates the model controller should load all instances. */
public static final int AO_MANAGEMENT_TYPE_LOAD_ALL = TypeMetadata.MANAGEMENT_TYPE_LOAD_ALL;
/** Indicates the model controller should load instances as requested (weakly referenced such that they are disposed of when not used). */
public static final int AO_MANAGEMENT_TYPE_LOAD_AS_NEEDED = TypeMetadata.MANAGEMENT_TYPE_LOAD_AS_NEEDED;
/** Indicates the model controller manage objects (may or may not come from a repository) without sync'ing with other processes. */
public static final int AO_MANAGEMENT_TYPE_LOAD_ALL_LOCAL_ONLY = TypeMetadata.MANAGEMENT_TYPE_LOAD_ALL_LOCAL_ONLY;
/** Indicates the model controller should load instances as requested (weakly referenced such that they are disposed of when not used) without sync'ing with other processes. */
public static final int AO_MANAGEMENT_TYPE_LOAD_AS_NEEDED_LOCAL_ONLY = TypeMetadata.MANAGEMENT_TYPE_LOAD_AS_NEEDED_LOCAL_ONLY;
/** The callback handler which will manage the lazy loading of attributes as well as getting and setting the monitor. */
protected static final TypeCallbackHandler TYPE_CALLBACK_HANDLER = new TypeCallbackHandler() {
public Object lazyLoadAttribute(Object instance, Attribute attribute) {
try {
return ((Entity) instance).lazyLoadAttribute(attribute);
}//try//
catch(Throwable e) {
Debug.log("Unhandled exception caught within the lazyLoadAttribute(int) method.", e);
return null;
}//catch//
}//lazyLoadAttribute()//
public ModelListener calculatedAttributeRegister(Object instance, Attribute attribute, IEntity.ICalculatedAttributeListener listener) {
try {
return ((Entity) instance).calculatedAttributeRegister(attribute, listener);
}//try//
catch(Throwable e) {
Debug.log("Unhandled exception caught within the calculatedAttributeRegister(int, ICalculatedAttributeListener) method.", e);
return null;
}//catch//
}//calculatedAttributeRegister()//
public Object calculatedAttributeUpdate(Object instance, Attribute attribute) {
try {
return ((Entity) instance).calculatedAttributeUpdate(attribute);
}//try//
catch(Throwable e) {
Debug.log("Unhandled exception caught within the calculatedAttributeUpdate(int) method.", e);
return null;
}//catch//
}//calculatedAttributeUpdate()//
public Monitor getMonitor(Object instance) {
return ((Entity) instance).getMonitor();
}//getMonitor()//
public void setMonitor(Object instance, IEntity parent, boolean isPartOf) {
if(parent == null) {
if(((Entity) instance).getPartOfEntityCounter() != 0) {
((Entity) instance).setPartOfEntityCounter(((Entity) instance).getPartOfEntityCounter() - 1);
//Notify the parent if this object has changed.//
if(((Entity) instance).getAttributeSupport().getVirtualObjectChangeCounter() > 0) {
TypeCallbackHandler handler = MetadataService.getSingleton().getTypeMetadata(((Entity) instance).getPartOfEntity().getClass()).getTypeCallbackHandler();
handler.updateVirtualObjectChangeCounter(((Entity) instance).getPartOfEntity(), false, 1);
}//if//
if(((Entity) instance).getPartOfEntityCounter() == 0) {
//Don't do this.. we will keep them linked to avoid threading problems.`
//((Entity) instance).setPartOfEntity(null);
}//if//
}//if//
}//if//
else if(((Entity) instance).getPartOfEntity() == parent) { //The parent references this object multiple times as part-of, so track that with a counter.//
((Entity) instance).setPartOfEntityCounter(((Entity) instance).getPartOfEntityCounter() + 1);
//Notify the parent if this object has changed.//
//Note: We are notifying again since the counter has incremented. The counter tracks the number of times the parent references this object in a part-of fashion.//
if(((Entity) instance).getAttributeSupport().getVirtualObjectChangeCounter() > 0) {
TypeCallbackHandler handler = MetadataService.getSingleton().getTypeMetadata(parent.getClass()).getTypeCallbackHandler();
handler.updateVirtualObjectChangeCounter(parent, true, 1);
}//if//
}//else if//
else if(((Entity) instance).getPartOfEntity() != null) {
if(((Entity) instance).getMonitor() != parent.getMonitor()) {
Debug.log(new RuntimeException("Error: Invalid monitor state: An entity cannot be contained by more than one object or collection."));
//TODO: Throw a custom exception and catch it in the code that is setting up the containment.//
}//if//
}//else if//
else { //Make this object part of the parent object.//
if((parent instanceof VersionedModel) && ((VersionedModel) parent).getLogicalObjectId() != null) {
if(instance instanceof VersionedModel) {
//Copy the logical object id from the parent to the children within the logical object.//
((Entity) instance).setAttributeValue(VersionedModel.LOGICAL_OBJECT_ID, ((VersionedModel) parent).getLogicalObjectId());
}//if//
else {
throw new RuntimeException("Cannot make a non-VersionedModel part-of a VersionedModel based logical object. VersionedModel provides code for repository based versioning allowing multiple instances of a logical object to be in memory at one time - however all objects in the logical object must extend VerionedModel.");
}//else//
}//if//
((Entity) instance).setPartOfEntityCounter(1);
((Entity) instance).setPartOfEntity(parent);
//Notify the parent if this object has changed.//
if(((Entity) instance).getAttributeSupport().getVirtualObjectChangeCounter() > 0) {
TypeCallbackHandler handler = MetadataService.getSingleton().getTypeMetadata(parent.getClass()).getTypeCallbackHandler();
handler.updateVirtualObjectChangeCounter(parent, true, 1);
}//if//
}//else//
}//setMonitor()//
public AttributeSupport getAttributeSupport(Object instance) {
return ((Entity) instance).attributeSupport;
}//getAttributeSupport()//
public void preClone(Object instance, MetadataContainer metadata, CloneContext cloneContext) {
((Entity) instance).getAttributeSupport().preClone(metadata, cloneContext);
}//preClone()//
public ISupportsContainment clone(Object instance, MetadataContainer metadata, CloneContext cloneContext) {
Entity result = null;
//Load a previous clone in this cloning context.//
result = (Entity) cloneContext.getClone(instance);
if(result == null) {
//Create a shallow copy of the entity.//
result = (Entity) ((Entity) instance).clone();
//Clone attribute values that are part of this entity and reference the clones. Also clear key attributes, repository bindings, and transient attributes.//
result.getAttributeSupport().completeClone(cloneContext, metadata, ((Entity) instance).getAttributeSupport());
}//if//
return result;
}//clone()//
public void loadAttributes(Object instance, LoadAttributesContext context) {
((Entity) instance).getAttributeSupport().loadAttributes(context);
}//loadAttributes()//
public void postLoadAttributes(Object instance, LoadAttributesContext context) {
((Entity) instance).getAttributeSupport().postLoadAttributes(context);
}//postLoadAttributes()//
public boolean getIsVirtualObjectChanged(Object instance) {
return ((Entity) instance).getIsVirtualObjectChanged();
}//getIsVirtualObjectChanged()//
public void resetVirtualObjectChangeFlags(Object instance) {
((Entity) instance).resetVirtualObjectChangeFlags();
}//resetVirtualObjectChangeFlags()//
public void reverseVirtualObjectChanges(Object instance) {
((Entity) instance).reverseVirtualObjectChanges();
}//reverseVirtualObjectChanges()//
public void updateVirtualObjectChangeCounter(Object instance, boolean isChanged, int referenceCount) {
((Entity) instance).getAttributeSupport().updateVirtualObjectChangeCounter(isChanged, referenceCount);
}//updateVirtualObjectChangeCounter()//
public boolean getIsVirtualObjectAltered(Object instance) {
return ((Entity) instance).getIsVirtualObjectAltered();
}//getIsVirtualObjectAltered()//
public void resetVirtualObjectAlteredFlags(Object instance) {
((Entity) instance).resetVirtualObjectAlteredFlags();
}//resetVirtualObjectAlteredFlags()//
public void updateVirtualObjectAlteredCounter(Object instance, boolean isAltered, int referenceCount) {
((Entity) instance).getAttributeSupport().updateVirtualObjectAlteredCounter(isAltered, referenceCount);
}//updateVirtualObjectAlteredCounter()//
/* (non-Javadoc)
* @see com.foundation.metadata.TypeCallbackHandler#setMaintainWeakLinkBackReferences(java.lang.Object)
*/
public void setMaintainWeakLinkBackReferences(Object instance) {
//Never called.//
}//setMaintainWeakLinkBackReferences()//
/* (non-Javadoc)
* @see com.foundation.metadata.TypeCallbackHandler#addWeakLinkBackReference(java.lang.Object, java.lang.Object)
*/
public void addWeakLinkBackReference(Object instance, Object collection) {
((Entity) instance).weakLinkBackReferenceRootNode = new BackReferenceNode(collection, ((Entity) instance).weakLinkBackReferenceRootNode);
}//addWeakLinkBackReference()//
/* (non-Javadoc)
* @see com.foundation.metadata.TypeCallbackHandler#removeWeakLinkBackReference(java.lang.Object, java.lang.Object)
*/
public void removeWeakLinkBackReference(Object instance, Object collection) {
BackReferenceNode previous = null;
BackReferenceNode next = ((Entity) instance).weakLinkBackReferenceRootNode;
while(next != null && next.getReference() != collection) {
previous = next;
next = next.getNext();
}//while//
if(next != null) {
if(previous != null) {
previous.setNext(next.getNext());
}//if//
else {
((Entity) instance).weakLinkBackReferenceRootNode = next.getNext();
}//else//
}//if//
}//removeWeakLinkBackReference()//
};//TypeCallbackHandler//
static {
//Initialize the type callback handler for this ISupportsContainment class.//
MetadataService.getSingleton().setTypeCallbackHandler(Entity.class, TYPE_CALLBACK_HANDLER);
}//static//
/** The support object managing the registered event listeners and handling firing of events by this instance. */
private transient EventSupport eventSupport = null;
/** The support object managing the attributes for this instance. */
private transient AttributeSupport attributeSupport = null;
/** The access object for the attribute support; providing access to normally protected methods. */
private transient AttributeSupport.AttributeSupportAccess attributeSupportAccess = null;
/** The root node in the linked list of back references used when the object is referenced by a collection that is referenced by an entity using the part-of and weak flags. */
private BackReferenceNode weakLinkBackReferenceRootNode = null;
/** The entity key, normally built automatically using attribute metadata. Uniquely identifies the model in memory. */
//Note: see just below the contructors for the methods that were developed to go with this. The code was never finished as it wasn't needed at the time. Keys will be required to reference objects within a logical object.
//private transient EntityKey key = null;
/**
* Used to provide a transaction service with intimate access to all entity objects.
*/
public static class TransactionServiceAccessObject implements IObjectAccess {
public TransactionServiceAccessObject() {
}//TransactionServiceAccessObject()//
public Object getAttributeValue(Object entity, int attributeIdentifier) {
return ((Entity) entity).getAttributeSupport().getAttributeValue(attributeIdentifier);
}//getAttributeValue()//
public void setAttributeValue(Object entity, int attributeIdentifier, Object value, boolean updateReflections) {
((Entity) entity).getAttributeSupportAccess().setAttributeValue(((Entity) entity).getAttributeSupport(), attributeIdentifier, value, CONTEXT_TRUSTED, updateReflections);
}//setAttributeValue()//
public Object getRepositoryIdentifier(Object entity) {
return ((Entity) entity).attributeSupport.getRepositoryIdentifier();
}//getRepositoryIdentifier()//
public void setRepositoryIdentifier(Object entity, Object repositoryIdentifier) {
((Entity) entity).attributeSupport.setRepositoryIdentifier(repositoryIdentifier);
}//setRepositoryIdentifier()//
public Object newInstance(Class type) throws InstantiationException, IllegalAccessException {
if(type.isAssignableFrom(Entity.class)) {
return type.newInstance();
}//if//
else {
return null;
}//else//
}//newInstance()//
public boolean getIsAttributeRealized(Object entity, int attributeIdentifier) {
return ((Entity) entity).getAttributeSupport().getIsAttributeRealized(attributeIdentifier);
}//getIsAttributeRealized()//
public boolean getIsAttributeChanged(Object entity, int attributeIdentifier) {
return ((Entity) entity).getAttributeSupport().getIsAttributeChanged(attributeIdentifier);
}//getIsAttributeChanged()//
public void resetChangeFlags(Object entity) {
((Entity) entity).resetObjectChangeFlags();
}//resetChangeFlags()//
public AttributeSupport.AttributeState getAttributeState(Object entity) {
return ((Entity) entity).getAttributeSupport().getState();
}//getAttributeState()//
public Object clone(Object entity) {
return ((Entity) entity).clone();
}//getAttributeState()//
public void refreshBoundAttributes(Object entity, boolean updateReflections) {
((Entity) entity).getAttributeSupport().refreshBoundAttributes(updateReflections);
}//refreshBoundAttributes()//
}//TransactionServiceAccessObject//
/**
* Entity constructor.
*/
public Entity() {
super();
eventSupport = new EventSupport(this);
attributeSupport = new AttributeSupport(this, eventSupport);
attributeSupportAccess = attributeSupport.getAttributeSupportAccess();
//Note: There are three basic places where this is called from: 1) The app as it creates new entities for sharing or querying; 2) The database which uses clone() which also registers the listeners; 3) The reflection context which uses newInstance() to call this constructor.//
//The problem with calling this here is that if the listener lazy loads things then it can create obvious problems (db queries before the object has been initialized, lazy loads of empty collections where there should be full ones from the db, etc.//
//This cannot be removed though since the listeners must be registered before the model is used.//
attributeSupport.registerCalculatedAttributeListeners();
}//Entity()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityGetKey()
*/
public Object entityGetKey() {
TypeMetadata typeMetadata = getAttributeSupport().getTypeMetadata();
int keyCount = typeMetadata.getKeyAttributeCount();
Object result = null;
if(keyCount == 1) {
result = getAttributeSupport().getAttributeValue(typeMetadata.getKeyAttributeMetadata(0).getNumber());
}//if//
else if(keyCount > 1) {
Object[] array = new Object[keyCount];
for(int index = 0; index < keyCount; index++) {
array[index] = getAttributeSupport().getAttributeValue(typeMetadata.getKeyAttributeMetadata(index).getNumber());
}//for//
result = new CompositeEntityKey(array);
}//else if//
else {
Debug.log(new RuntimeException("The class " + getClass().getName() + " does not define any key attributes. This will likely cause a failure in the application."));
}//else//
return result;
}//entityGetKey()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entitySetKey(java.lang.Object)
*/
public void entitySetKey(Object key) {
AttributeSupport attributeSupport = getAttributeSupport();
TypeMetadata typeMetadata = attributeSupport.getTypeMetadata();
int keyCount = typeMetadata.getKeyAttributeCount();
int attributeCount = attributeSupport.getAttributeCount();
//Ensure this is a blank entity.//
for(int index = 0; index < attributeCount; index++) {
if(attributeSupport.getIsAttributeRealized(index)) {
throw new RuntimeException("Cannot initialize an entity using a key unless no attributes have been previously assigned values.");
}//if//
}//for//
if(keyCount == 1) {
setAttributeValue(typeMetadata.getKeyAttributeMetadata(0).getAttributeIdentifier(), key);
}//if//
else if(key instanceof CompositeEntityKey && ((CompositeEntityKey) key).getPartCount() == keyCount) {
CompositeEntityKey composite = (CompositeEntityKey) key;
for(int index = 0; index < keyCount; index++) {
setAttributeValue(typeMetadata.getKeyAttributeMetadata(index).getAttributeIdentifier(), composite.getPart(index));
}//for//
}//else if//
else {
throw new RuntimeException("Invalid key detected.");
}//else//
}//entitySetKey()//
/**
* Clones this entity. This method requires that the entity declare a (public?) default constructor.
* <p>NOTE: This is a shallow copy. All attributes are copied by reference including part-of attributes. This is not a safe copy due to the part-of attributes needing to also be copied or no longer referenced, and should only be used as part of a safe clone, or by the transaction service (which expects no part-of attributes to be set - query by example).<p>
* @return The cloned entity instance.
*/
protected Object clone() {
Entity clone = null;
try {
clone = (Entity) super.clone();
clone.eventSupport = new EventSupport(clone);
clone.attributeSupport = (AttributeSupport) attributeSupport.clone(clone, clone.eventSupport);
clone.attributeSupportAccess = clone.attributeSupport.getAttributeSupportAccess();
clone.attributeSupport.registerCalculatedAttributeListeners();
}//try//
catch(CloneNotSupportedException e) {
Debug.log("Caught while trying to clone an entity.", e);
}//catch//
return clone;
}//clone()//
/* (non-Javadoc)
* @see com.foundation.clone.ICloneable#cloneObject(com.foundation.common.MetadataContainer)
*/
public Object cloneObject(CloneContext context, MetadataContainer metadata) {
Object result = context == null ? null : context.getClone(this);
if(result == null) {
boolean release = context == null;
if(context == null) {
context = TYPE_CALLBACK_HANDLER.createCloneContext();
}//if//
try {
if(metadata == null) {
metadata = EMPTY_METADATA;
}//if//
lock(this);
getAttributeSupport().suspendCalculatedAttributeUpdates(true);
try {
TYPE_CALLBACK_HANDLER.preClone(this, metadata, context);
result = TYPE_CALLBACK_HANDLER.clone(this, metadata, context);
}//try//
finally {
getAttributeSupport().resumeCalculatedAttributeUpdates();
unlock(this);
}//finally//
}//try//
finally {
if(release) {
context.release();
}//if//
else if(result != null) {
//Debug.log("Cloned " + this.getClass().getName() + " hash: " + StringSupport.toHexString(this.hashCode()));
context.setClone(this, result);
}//else if//
}//finally//
}//if//
return result;
}//cloneObject()//
/**
* Forces the entire logical object starting with this entity to be loaded into memory.
* This method navigates all part-of references to ensure that all are in memory.
* @param runnable The optional runnable that will be run prior to re-enabling weak references in the tree. If attributes are listed as weak then they may be removed from memory at any time while not being used. This is not necessary in reflections since they don't honor the weak flag on the attributes.
*/
public void loadLogicalObject(Runnable runnable) {
lock(this);
try {
LoadAttributesContext context = new LoadAttributesContext(this, AttributeSupport.LOAD_ATTRIBUTE_OPTION_EXCLUDE_CALCULATED_ATTRIBUTES | AttributeSupport.LOAD_ATTRIBUTE_OPTION_EXCLUDE_STANDARD_ATTRIBUTES | AttributeSupport.LOAD_ATTRIBUTE_OPTION_EXCLUDE_COLLECTION_ATTRIBUTES | AttributeSupport.LOAD_ATTRIBUTE_OPTION_EXCLUDE_BACK_REFERENCES | AttributeSupport.LOAD_ATTRIBUTE_OPTION_EXCLUDE_BOUND_ATTRIBUTES | AttributeSupport.LOAD_ATTRIBUTE_OPTION_TRAVERSE_LOGICAL_OBJECT);
if(runnable != null) {
runnable.run();
}//if//
context.release();
}//try//
finally {
unlock(this);
}//finally//
}//loadLogicalObject()//
/* {Non-Javadoc}
* @see com.common.comparison.IComparable#compareTo(Object)
*/
public int compareTo(Object object) {
return equalsEquals(object) ? 0 : hashCode() - object.hashCode();
}//compareTo()//
/**
* Creates a collection mapping object for an attribute marked as a mapped collection.
* <p>Subclasses should override this method AND likely also the lazyLoadAttribute(..) method if they define mapped collection attributes.
* This method will be called to automate updating, creating, and deleting the mappings in the database, but not to load them from the database.
* They should contain a series of if/else if statements for each mapped collection, and should call the super classes' method if they don't handle the request.</p>
* <p><b>Warning: Value may be null in which case the result will be used to query for all mapped objects.</b></p>
* @param attribute The identifier for the mapped collection attribute.
* @param value The collection value requiring a map object. This value may be null when the caller wants to query for all mappings.
* @return A map object that can be used to save or remove the mapping in the repository.
*/
protected Entity createCollectionMap(Attribute attribute, Object value) {
return null;
}//createCollectionMap()//
/**
* Prepares for a transaction by providing the objects involved an opportunity to prepare.
* @param value The value to be prepared. This may be either an Entity, or an ITrackedCollection.
* @param attributeNumber The attribute number from which the value was derived. This is used to get attribute metadata.
* @param isDelete Whether the attribute value is being deleted.
* @return Whether the transaction should proceed.
*/
private boolean prepTransaction(Object value, int attributeNumber, boolean isDelete) {
boolean result = true;
boolean isCollection = attributeSupport.getIsCollection(attributeNumber);
boolean isMapped = attributeSupport.getIsAttributeMapped(attributeNumber);
if(isCollection) {
if(!isMapped && value instanceof ITrackedCollection) {
ITrackedCollection collection = (ITrackedCollection) value;
LiteList removedValues = new LiteList(100, 1000);
LiteList addedValues = new LiteList(100, 1000);
collection.getCollectionChanges(addedValues, removedValues);
if(isDelete) {
for(int index = 0, length = removedValues.getSize(); result && index < length; index++) {
Object next = removedValues.get(index);
if(next instanceof Entity) {
if(((Entity) next).isObjectRepositoryBound() && !((Entity) next).getIsObjectNew()) {
result = ((Entity) next).prepTransaction(true);
}//if//
}//if//
else {
//TODO: Don't know what to do.
}//else//
}//for//
for(int index = 0, length = addedValues.getSize(); result && index < length; index++) {
Object next = addedValues.get(index);
if(next instanceof Entity) {
if(((Entity) next).isObjectRepositoryBound() && !((Entity) next).getIsObjectNew()) {
result = ((Entity) next).prepTransaction(true);
}//if//
}//if//
else {
//TODO: Don't know what to do.
}//else//
}//for//
}//if//
else {
for(int index = 0, length = removedValues.getSize(); result && index < length; index++) {
Object next = removedValues.get(index);
if(next instanceof Entity) {
if(((Entity) next).isObjectRepositoryBound() && !((Entity) next).getIsObjectNew()) {
result = ((Entity) next).prepTransaction(true);
}//if//
}//if//
else {
//TODO: Don't know what to do.
}//else//
}//for//
for(int index = 0, length = addedValues.getSize(); result && index < length; index++) {
Object next = addedValues.get(index);
if(next instanceof Entity) {
if(((Entity) next).isObjectRepositoryBound()) {
result = ((Entity) next).prepTransaction(false);
}//if//
}//if//
else {
//TODO: Don't know what to do.
}//else//
}//for//
}//else//
}//if//
}//if//
else if(value instanceof Entity) {
result = ((Entity) value).prepTransaction(isDelete);
}//else if//
return result;
}//prepTransaction()//
/**
* Prepares this entity for being part of a transaction.
* @param isDelete Whether the transaction is a deletion.
* @return Whether the transaction should continue.
*/
public final boolean prepTransaction(boolean isDelete) {
AttributeSupport attributeSupport = getAttributeSupport();
int attributeCount = attributeSupport.getAttributeCount();
boolean result = true;
boolean isNew = attributeSupport.getIsObjectNew(); //New or not storeable in a repository.//
boolean checkChildren = true;
//Don't apply changes to a reflection to the repository.//
if((getAttributeSupport().getIsObjectRepositoryBound()) && (!isReflection())) {
if(isNew && !isDelete) {
result = prepEntityCreate();
}//if//
else if(!isDelete) {
result = prepEntityUpdate();
}//else if//
else if(!isNew) {
result = prepEntityDelete();
}//else if//
else {
checkChildren = false;
}//else//
if(checkChildren) {
//Update or create all objects that are a part of this object, and then update again if necessary.//
for(int index = 0; (result) && (index < attributeCount); index++) {
boolean isMapped = attributeSupport.getIsAttributeMapped(index);
boolean isPartOf = attributeSupport.getIsAttributePartOf(index);
//If the attribute is either "part of" this object, or it is a mapped collection, and it had a value, then update the value
if((isPartOf || isMapped) && (attributeSupport.getIsAttributeRealized(index))) {
Object value = getAttributeSupport().getAttributeValue(index);
if(isDelete) {
//Delete the old value, not the new one, if the value has changed.//
if(attributeSupport.getIsAttributeChanged(index)) {
value = attributeSupport.getOldAttributeValue(index);
}//if//
result = prepTransaction(value, index, true);
}//if//
else {
//Update or create the attribute value.//
result = prepTransaction(value, index, false);
//If the value had changed then delete the old value.//
if((isPartOf) && (result) && (attributeSupport.getIsAttributeChanged(index))) {
Object oldValue = attributeSupport.getOldAttributeValue(index);
result = prepTransaction(oldValue, index, true);
}//if//
}//else//
}//if//
}//for//
}//if//
}//if//
return result;
}//prepTransaction()//
/**
* Prepares the entity for a create transaction.
*/
protected boolean prepEntityCreate() {
return true;
}//prepEntityCreate()//
/**
* Prepares the entity for an update transaction.
*/
protected boolean prepEntityUpdate() {
return true;
}//prepEntityUpdate()//
/**
* Prepares the entity for a delete transaction.
*/
protected boolean prepEntityDelete() {
return true;
}//prepEntityDelete()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityDelete()
*/
public TransactionErrorInfo entityDelete() {
TransactionErrorInfo result = null;
if(!isReflection()) {
result = entityDeleteCustomize(-1);
}//if//
return result;
}//entityDelete()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityDelete(int)
*/
public TransactionErrorInfo entityDelete(int debugLevel) {
TransactionErrorInfo result = null;
if(!isReflection()) {
result = entityDeleteCustomize(debugLevel);
}//if//
return result;
}//entityDelete()//
/**
* Kicks off the entity delete process.
* <p>Can be overwritten to provide a different repository O/R mapping system, or to provide custom non-repository storage changes (eg: file based storage).</p>
* @param debugLevel The TransactionService.DEBUG_xxx identifier specifying the level of debugging for the transaction. This may be -1 to use the default value in the TransactionService.
* @return The result of the operation, or null if the operation was a success.
*/
protected TransactionErrorInfo entityDeleteCustomize(int debugLevel) {
TransactionContextHolder contextHolder = null;
TransactionErrorInfo result = null;
//Note: Don't check for isNew here since there may be contained objects that are in the db? - This doesn't seem like it should be possible.//
//Don't apply changes to a reflection to the repository.//
if(getAttributeSupport().getIsObjectRepositoryBound()) {
try {
//Create a transactional context.//
contextHolder = TransactionContextHolder.getInstance(null);
//Prepare for the transaction.//
prepTransaction(true);
//Set the custom debug level. The context will handle values outside the valid range as not having a custom debug level.//
contextHolder.setDebugLevel(debugLevel);
//Perform the deletion.//
result = entityDelete(contextHolder);
//Commit or rollback the transaction.//
if(result != null || !contextHolder.commit()) {
if(result == null) {
result = ENTITY_TRANSACTION_COMMIT_FAILED;
}//if//
if(!contextHolder.rollback()) {
Debug.log("Error: Failed to rollback the transaction. The repository may be in an unexpected state (repository specific behavior unknown).");
}//if//
}//if//
}//try//
finally {
//Make sure we always close the transaction.//
if(contextHolder != null) {
contextHolder.close();
}//if//
}//finally//
}//if//
return result;
}//entityDeleteCustomize()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityDelete(com.foundation.common.Entity.TransactionContextHolder)
*/
public TransactionErrorInfo entityDelete(TransactionContextHolder context) {
AttributeSupport attributeSupport = getAttributeSupport();
int attributeCount = attributeSupport.getAttributeCount();
TransactionErrorInfo result = null;
boolean isNew = attributeSupport.getIsObjectNew(); //New or not storeable in a repository.//
//Don't apply changes to a reflection to the repository.//
if((getAttributeSupport().getIsObjectRepositoryBound()) && (!isReflection())) {
//If the object is not new then we can delete it.//
if(!isNew) {
for(int index = 0; (result == null) && (index < attributeCount); index++) {
boolean isMapped = attributeSupport.getIsAttributeMapped(index);
boolean isPartOf = attributeSupport.getIsAttributePartOf(index);
//If the attribute is either "part of" this object, or it is a mapped collection, and it had a value, then delete the value
if(isPartOf || isMapped) {
//If the value had changed then delete the old value if possible.//
if(attributeSupport.getIsAttributeChanged(index)) {
Object value = attributeSupport.getOldAttributeValue(index);
if(value != null) {
result = entityDelete(context, value, index);
}//if//
}//if//
else {
Object value = getAttributeSupport().getAttributeValue(index); //Lazy load the value if necessary.//
if(value != null) {
result = entityDelete(context, value, index);
}//if//
}//else//
}//if//
}//for//
}//if//
//Delete this object.//
if((result == null) && (!isNew)) {
result = internalEntityDelete(context);
}//if//
}//if//
return result;
}//entityDelete()//
/**
* Deletes the object in the default repository.
* Note that this method may be called for collection values that are not part of this entity since they could be mapped, requiring the mappings to be deleted.
* <p><b>This method may only be used to perform single instance deletions in the repository.</b></p>
* @param context The transactional context within which the delete is occuring. All repository interactions must be through this context so that they can roll back or commit as one unit.
* @param value The attribute value to be deleted.
* @return The result of the operation. A null value indicates success. This will report success if the transaction was unnecessary.
*/
protected TransactionErrorInfo entityDelete(TransactionContextHolder contextHolder, Object value, int attributeNumber) {
AttributeSupport attributeSupport = getAttributeSupport();
TransactionErrorInfo result = null;
boolean isCollection = attributeSupport.getIsCollection(attributeNumber);
boolean isPartOf = attributeSupport.getIsAttributePartOf(attributeNumber);
if(value != null) {
//Make sure we have a local non-proxy value.//
if(Orb.isProxy(value)) {
if(Orb.isLocal(value)) {
value = Orb.getLocal(value);
}//if//
else {
//TODO: Improve the quality of this error.
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
value = null;
Debug.log("Error: Cannot assign a remote value to an attribute marked as PART OF. Part of indicates the value is a closely bound component of the declarer and thus is not a remote proxy.");
}//else//
}//if//
//If the value is an entity then delete it, if it is a collection then delete all collected values, otherwise do nothing (shouldn't happen).//
if((result == null) && (isPartOf) && (value instanceof Entity)) {
result = ((Entity) value).entityDelete(contextHolder);
}//if//
else if((result == null) && (isCollection) && (value instanceof ITrackedCollection)) {
boolean isMapped = attributeSupport.getIsAttributeMapped(attributeNumber);
ITrackedCollection collection = (ITrackedCollection) value;
//If the collection values are mapped to this entity using a mapping object then we should delete all mappings for collection changes.//
if(isMapped) {
Entity map = createCollectionMap(getAttributeSupport().getAttributeIdentifier(attributeNumber), null);
//Delete all the mappings.//
map.getTransactionService().delete(map);
}//if//
if(isPartOf) {
IIterator iterator;
LiteList removedValues = new LiteList(100, 1000);
LiteList addedValues = new LiteList(100, 1000);
collection.getCollectionChanges(addedValues, removedValues);
//Remove old values from the repository.//
for(int index = 0, length = removedValues.getSize(); result == null && index < length; index++) {
Object next = removedValues.get(index);
//Make sure we are not dealing with a proxy.//
if(Orb.isProxy(next)) {
if(Orb.isLocal(next)) {
next = Orb.getLocal(next);
}//if//
else {
//TODO: Improve the quality of this error.
//This should never occur.//
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
next = null;
Debug.log("Error: Cannot assign a remote value to a collection attribute marked as PART OF. Part of indicates the value is a closely bound component of the declarer and thus is not a remote proxy.");
}//else//
}//if//
if((result == null) && (next instanceof Entity)) {
if(!((Entity) next).getAttributeSupport().getIsObjectNew()) {
//Remove values from the repository since they only should exist within the context of this object.//
result = ((Entity) next).internalEntityDelete(contextHolder);
}//if//
}//if//
else {
//TODO: Don't know what to do?
}//else//
}//for//
iterator = ((ICollection) value).iterator();
//Delete all the collection values.//
while((result == null) && (iterator.hasNext())) {
Object next = iterator.next();
//Make sure we are not dealing with a proxy.//
if(Orb.isProxy(next)) {
if(Orb.isLocal(next)) {
next = Orb.getLocal(next);
}//if//
else {
//TODO: Improve the quality of this error.
//This should never occur.//
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
next = null;
Debug.log("Error: Cannot assign a remote value to a collection attribute marked as PART OF. Part of indicates the value is a closely bound component of the declarer and thus is not a remote proxy.");
}//else//
}//if//
//Update the collection value.//
if((result == null) && (next instanceof Entity)) {
if(!((Entity) next).getAttributeSupport().getIsObjectNew()) {
result = ((Entity) next).entityDelete(contextHolder);
}//if//
}//if//
else {
//TODO: What should we do?
}//else//
}//while//
//Reset the change tracking.//
collection.resetChangeTracking();
}//if//
}//else if//
}//if//
return result;
}//entityDelete()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityUpdate()
*/
public TransactionErrorInfo entityUpdate() {
return entityUpdate((Runnable) null, null, -1);
}//entityUpdate()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityUpdate(int)
*/
public TransactionErrorInfo entityUpdate(int debugLevel) {
return entityUpdate((Runnable) null, null, debugLevel);
}//entityUpdate()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityUpdate(java.lang.Runnable)
*/
public TransactionErrorInfo entityUpdate(Runnable updateTask) {
return entityUpdate(updateTask, null, -1);
}//entityUpdate()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityUpdate(java.lang.Runnable, int)
*/
public TransactionErrorInfo entityUpdate(Runnable updateTask, int debugLevel) {
return entityUpdate(updateTask, null, debugLevel);
}//entityUpdate()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityUpdate(java.lang.Runnable, java.lang.Object)
*/
public TransactionErrorInfo entityUpdate(Runnable updateTask, Object defaultRepositoryIdentifier) {
return entityUpdate(updateTask, null, -1);
}//entityUpdate()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityUpdate(java.lang.Runnable, java.lang.Object, int)
*/
public TransactionErrorInfo entityUpdate(Runnable updateTask, Object defaultRepositoryIdentifier, int debugLevel) {
return entityUpdateCustomize(updateTask, defaultRepositoryIdentifier, debugLevel);
}//entityUpdate()//
/**
* Begins the process of updating the entity in the repository. This is setup as a good override point for code not using the Brainstorm transactional system.
* @param updateTask The task that should be called (if provided) to update the model prior to performing the update.
* @param defaultRepositoryIdentifier The identifier of the repository to be used in the query.
* @param debugLevel The TransactionService.DEBUG_xxx identifier specifying the level of debugging for the transaction. This may be -1 to use the default value in the TransactionService.
* @return The result of the transaction, or null if the transaction was a success.
*/
protected TransactionErrorInfo entityUpdateCustomize(Runnable updateTask, Object defaultRepositoryIdentifier, int debugLevel) {
TransactionErrorInfo result = null;
//Don't apply changes to a reflection to the repository.//
if(!isReflection()) {
//Ensure that this is the top of the logical object.//
if(getPartOfEntity() != null) {
IEntity top = getPartOfEntity();
while(top.getPartOfEntity() != null) {
top = top.getPartOfEntity();
}//while//
result = top.entityUpdate(updateTask, defaultRepositoryIdentifier, debugLevel);
}//if//
else {
result = entityUpdateInternal(updateTask, defaultRepositoryIdentifier, debugLevel);
}//else//
}//if//
return result;
}//entityUpdateCustomize()//
/**
* Kicks off the entity update using a transaction and SQL. This can be overridden to use a different O/R system.
* @param updateTask The task to be run to update the model post verification of the ability to update the model, and pre transaction.
* @param defaultRepositoryIdentifier The repository identifier to use when no other is given.
* @param debugLevel The TransactionService.DEBUG_xxx identifier specifying the level of debugging for the transaction. This may be -1 to use the default value in the TransactionService.
* @return The result of the transaction, or null for success.
*/
private TransactionErrorInfo entityUpdateInternal(Runnable updateTask, Object defaultRepositoryIdentifier, int debugLevel) {
TransactionContextHolder contextHolder = null;
TransactionErrorInfo result = null;
try {
contextHolder = TransactionContextHolder.getInstance(defaultRepositoryIdentifier);
// Object repositoryIdentifier = getRepositoryIdentifier(defaultRepositoryIdentifier);
//
// if(isObjectRepositoryBound() && getTransactionService() != null && repositoryIdentifier != null) {
// //Create a transactional context.//
// context = getTransactionService().createTransactionContext(repositoryIdentifier);
// }//if//
//Prepare for the transaction.//
prepTransaction(false);
//Apply the custom debug level to the transaction context so it gets applied to all the transactions passing through (invalid values such as -1 will be handled by the context).//
contextHolder.setDebugLevel(debugLevel);
//Perform the update.//
result = entityUpdateInternal(contextHolder, updateTask);
//Commit or rollback the transaction.//
if(result != null || !(contextHolder.commit())) {
if(result == null) {
result = ENTITY_TRANSACTION_COMMIT_FAILED;
}//if//
if(!contextHolder.rollback()) {
Debug.log(new RuntimeException("Error: Failed to rollback the transaction. The repository may be in an unexpected state (repository specific behavior unknown)."));
}//if//
}//if//
}//try//
//This catch should never be reachable.//
// catch(TransactionException e) {
// Debug.log(e);
// }//catch//
finally {
//Make sure we always close the transaction.//
if(contextHolder != null) {
contextHolder.close();
contextHolder = null;
}//if//
}//finally//
return result;
}//entityUpdateInternal()//
/**
* Uses the transaction context to read and lock the version data in the repository.
* @param context The context under which we are performing the query.
* @param repositoryIdentifier The identifier for the repository we are checking.
* @return Whether the version in the repository matches the version in memory.
*/
protected boolean lockAndCheckRepositoryVersion(TransactionContext context, Object repositoryIdentifier) {
boolean result = false;
try {
TypeMetadata typeMetadata = getApplication().getMetadataService().getTypeMetadata(this.getClass());
TypeRepositoryMetadata typeRepositoryMetadata = typeMetadata.getTypeRepositoryMetadata(repositoryIdentifier);
Entity instance = (Entity) getClass().newInstance();
ReadSingleTransaction transaction = getTransactionService().initializeReadSingleTransaction(instance, new Attribute[] {typeRepositoryMetadata.getVersionAttribute().getAttributeMetadata().getAttributeIdentifier()}, new Attribute[] {typeRepositoryMetadata.getLogicalObjectIdAttribute().getAttributeMetadata().getAttributeIdentifier()});
Object repositoryVersion = null;
Object memoryVersion = null;
transaction.setUseRequesterAsReceiver(true);
transaction.setReadForUpdate(true);
instance.getAttributeSupportAccess().setAttributeValue(instance.getAttributeSupport(), typeRepositoryMetadata.getLogicalObjectIdAttribute().getNumber(), getAttributeSupport().getAttributeValue(typeRepositoryMetadata.getLogicalObjectIdAttribute().getNumber()), CONTEXT_UNTRUSTED, true);
getTransactionService().process(transaction, false);
repositoryVersion = instance.getAttributeSupport().getAttributeValue(typeRepositoryMetadata.getVersionAttribute().getNumber());
memoryVersion = getAttributeSupport().getAttributeValue(typeRepositoryMetadata.getVersionAttribute().getNumber());
if(!repositoryVersion.equals(memoryVersion)) {
result = true;
}//if//
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
return result;
}//lockAndCheckRepositoryVersion()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#isObjectNew()
*/
public boolean isObjectNew() {
return isObjectRepositoryBound() && attributeSupport.getIsObjectNew();
}//isObjectNew()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#isObjectRepositoryBound()
*/
public boolean isObjectRepositoryBound() {
return attributeSupport.getIsObjectRepositoryBound();
}//isObjectRepositoryBound()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#useRepositoryVersionTracking()
*/
public boolean useRepositoryVersionTracking() {
TypeRepositoryMetadata typeRepositoryMetadata = getAttributeSupport().getTypeRepositoryMetadata();
return typeRepositoryMetadata != null && typeRepositoryMetadata.getVersionAttribute() != null && typeRepositoryMetadata.getLogicalObjectIdAttribute() != null;
}//useRepositoryVersionTracking()//
/**
* Called by entityUpdate when an instance of this class is being stored for the first time in the repository.
* @param context The transactional context within which the update is occuring. All repository interactions must be through this context so that they can roll back or commit as one unit.
* @return The result of the operation. A null value indicates success.
*/
protected TransactionErrorInfo internalEntityCreate(TransactionContext context) {
TransactionErrorInfo result = null;
//Give the object a chance to perform actions prior to creating the object in the repository.//
if(preEntityCreate()) {
//Create the object in the repository.//
if(context.create(this) == 0) {
//TODO: Improve the quality of this error. It would be nice to know if the transaction failed some kind of repository requirement so we can pass that error back to the validation routines.
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
}//if//
//Give the object a chance to perform actions after creating the object in the repository.//
context.addPostCompleteHandler(new TransactionContext.IPostCompleteHandler() {
public void doTransactionComplete(boolean isCommitted) {
postEntityCreate(isCommitted);
}//run()//
});
}//if//
else {
//TODO: Improve the quality of this error. It would be nice to have the preparation code report a result that wasn't a simple boolean.
result = ENTITY_TRANSACTION_FAILED_PREPERATIONS;
}//else//
return result;
}//internalEntityCreate()//
/**
* Called by entityUpdate when an instance of this class is being updated in the repository.
* @param context The transactional context within which the update is occuring. All repository interactions must be through this context so that they can roll back or commit as one unit.
* @return The result of the operation. A null value indicates success.
*/
protected TransactionErrorInfo internalEntityUpdate(TransactionContextHolder contextHolder) {
TransactionErrorInfo result = null;
TransactionContext context = contextHolder.getTransactionContext(this);
if(context != null) {
//Give the object a chance to perform actions prior to updating the object in the repository.//
if(preEntityUpdate()) {
//Update the object in the repository.//
if(context.update(this) == 0) {
//TODO: Improve the quality of this error. It would be nice to know if the transaction failed some kind of repository requirement so we can pass that error back to the validation routines.
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
}//if//
//Give the object a chance to perform actions after updating the object in the repository.//
context.addPostCompleteHandler(new TransactionContext.IPostCompleteHandler() {
public void doTransactionComplete(boolean isCommitted) {
postEntityUpdate(isCommitted);
}//run()//
});
}//if//
else {
//TODO: Improve the quality of this error. It would be nice to have the preparation code report a result that wasn't a simple boolean.
result = ENTITY_TRANSACTION_FAILED_PREPERATIONS;
}//else//
}//if//
return result;
}//internalEntityUpdate()//
/**
* Called by entityUpdate when an instance of this class is being updated in the repository.
* @param context The transactional context within which the update is occuring. All repository interactions must be through this context so that they can roll back or commit as one unit.
* @return The result of the operation. A null value indicates success.
*/
protected TransactionErrorInfo internalEntityDelete(TransactionContextHolder contextHolder) {
TransactionErrorInfo result = null;
TransactionContext context = contextHolder.getTransactionContext(this);
if(context != null) {
//Give the object a chance to perform actions prior to updating the object in the repository.//
if(preEntityDelete()) {
if(context.delete(this) == 0) {
//TODO: Improve the quality of this error. It would be nice to know if the transaction failed some kind of repository requirement so we can pass that error back to the validation routines.
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
}//if//
//Give the object a chance to perform actions after updating the object in the repository.//
context.addPostCompleteHandler(new TransactionContext.IPostCompleteHandler() {
public void doTransactionComplete(boolean isCommitted) {
postEntityDelete(isCommitted);
}//run()//
});
}//if//
else {
//TODO: Improve the quality of this error. It would be nice to have the preparation code report a result that wasn't a simple boolean.
result = ENTITY_TRANSACTION_FAILED_PREPERATIONS;
}//else//
}//if//
return result;
}//internalEntityDelete()//
/**
* Called prior to creating the entity in the repository.
* @return Whether the transaction should continue.
*/
protected boolean preEntityCreate() {
return true;
}//preEntityCreate()//
/**
* Called after to creating the entity in the repository.
* @param isCommitted Whether the transaction committed.
*/
protected void postEntityCreate(boolean isCommitted) {
}//postEntityCreate()//
/**
* Called prior to updating the entity in the repository.
* @return Whether the transaction should continue.
*/
protected boolean preEntityUpdate() {
return true;
}//preEntityUpdate()//
/**
* Called after to updating the entity in the repository.
* @param isCommitted Whether the transaction committed.
*/
protected void postEntityUpdate(boolean isCommitted) {
}//postEntityUpdate()//
/**
* Called prior to deleting the entity in the repository.
* @return Whether the transaction should continue.
*/
protected boolean preEntityDelete() {
return true;
}//preEntityDelete()//
/**
* Called after to deleting the entity in the repository.
* @param isCommitted Whether the transaction committed.
*/
protected void postEntityDelete(boolean isCommitted) {
}//postEntityDelete()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#entityUpdate(com.foundation.transaction.TransactionContext)
*/
public TransactionErrorInfo entityUpdate(TransactionContextHolder context) {
return entityUpdateInternal(context, null);
}//entityUpdate()//
/**
* Called by all the public entityUpdate methods when the model is not connected to a transactional system (database).
* <p>Override as to perform any custom storage of the logical model. This must only be overridden at the top of the logical object since it is the only model to receive the call. This method can be used to store to an xml or binary file, or just about any method of storage desired.</p>
* @return The result of the operation. A null value indicates success.
*/
protected TransactionErrorInfo entityUpdateCustomize() {
//Override and provide long term storage functionality.//
//This method is called when the class is not bound to a repository and entityUpdate() has been called, or a reflection was synchronized back to this live model.//
return null;
}//entityUpdateCustomize()//
/**
* Called by all the public entityUpdate methods. This method kicks off the entity update by verifying the object in memory matches the one in the repository, updating the model, then performing the actual repository update.
* @param context The context for the transaction.
* @param updateTask The task to be run after verifying the repository version (if necessary). This task will update the model once the repository version and local version have been tested against the copy's version.
* @return The result of the operation. A null value indicates success. This will report success if the transaction was unnecessary.
*/
private TransactionErrorInfo entityUpdateInternal(TransactionContextHolder context, Runnable updateTask) {
boolean repositoryVersionOkay = true;
boolean hasVersion = false;
TransactionErrorInfo result = null;
//Lock the repository for this logical object and validate the version number if this is not creating an entirely new logical object.//
if(!isObjectNew() && context.getTransactionContext(this) != null) {
if(useRepositoryVersionTracking()) {
TransactionContext transactionContext = context.getTransactionContext(this);
hasVersion = true;
repositoryVersionOkay = lockAndCheckRepositoryVersion(transactionContext, transactionContext.getRepositoryIdentifier());
}//if//
}//if//
if(repositoryVersionOkay) {
LogicalObjectChangeLog changeLog = null;
try {
if(updateTask != null) {
//Make the changes to the model.//
updateTask.run();
}//if//
if(hasVersion) {
TypeRepositoryMetadata typeRepositoryMetadata = getAttributeSupport().getTypeRepositoryMetadata();
Object logicalObjectId = getAttributeSupport().getAttributeValue(typeRepositoryMetadata.getLogicalObjectIdAttribute().getNumber());
Object newVersion = getAttributeSupport().getAttributeValue(typeRepositoryMetadata.getVersionAttribute().getNumber());
if(!(logicalObjectId instanceof Long)) {
if(logicalObjectId instanceof Number) {
logicalObjectId = new Long(((Number) logicalObjectId).longValue());
}//if//
else {
//TODO: Error: Cannot convert a non-number to a long value! This attribute should never be a non-numeric value.
}//else//
}//if//
if(newVersion instanceof Number) {
newVersion = new Long(((Number) newVersion).longValue() + 1);
}//if//
else {
//TODO: Error: Cannot convert a non-number to a long value! This attribute should never be a non-numeric value.
}//else//
changeLog = new LogicalObjectChangeLog((Long) logicalObjectId, (Long) newVersion);
//TODO: Need to collect metadata on the changes so that we can store it in the change history table (to allow other instances of the logical object to update via the repository).
//TODO: Need to update the version attribute in all the objects in the logical object.. only one needs to be untrusted - preferably the first object that is being updated anyhow.
//TODO: Need to provide a versionId to all new objects in the logical object.
}//if//
//Recursively navigate the logical object performing updates and saving the change data.//
result = entityUpdate(context, changeLog); //Note: Will throw an exception of any part of the transaction fails.//
}//try//
catch(TransactionException e) {
//TODO: Provide a more descriptive error based on the error returned by the repository.
//TODO: Use the error identifier in the exception.
result = e.getErrorInfo();
}//catch//
if(result == null && changeLog != null) {
//TODO: Store the change log.
}//if//
}//if//
else {
//TODO: Initiate a thread to update the logical object using the most recent data in the repository.
//TODO: Flag the logical object as updating from the repository so that if the update thread loses its lock others can read, but not synchronize changes.
//TODO: Notify reflections (maybe only those updating?) that an update is in progress, and notify them when it finishes.
//The update will have to also update any non-part-of references and collections of references by calling the lazy load handler.
//This shouldn't change the attribute's value until the lazy load handler has a result since other threads may read the value at the same time.
}//else//
return result;
}//entityUpdateInternal()//
/**
* Starts the actual updating of the entity in the repository.
* <p>Note: This is a method to override for custom processing of repository updates.</p>
* @param context The transaction context holder. This abstracts access to a transaction context specific to the repository used by the entity.
* @param changeLog The optional log of the local object changes for use if maintaining the versioning in the repository (to allow multiple in memory copies).
* @return The result of the operation. A null value indicates success. This will report success if the transaction was unnecessary.
*/
protected TransactionErrorInfo entityUpdate(TransactionContextHolder contextHolder, LogicalObjectChangeLog changeLog) {
AttributeSupport attributeSupport = getAttributeSupport();
int attributeCount = attributeSupport.getAttributeCount();
TransactionErrorInfo result = null;
boolean wasNew = false;
//Don't apply reflection changes to the repository.//
if(!isReflection()) {
if(getAttributeSupport().getIsObjectRepositoryBound()) {
TransactionContext context = contextHolder.getTransactionContext(this);
//Create or update the object.//
if(attributeSupport.getIsObjectNew()) {
//Don't bother trying to create an object that doesn't exist in any repository.//
if(attributeSupport.getIsObjectRepositoryBound(context.getRepositoryIdentifier())) {
//Refresh bound attributes just prior to creating the object in the repository.//
attributeSupport.refreshBoundAttributes(false);
result = internalEntityCreate(context);
wasNew = true;
}//if//
}//if//
else {
/* Don't do this here because we need to know the old values later (updating resets the change flags and clears old values).
if(attributeSupport.getCanObjectUpdate()) {
result = context.update(this) != 0;
}//if//
*/
}//else//
}//if//
//Update or create all objects that are a part of this object, and then update again if necessary.//
for(int index = 0; (result == null) && (index < attributeCount); index++) {
boolean isMapped = attributeSupport.getIsAttributeMapped(index);
boolean isPartOf = attributeSupport.getIsAttributePartOf(index);
//If the attribute is either "part of" this object, or it is a mapped collection, and it had a value, then update the value.//
if((isPartOf || isMapped) && (attributeSupport.getIsAttributeRealized(index))) {
Object value = getAttributeSupport().getAttributeValue(index);
//Make sure we have a local non-proxy value.//
if(Orb.isProxy(value)) {
if(Orb.isLocal(value)) {
value = Orb.getLocal(value);
}//if//
else {
//TODO: Improve the quality of this error.
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
value = null;
Debug.log("Error: Cannot assign a remote value to an attribute marked as PART OF. Part of indicates the value is a closely bound component of the declarer and thus is not a remote proxy.");
}//else//
}//if//
if(result == null) {
result = entityUpdate(contextHolder, value, index, changeLog);
}//if//
//If the value had changed then delete the old value if possible.//
if((isPartOf) && (result == null) && (attributeSupport.getIsAttributeChanged(index))) {
Object oldValue = attributeSupport.getOldAttributeValue(index);
result = entityDelete(contextHolder, oldValue, index);
}//if//
}//if//
}//for//
//Update this object in case attributes have changed due to changes in contained objects.//
if(result == null) {
//Refresh bound attributes such that a new instances referenced that have subsiquently set id's will update any references to those id's here.//
attributeSupport.refreshBoundAttributes(false);
//Update the model if it has changed at all.//
if(attributeSupport.getCanObjectUpdate(!wasNew)) {
if(changeLog != null) {
//Add the entity to the change log since it had attributes that required direct updating in the repository.//
changeLog.add(this);
}//if//
//Perform the repository update.//
result = internalEntityUpdate(contextHolder);
}//if//
}//if//
//If the object was new we need to see if any of its attributes are supposed to be weakly referenced.//
if(wasNew) {
attributeSupport.updateWeakReferences();
}//if//
}//if//
return result;
}//entityUpdate()//
/**
* Updates the default repository with the latest entity data.
* <p>This method may be called if the entity is not in the repository (it was not read from the repository before being edited), or when the entity was read from the repository and then subsiqently edited.</p>
* <p><b>This method may only be used to perform single instance updates in the repository.</b></p>
* @param context The transactional context within which the update is occuring. All repository interactions must be through this context so that they can roll back or commit as one unit.
* @param value The value to be updated.
* @param isPartOf Whether the value is part of this object.
* @param isCollection Whether the attribute whose value is being updated was declared as a collection.
* @param changeLog The optional change log, used if the logical object is version tracked in the repository.
* @return The result of the operation. A null value indicates success. This will report success if the transaction was unnecessary.
*/
protected TransactionErrorInfo entityUpdate(TransactionContextHolder contextHolder, Object value, int attributeNumber, LogicalObjectChangeLog changeLog) {
AttributeSupport attributeSupport = getAttributeSupport();
TransactionErrorInfo result = null;
boolean isCollection = attributeSupport.getIsCollection(attributeNumber);
boolean isPartOf = attributeSupport.getIsAttributePartOf(attributeNumber);
LiteList removedValues = null;
LiteList addedValues = null;
if(value instanceof ITrackedCollection) {
if(attributeSupport.getIsAttributeChanged(attributeNumber)) {
//All the values were added if this is a totally new list added to the model.//
addedValues = new LiteList((ITrackedCollection) value);
removedValues = LiteList.EMPTY_LIST;
}//if//
else if(((ITrackedCollection) value).hasCollectionChanges()) {
removedValues = new LiteList(100, 1000);
addedValues = new LiteList(100, 1000);
((ITrackedCollection) value).getCollectionChanges(addedValues, removedValues);
}//else if//
else {
removedValues = LiteList.EMPTY_LIST;
addedValues = LiteList.EMPTY_LIST;
}//else//
}//if//
//If the attribute is an object reference that changed values, or a collection that has adds or removes, then mark the attribute as having changed in the log so we know to re-get the object or collection of objects when refreshing from the repository.//
if((changeLog != null) && (isPartOf) && ((attributeSupport.getIsAttributeChanged(attributeNumber)) || (isCollection && (value instanceof ITrackedCollection) && ((removedValues.getSize() > 0) || (addedValues.getSize() > 0))))) {
changeLog.add(this, attributeNumber);
}//if//
//If the value is an entity then update it, if it is a collection then update all collected values, otherwise do nothing (shouldn't happen).//
if((result == null) && (isPartOf) && (value instanceof Entity)) {
result = ((Entity) value).entityUpdate(contextHolder, changeLog);
}//if//
else if((result == null) && (isCollection) && (value instanceof ITrackedCollection)) {
boolean isMapped = attributeSupport.getIsAttributeMapped(attributeNumber);
ITrackedCollection collection = (ITrackedCollection) value;
//Note: We have to do this to every collection because it doesn't know if the contained values have changed.//
//It doesn't know because the contained values think they are part of the collection's parent, not the actual collection (such that a model can be referenced by the parent via multiple attributes and multiple collections without violating containment rules).//
//If part-of: Update all the collection values first so we are sure to have the latest keys.//
if(isPartOf) {
IIterator iterator = collection.iterator();
while((result == null) && (iterator.hasNext())) {
Object next = iterator.next();
//Make sure we are not dealing with a proxy.//
if(Orb.isProxy(next)) {
if(Orb.isLocal(next)) {
next = Orb.getLocal(next);
}//if//
else {
//TODO: Improve the quality of this error.
//This should never occur.//
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
next = null;
Debug.log("Error: Cannot assign a remote value to a collection attribute marked as PART OF. Part of indicates the value is a closely bound component of the declarer and thus is not a remote proxy.");
}//else//
}//if//
//Update the collection value.//
if((result == null) && (next instanceof Entity) && ((Entity) next).getIsVirtualObjectChanged()) {
result = ((Entity) next).entityUpdate(contextHolder, changeLog);
}//if//
else {
//TODO: Don't know what to do?
}//else//
}//while//
}//if//
//If the collection values are mapped to this entity using a mapping object then we should delete and add necessary mappings for collection changes.//
if(isMapped) {
IIterator iterator = removedValues.iterator();
for(int index = 0, length = removedValues.getSize(); result == null && index < length; index++) {
Object next = removedValues.get(index);
//Only IEntity values can have a mapping so ignore all others. Also only perform a repository operation if both objects exist in a repository since they must both have valid keys (note that it is possible to have a valid key without being in the repository, but we will ignore that here since it shouldn't apply).//
if(next instanceof IEntity && !((IEntity) next).isObjectNew() && !isObjectNew()) {
Entity map = createCollectionMap(getAttributeSupport().getAttributeIdentifier(attributeNumber), next);
if(map != null && map.getTransactionService().delete(map) == 0) {
//Debug.log("Error: Unable to delete the old mapping for " + getClass().getName() + '.' + attributeSupport.getAttributeName(attributeNumber) + ". Object: " + map);
//result = false;
}//if//
}//if//
}//for//
for(int index = 0, length = addedValues.getSize(); result == null && index < length; index++) {
Object next = addedValues.get(index);
//Only IEntity values can have a mapping so ignore all others. Also only perform a repository operation if both objects exist in a repository since they must both have valid keys (note that it is possible to have a valid key without being in the repository, but we will ignore that here since it shouldn't apply).//
if(next instanceof IEntity && !((IEntity) next).isObjectNew() && !isObjectNew()) {
Entity map = createCollectionMap(getAttributeSupport().getAttributeIdentifier(attributeNumber), next);
if(map != null && ((result = map.entityUpdate(contextHolder, changeLog)) != null)) {
Debug.log("Error: Unable to create the desired mapping for " + getClass().getName() + '.' + attributeSupport.getAttributeName(attributeNumber) + '.');
}//if//
}//if//
}//for//
}//if//
if(isPartOf) {
//Remove old values from the repository.//
for(int index = 0, length = removedValues.getSize(); result == null && index < length; index++) {
Object next = removedValues.get(index);
//Make sure we are not dealing with a proxy.//
if(Orb.isProxy(next)) {
if(Orb.isLocal(next)) {
next = Orb.getLocal(next);
}//if//
else {
//TODO: Improve the quality of this error.
//This should never occur.//
result = ENTITY_TRANSACTION_UNKOWN_ERROR;
next = null;
Debug.log("Error: Cannot assign a remote value to a collection attribute marked as PART OF. Part of indicates the value is a closely bound component of the declarer and thus is not a remote proxy.");
}//else//
}//if//
if((result == null) && (next instanceof Entity)) {
if(!((Entity) next).getAttributeSupport().getIsObjectNew()) {
//Remove values from the repository since they only should exist within the context of this object.//
result = ((Entity) next).internalEntityDelete(contextHolder);
}//if//
}//if//
else {
//TODO: Don't know what to do?
}//else//
}//for//
}//if//
//Reset the change tracking.//
collection.resetChangeTracking();
}//else if//
return result;
}//entityUpdate()//
/* {Non-Javadoc}
* @see com.common.comparison.IComparable#equals(Object)
*/
public boolean equals(Object object) {
return equalsEquals(object);
}//equals()//
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
/* No longer used since this causes problems when synchronizing new objects (ie: instance A is cloned [creating instance B] which becomes A's reflected object).
IReflectable object = this;
while(Orb.isLocal(object) && object.isReflection()) {
object = (IReflectable) object.getReflected();
}//while//
return object != this ? object.hashCode() : super.hashCode();
*/
return getAttributeSupport().getHash();
}//hashCode()//
/* {Non-Javadoc}
* @see com.common.comparison.IComparable#equalsEquals(Object)
*/
public boolean equalsEquals(Object object) {
boolean result = false;
if(Orb.isLocal(object)) {
object = Orb.getLocal(object);
if(object != null) {
//If the types match then remove any local reflections and compare the reflected values.//
if(object.getClass().equals(getClass())) {
IReflectable o1 = this;
IReflectable o2 = (IReflectable) object;
while(Orb.isLocal(o1) && o1.isReflection()) {
o1 = (IReflectable) o1.getReflected();
}//while//
while(Orb.isLocal(o2) && o2.isReflection()) {
o2 = (IReflectable) o2.getReflected();
}//while//
//Use equality unless the reflected values are proxies.//
result = Orb.isLocal(o1) ? o1 == o2 : o1.equals(o2);
}//if//
//Assume the object is a key and see if it is this model's key.//
else {
TypeMetadata typeMetadata = getAttributeSupport().getTypeMetadata();
int keyCount = typeMetadata.getKeyAttributeCount();
if(keyCount == 1) {
Object key = getAttributeSupport().getAttributeValue(typeMetadata.getKeyAttributeMetadata(0).getNumber());
result = Comparator.equals(key, object);
}//if//
else if(keyCount > 1 && object instanceof CompositeEntityKey && ((CompositeEntityKey) object).getPartCount() == keyCount) {
boolean match = true;
CompositeEntityKey keyValues = (CompositeEntityKey) object;
for(int index = 0; match && index < keyCount; index++) {
match = Comparator.equals(keyValues.getPart(index), getAttributeSupport().getAttributeValue(typeMetadata.getKeyAttributeMetadata(index).getNumber()));
}//for//
result = match;
}//else if//
return result;
}//else//
}//if//
}//if//
return result;
}//equalsEquals()//
/* (non-Javadoc)
* @see com.common.util.json.IJsonAware#toJson(com.common.util.json.IJsonContext)
2014-05-30 10:31:51 -07:00
*/
public void toJson(IJsonContext context) {
2014-05-30 10:31:51 -07:00
try {
boolean commaPrefix = false;
EntityMetadata metadata = (EntityMetadata) context.getMetadataContainer().getMetadata(getClass());
2014-05-30 10:31:51 -07:00
IntHashSet metadataAttributes = null;
IIntIterator metadataAttributeIterator = null;
int count;
LiteHashSet included = context.getIncluded();
StringWriter writer = context.getWriter();
2014-05-30 10:31:51 -07:00
//Use the metadata provided if any.//
if(metadata != null) {
metadataAttributes = metadata.getAttributes();
metadataAttributeIterator = metadataAttributes.iterator();
}//if//
count = metadataAttributes != null ? metadataAttributes.getSize() : getAttributeCount();
writer.write('{');
if(count != 0) {
boolean[] includeAttribute = new boolean[count];
Object[] values = new Object[count];
for(int index = 0; index < count; index++) {
int attributeIndex = metadataAttributes != null ? metadataAttributeIterator.next() : index;
//Ignore the attribute if it hasn't been set yet and it isn't lazy loaded.//
if(getAttributeSupport().getIsAttributeRealized(attributeIndex) && !getAttributeSupport().getIsAttributeLazy(attributeIndex)) {
Object value = getAttributeSupport().getAttributeValue(attributeIndex);
//If the attribute is in the metadata then automatically include it.//
if(metadataAttributes != null) {
includeAttribute[attributeIndex] = true;
}//if//
else {
//Ignore transient attributes or back references.//
if(getAttributeSupport().getIsAttributeTransient(attributeIndex) || getAttributeSupport().getIsBackReference(attributeIndex)) {
includeAttribute[attributeIndex] = false;
}//if//
//If the value is null or immutable then don't worry about recursion.//
else if(!(value == null || value instanceof Number || value instanceof Date || value instanceof String)) {
includeAttribute[attributeIndex] = true;
if(Orb.isProxy(value)) {
if(Orb.isLocal(value)) {
value = Orb.getLocal(value);
}//if//
else {
includeAttribute[attributeIndex] = false;
}//else//
}//if//
if(includeAttribute[attributeIndex]) {
if(included.containsValue(value)) {
includeAttribute[attributeIndex] = false;
}//if//
else {
included.add(value);
}//else//
}//if//
}//if//
}//else//
values[attributeIndex] = value;
}//if//
}//for//
metadataAttributeIterator.resetToFront();
//Add the actual attributes and values to the JSON output.//
for(int index = 0; index < count; index++) {
int attributeIndex = metadataAttributes != null ? metadataAttributeIterator.next() : index;
//Only add attributes we previously marked as being included.//
if(includeAttribute[attributeIndex]) {
String name = getAttributeName(attributeIndex);
Object value = values[attributeIndex];
if(commaPrefix) {
writer.write(',');
}//if//
context.writeNewLine();
2014-05-30 10:31:51 -07:00
//Note: No need to escape things since attribute names only allow A-z and underscore.//
writer.write('"');
writer.write(name);
writer.write('"');
writer.write(':');
if(!context.isCompact()) {
2014-05-30 10:31:51 -07:00
writer.write(' ');
}//if//
//Write the value out.//
context.toJson(value);
2014-05-30 10:31:51 -07:00
commaPrefix = true;
}//if//
}//for//
context.writeNewLine();
2014-05-30 10:31:51 -07:00
}//if//
writer.write('}');
}//try//
catch(Throwable e) {
Debug.log(e);
}//catch//
}//toJson()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getApplication()
*/
public IApplication getApplication() {
throw new com.foundation.exception.FoundationDesignException("The application should provide a subclass of the Model, Controller, ViewController, and other foundation subclasses of Entity as needed such that all application Model, Controller, etc. subclasses extend the ApplicationModel, ApplicationController, etc. subclass for that application. This application subclass of the Model, Controller, etc. should provide a valid getApplication() method implementation such that all the application entities can obtain a reference to their application.");
}//getApplication()//
/**
* Gets the number of attributes this entity has declared.
* @return The count of entity attributes.
*/
public int getAttributeCount() {
return getAttributeSupport().getAttributeCount();
}//getAttributeCount()//
/**
* Gets the name of the attribute associated with the given attribute number.
* <p>This method allows the application easy access to the entity's attribute names using the attribute identifiers.
* <code>
* ((IEntity) object).getAttributeName(MyEntity.MY_ATTRIBUTE);
* </code>
* @param attributeNumber The number that was assigned to the desired attribute.
* @return The name of the attribute.
*/
public String getAttributeName(int attributeNumber) {
return getAttributeSupport().getAttributeName(attributeNumber);
}//getAttributeName()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getAttributeSupport()
*/
public AttributeSupport getAttributeSupport() {
return attributeSupport;
}//getAttributeSupport()//
/**
* Gets the access object for the attribute support.
* @return The access object which provides access to normally protected methods in the attribute support.
*/
protected AttributeSupport.AttributeSupportAccess getAttributeSupportAccess() {
return attributeSupportAccess;
}//getAttributeSupportAccess()//
/**
* Retrieves an attribute's value.
* @param attribute The unique (within the class) identifier for the attribute.
* @return The value assigned to the attribute. Note that the value may not be the most current value if the calling thread is not synchronized on the same object as a writing thread.
*/
protected Object getAttributeValue(Attribute attribute) {
return getAttributeSupport().getAttributeValue(attribute.getNumber());
}//getAttributeValue()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getPartOfEntity()
*/
public IEntity getPartOfEntity() {
return attributeSupportAccess.getPartOfEntity(getAttributeSupport());
}//getPartOfEntity()//
/**
* Sets the entity the entity is part of.
* @param partOfEntity The entity that this entity is a part of. This may be null.
*/
protected void setPartOfEntity(IEntity partOfEntity) {
attributeSupportAccess.setPartOfEntity(getAttributeSupport(), partOfEntity);
//Register and unregister listeners on the parent for bound attributes.//
getAttributeSupport().registerReferenceBoundAttributes(partOfEntity == null ? null : partOfEntity.getAttributeSupport());
}//setPartOfEntity()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getPartOfEntityCounter()
*/
public int getPartOfEntityCounter() {
return attributeSupportAccess.getPartOfEntityCounter(getAttributeSupport());
}//getPartOfEntityCounter()//
/**
* Sets the count of times the part-of entity has been set to the current value.
* @param partOfEntityCounter The counter for setting the part of entity.
*/
private void setPartOfEntityCounter(int partOfEntityCounter) {
attributeSupportAccess.setPartOfEntityCounter(getAttributeSupport(), partOfEntityCounter);
}//setPartOfEntityCounter()//
/**
* Gets the event firing and listening support object.
* @return The support object that contains the event firing code and manages event listeners.
*/
protected EventSupport getEventSupport() {
return eventSupport;
}//getEventSupport()//
/**
* Determines whether the attribute has been changed since the last time the change flags were reset.
* @param attribute The number identifying the attribute in question.
* @return Whether the attribute has been given a new value (including null).
*/
public boolean getIsAttributeChanged(Attribute attribute) {
return getAttributeSupport().getIsAttributeChanged(attribute.getNumber());
}//getIsAttributeChanged()//
/**
* Determines whether the attribute has ever had any value assigned to it.
* @param attributeNumber The number identifying the attribute in question.
* @return Whether the attribute has been given any value (including null).
*/
public boolean getIsAttributeRealized(Attribute attribute) {
return getAttributeSupport().getIsAttributeRealized(attribute.getNumber());
}//getIsAttributeRealized()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getIsObjectChanged()
*/
public boolean getIsObjectChanged() {
return getAttributeSupport().getIsObjectChanged();
}//getIsObjectChanged()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getIsVirtualObjectChanged()
*/
public boolean getIsVirtualObjectChanged() {
return getAttributeSupport().getIsVirtualObjectChanged();
}//getIsVirtualObjectChanged()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getIsAltered()
*/
public boolean getIsAltered() {
return getAttributeSupport().getIsAltered();
}//getIsAltered()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getIsVirtualObjectAltered()
*/
public boolean getIsVirtualObjectAltered() {
return getAttributeSupport().getIsVirtualObjectAltered();
}//getIsVirtualObjectAltered()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#resetAlteredFlag()
*/
public void resetAlteredFlag() {
getAttributeSupport().resetAlteredFlag();
}//resetAlteredFlag()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#resetVirtualObjectAlteredFlags()
*/
public void resetVirtualObjectAlteredFlags() {
getAttributeSupport().resetVirtualObjectAlteredFlags();
}//resetVirtualObjectAlteredFlags()//
/**
* Determines whether any the object is considered new, meaning it was not previously read from a repository.
* @return Whether the object has never been read from the repository and is therefor considered new.
*/
public boolean getIsObjectNew() {
return getAttributeSupport().getIsObjectNew();
}//getIsObjectNew()//
/* (non-Javadoc)
* @see com.foundation.metadata.ISupportsContainment#getMonitor()
*/
public final Monitor getMonitor() {
IEntity partOfEntity = attributeSupportAccess.getPartOfEntity(getAttributeSupport());
return partOfEntity != null ? partOfEntity.getMonitor() : attributeSupport;
}//getMonitor()//
/**
* Sets a custom lazy load handler for a specific attribute.
* @param attribute The attribute to have the handler. This attribute should be flagged as lazily loaded, or have a default value set, otherwise the handler will never be called. Reflections will ignore all lazy loading since they always call the reflected object for the value, unless the attribute is marked as not being reflected.
* @param handler The handler called to lazily load the attribute.
* @return The old custom lazy load handler, or null if an old handler didn't exist.
*/
public ICustomLazyLoadHandler setCustomLazyLoadHandler(Attribute attribute, ICustomLazyLoadHandler handler) {
return getAttributeSupport().setCustomLazyLoadHandler(attribute, handler);
}//setCustomLazyLoadHandler()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getOldAttributeValue(com.foundation.metadata.Attribute)
*/
public Object getOldAttributeValue(Attribute attribute) {
return attributeSupport.getOldAttributeValue(attribute.getNumber());
}//getOldAttributeValue()//
/**
* Gets the reference to the reflected object. If the reflected object is remote, then this will be a proxy to the reflected object.
* <p>Note: Reflections are not serializable, but the reflected reference may be serializable.</p>
* @return A reference to the object being reflected, or null if this is not a reflection of another object.
*/
public final IReflectable getReflected() {
return attributeSupport.getReflectedObject() != null ? attributeSupport.getReflectedObject() : this;
}//getReflected()//
/**
* Determines whether this reflectable object is a reflection created within the given context.
* <p>Note: Reflections are not serializable, but the reflected reference may be serializable.</p>
* @param reflectionContext The optional context used to check whether this is a reflection. If null then the response will be true if the object is a reflection in any context.
* @return Whether this object is a reflection of another object and was created in the given context.
*/
public final boolean isReflection() {
return attributeSupport.isReflection(null);
}//isReflection()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#isReflection(com.foundation.attribute.ReflectionContext)
*/
public boolean isReflection(ReflectionContext reflectionContext) {
return attributeSupport.isReflection(reflectionContext);
}//isReflection()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#getReflectionContext()
*/
public ReflectionContext getReflectionContext() {
return attributeSupport.getReflectionContext();
}//getReflectionContext()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getTransactionService()
*/
public TransactionService getTransactionService() {
return getApplication().getTransactionService();
}//getTransactionService()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getRepositoryIdentifier()
*/
public Object getRepositoryIdentifier() {
return getRepositoryIdentifier(null);
}//getRepositoryIdentifier()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#getRepositoryIdentifier(java.lang.Object)
*/
public Object getRepositoryIdentifier(Object defaultRepositoryIdentifier) {
return getAttributeSupport().getRepositoryIdentifier() == null ? defaultRepositoryIdentifier == null ? getApplication().getDefaultRepositoryIdentifier(getClass()) : defaultRepositoryIdentifier : getAttributeSupport().getRepositoryIdentifier();
}//getRepositoryIdentifier()//
/**
* Lazily loads attributes that were marked as lazy when defined.
* <p>Subclasses should override this method if they define attributes requiring lazy initialization.
* They should contain a switch statement, and should call the super classes' method if they don't handle the request.</p>
* <p>Note: If this call is in the context of a multi-thread accessable entity then the calling thread will have already locked on the entity's monitor.</p>
* @param attribute The identifier for the attribute whose value is to be lazily loaded.
* @return The attribute's value.
*/
protected Object lazyLoadAttribute(Attribute attribute) {
//Subclasses should extend this method if they define lazy loaded attributes.//
return null;
}//lazyLoadAttribute()//
/**
* Requests that the entity register the necessary listeners for the calculated attribute.
* <p>Note: If this call is in the context of a multi-thread accessable entity then the calling thread will have already locked on the entity's monitor.</p>
* @param attribute The attribute that was marked as AO_CALCULATED.
* @param listener The listener that should be called if the attribute needs recalculating. This listener implements the IModelListenerAttributeHandler and IModelListenerCollectionHandler interfaces so they can easily be attached via an AttributeBinding when using a ModelListener.
* @return The model listener that will listen for changes relevant to the calculated attribute. This listener will be retained by the entity to prevent the listener from being GC'd.
*/
protected ModelListener calculatedAttributeRegister(Attribute attribute, ICalculatedAttributeListener listener) {
//Subclasses should extend this method if they define calculated attributes.//
return null;
}//calculatedAttributeRegister()//
/**
* Gets the updated value for a calculated attribute after the calculated attribute listener has been notified that a change has occured.
* <p>Note: If this call is in the context of a multi-thread accessable entity then the calling thread will have already locked on the entity's monitor.</p>
* @param attribute The attribute that was marked as AO_CALCULATED.
* @return The value of the calculated attribute.
*/
protected Object calculatedAttributeUpdate(Attribute attribute) {
//Subclasses should extend this method if they define calculated attributes.//
return null;
}//calculatedAttributeUpdate()//
/* (non-Javadoc)
* @see com.common.io.IExternalizable#readExternal(com.common.io.IObjectInputStream)
*/
public Object readExternal(com.common.io.IObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
in.readByte();
if(eventSupport == null) {
eventSupport = new EventSupport(this);
}//if//
if(attributeSupport == null) {
attributeSupport = new AttributeSupport(this, eventSupport);
attributeSupportAccess = attributeSupport.getAttributeSupportAccess();
}//if//
attributeSupport.readExternal(in);
attributeSupport.registerCalculatedAttributeListeners();
return null;
}//readExternal()//
/* (non-Javadoc)
* @see java.io.Externalizable#readExternal(java.io.ObjectInput)
*/
public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException {
in.readByte();
if(eventSupport == null) {
eventSupport = new EventSupport(this);
}//if//
if(attributeSupport == null) {
attributeSupport = new AttributeSupport(this, eventSupport);
attributeSupportAccess = attributeSupport.getAttributeSupportAccess();
}//if//
attributeSupport.readExternal(in);
attributeSupport.registerCalculatedAttributeListeners();
}//readExternal()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionPreSynchronize(java.lang.Object, com.foundation.attribute.ReflectionContext, com.foundation.attribute.ReflectionContext, com.foundation.common.IEntity, com.foundation.attribute.IReflectable, com.foundation.attribute.ReflectionContext)
*/
public void zzrReflectionPreSynchronize(Object reflectable, ReflectionContext currentContext, ReflectionContext destinationContext, IEntity newPartOfEntity, IReflectable cloned, ReflectionContext synchronizingContext) {
((Entity) reflectable).reflectionPreSynchronize(currentContext, destinationContext, newPartOfEntity, cloned, synchronizingContext);
}//reflectionPreSynchronize()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionPostInitialize(java.lang.Object, com.foundation.attribute.AbstractReflectData)
*/
public void zzrReflectionPostInitialize(Object reflectable, AbstractReflectData reflectionData) {
((Entity) reflectable).attributeSupport.reflectionPostInitialize((ReflectObjectData) reflectionData);
}//reflectionPostInitialize()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionLocalRegister(java.lang.Object, com.foundation.attribute.CreateReflectDataContext)
*/
public void zzrReflectionLocalRegister(Object reflectable, CreateReflectDataContext createReflectDataContext) {
((Entity) reflectable).attributeSupport.registerReflection(createReflectDataContext);
}//reflectionLocalRegister()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionInitialize(java.lang.Object, com.foundation.attribute.AbstractReflectData)
*/
public Object zzrReflectionInitialize(Object reflectable, AbstractReflectData reflectionData) {
return ((Entity) reflectable).attributeSupport.reflectionInitialize(reflectionData);
}//reflectionInitialize()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionGetLastUpdateMessageNumber(java.lang.Object, com.foundation.attribute.IReflectUpdateHandler)
*/
public int zzrReflectionGetLastUpdateMessageNumber(Object reflectable, IReflectUpdateHandler updateHandler) {
Entity entity = (Entity) Orb.getLocal(reflectable);
Monitor monitor = entity.getMonitor();
entity.lock(monitor);
try {
return entity.attributeSupport.reflectionGetLastUpdateMessageNumber(updateHandler);
}//try//
finally {
entity.unlock(monitor);
}//finally//
}//reflectionGetLastUpdateMessageNumber()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionDestroy(java.lang.Object)
*/
public void zzrReflectionDestroy(Object reflectable) {
((Entity) reflectable).attributeSupport.destroyReflection();
}//reflectionDestroy()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionCreateSafeCopy(java.lang.Object, com.foundation.attribute.ReflectionContext)
*/
public IReflectable zzrReflectionCreateSafeCopy(Object reflectable, ReflectionContext reflectionContext) {
return ((Entity) reflectable).reflectionCreateSafeCopy(reflectionContext);
}//reflectionCreateSafeCopy()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrIsReflection(java.lang.Object, com.foundation.attribute.ReflectionContext)
*/
public boolean zzrIsReflection(Object reflectable, ReflectionContext reflectionContext) {
return ((Entity) reflectable).attributeSupport.isReflection(reflectionContext);
}//isReflection()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrGetReflectionId(java.lang.Object)
*/
public Object zzrGetReflectionId(Object reflectable) {
return ((Entity) reflectable).attributeSupport.getReflectionId();
}//getReflectionId()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrGetReflectionContext(java.lang.Object)
*/
public ReflectionContext zzrGetReflectionContext(Object reflectable) {
return ((Entity) reflectable).attributeSupport.getReflectionContext();
}//getReflectionContext()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrCollectPostSynchronizeReflectables(java.lang.Object, com.foundation.attribute.CreateReflectDataContext)
*/
public void zzrCollectPostSynchronizeReflectables(Object reflectable, CreateReflectDataContext createReflectDataContext) {
((Entity) reflectable).attributeSupport.collectPostSynchronizeReflectables(createReflectDataContext);
}//collectPostSynchronizeReflectables()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectableObject#reflectionLoadAttribute(java.lang.String, com.foundation.attribute.IReflectUpdateHandler, com.foundation.attribute.ReflectRegistrationData)
*/
public final Object reflectionLoadAttribute(final String attributeName, final IReflectUpdateHandler updateHandler, final ReflectRegistrationData registrationData) {
Object result = null;
//Support reflections of reflections.//
if(attributeSupport.getReflectionContext() != null) {
//Since we allow reflections of reflections, we need to perform the unregister within the reflection context's thread.//
result = attributeSupport.getReflectionContext().execute(new IRunnable() {
public Object run() {
Attribute attribute = attributeSupport.getAttribute(attributeName);
Object value = attributeSupport.loadReflectionAttribute(attribute, updateHandler, registrationData);
if(value instanceof IReflectable && !attribute.isReflectAsImmutable()) {
ReflectDataContainer data = ((IReflectable) value).zzrReflectionRegister(updateHandler, registrationData);
//If the reflectable object thinks it already has a reflection in the given context then return a proxy to the value.//
value = data == null ? (Orb.isLocal(updateHandler) ? value : Orb.getProxy(value, IReflectable.class)) : data;
}//if//
return value;
}//run()//
});
}//if//
else {
Monitor monitor = getMonitor();
boolean isRemoteReflectable = false;
lock(monitor);
try {
Attribute attribute = attributeSupport.getAttribute(attributeName);
result = attributeSupport.loadReflectionAttribute(attribute, updateHandler, registrationData);
if(result instanceof IReflectable && !attribute.isReflectAsImmutable()) {
if(Orb.isLocal(result)) {
ReflectDataContainer data = ((IReflectable) result).zzrReflectionRegister(updateHandler, registrationData);
//If the reflectable object thinks it already has a reflection in the given context then return a proxy to the value.//
result = data == null ? (Orb.isLocal(updateHandler) ? result : Orb.getProxy(result, IReflectable.class)) : data;
}//if//
else {
isRemoteReflectable = true;
}//else//
}//if//
}//try//
finally {
unlock(monitor);
}//finally//
if(isRemoteReflectable) {
ReflectDataContainer data = ((IReflectable) result).zzrReflectionRegister((IReflectUpdateHandler) (Orb.isProxy(updateHandler) ? updateHandler : Orb.getProxy(updateHandler, IReflectUpdateHandler.class)), registrationData);
//If the reflectable object thinks it already has a reflection in the given context then return a proxy to the value.//
result = data == null ? (Orb.isLocal(updateHandler) ? result : Orb.getProxy(result, IReflectable.class)) : data;
}//if//
}//else//
return result;
}//reflectionLoadAttribute()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#zzrReflectionRegister(com.foundation.attribute.IReflectUpdateHandler, com.foundation.attribute.ReflectRegistrationData)
*/
public final ReflectDataContainer zzrReflectionRegister(final IReflectUpdateHandler updateHandler, final ReflectRegistrationData registrationData) {
ReflectDataContainer result = null;
//Support reflections of reflections.//
if(attributeSupport.getReflectionContext() != null) {
//Since we allow reflections of reflections, we need to perform the register within the reflection context's thread.//
result = (ReflectDataContainer) attributeSupport.getReflectionContext().execute(new IRunnable() {
public Object run() {
try {
return internalReflectionRegister(updateHandler, registrationData, null);
}//try//
catch(Throwable e) {
Debug.log(e);
return null;
}//catch//
}//run()//
});
}//if//
else {
Monitor monitor = getMonitor();
try {
result = internalReflectionRegister(updateHandler, registrationData, monitor);
}//try//
catch(Throwable e) {
Debug.log(e);
return null;
}//catch//
}//else//
return result;
}//reflectionRegister()//
/**
* Performs the actual work of registering a reflection of this entity.
* @param updateHandler The handler used to update the reflection once it has been created.
* @param registrationData The metadata associated with the creation of the reflection.
* @param monitor The monitor that is being locked. This is necessary in a multi-threaded context to ensure that data is not accessed by multiple threads at one time. This should be null if this is being called in a reflection context thread since no locking is required and all objects accessed via the given object will be objects within that reflection context.
* @return The data that will make up the reflection (ReflectDataContainer), or null if the reflectable has already been reflected by the calling context.
*/
protected final ReflectDataContainer internalReflectionRegister(IReflectUpdateHandler updateHandler, ReflectRegistrationData registrationData, Monitor monitor) {
CreateReflectDataContext createReflectDataContext = null;
ReflectDataContainer result = null;
if(monitor != null) {
lock(monitor);
}//if//
try {
if(!attributeSupport.isReflected(updateHandler)) {
LoadAttributesContext context = null;
MetadataContainer metadataContainer = registrationData.getMetadataContainer();
if(registrationData.getMetadataIndex() != null) {
Class cls = null;
try {
cls = Class.forName(registrationData.getMetadataIndex());
}//try//
catch(Throwable e) {}
if(cls != null) {
metadataContainer = MetadataService.getSingleton().getReflectionMetadata(cls);
}//if//
}//if//
createReflectDataContext = new CreateReflectDataContext(updateHandler, metadataContainer, registrationData.getMetadataIndex(), monitor, false);
//If metadata is provided then use it to pre-load the attributes that will be reflected.//
if(metadataContainer != null) {
context = new LoadAttributesContext(this, metadataContainer, LoadAttributesContext.LOAD_ATTRIBUTE_OPTION_EXCLUDE_CALCULATED_ATTRIBUTES);
}//if//
//Recursively add reflection data using the metadata container as a guide to what to reflect (include only part-of references), otherwise if there is no metadata then reflect only loaded attributes and pass proxies to IReflectable instances.//
attributeSupport.registerReflection(createReflectDataContext);
//If metadata is provided then clean up after the pre-load.//
if(context != null) {
context.release();
}//if//
}//if//
}//try//
finally {
if(monitor != null) {
unlock(monitor);
}//if//
}//finally//
if(createReflectDataContext != null) {
//Reflect other non-part-of objects that the metadata indicates needs reflecting. This requires creating a new load attributes context.//
createReflectDataContext.processPendingExternalReflectables();
result = new ReflectDataContainer(createReflectDataContext.getReflectionDataContainer());
}//if//
else {
//result = Orb.isLocal(updateHandler) ? this : Orb.getProxy(this, IReflectable.class);
result = null;
}//else//
return result;
}//internalReflectionRegister()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#reflectionSynchronize(com.foundation.attribute.IReflectUpdateHandler, com.foundation.attribute.AbstractReflectData, com.foundation.attribute.CreateReflectDataContext)
*/
public final AbstractReflectData reflectionSynchronize(IReflectUpdateHandler updateHandler, AbstractReflectData data, CreateReflectDataContext createReflectDataContext) {
return attributeSupport.synchronizeReflection(updateHandler, data, createReflectDataContext);
}//reflectionSynchronize()//
/* (non-Javadoc)
* @see com.foundation.attribute.IReflectable#reflectionUnregister(com.foundation.attribute.IReflectUpdateHandler)
*/
public final void reflectionUnregister(final IReflectUpdateHandler updateHandler) {
//Support reflections of reflections.//
if(attributeSupport.getReflectionContext() != null) {
//Since we allow reflections of reflections, we need to perform the unregister within the reflection context's thread.//
attributeSupport.getReflectionContext().execute(new IRunnable() {
public Object run() {
attributeSupport.unregisterReflection(updateHandler);
return null;
}//run()//
});
}//if//
else {
Monitor monitor = getMonitor();
lock(monitor);
try {
attributeSupport.unregisterReflection(updateHandler);
}//try//
finally {
unlock(monitor);
}//finally//
}//else//
}//reflectionUnregister()//
/**
* Recursively prepares the reflectable (non-reflection) prior to being part of a synchronize action via the current reflection context.
* <p>For example, if a view creates a new model object and makes it part of an existing reflection of a logical model, then this method will be called on the new model object prior to synchronizing the changes.</p>
* <p>Note: This method only gets called if this object is not a reflection in the current context (in which case it is never going to be a reflection at all), but is referenced by a reflection that is being synchronized.
* This method should prepare a non-reflection for being moved to a different reflection context (or no reflection context) by replacing any references to reflections in the current context.</p>
* <p>This method does not copy this object, but does make permenant changes.</p>
* @param currentContext The current reflection context that is preparing to synchronize this object. This object might not be a reflection in the current context if this object is newly created.
* @param destinationContext The reflection context that this object is being synchronized to, if known. This may be null if the context is remote, or if not synchronizing to another reflection context. This is primarily used to make coding views more flexable since one view may base some but not all its data on the previous view's reflections.
* @param newPartOfEntity The part of entity reference that should be set during a recursive call. Initial callers should ALWAYS pass null.
* @param cloned The reflectable that this reflectable is a clone of. All non-reflections are cloned prior to synchronization so that the new shared objects will not have any ties to old proxies. Proxies to the non-clone will continue to work since the synchronization will either fail (in which case the proxied non-clone object doesn't change), or will succeed and pass the reflection data back to the reflection context which will utilize the non-clone object as the reflection object.
* @param synchronizingContext The reflection context that is performing the synchronization operation.
*/
private void reflectionPreSynchronize(ReflectionContext currentContext, ReflectionContext destinationContext, IEntity newPartOfEntity, IReflectable cloned, ReflectionContext synchronizingContext) {
//Clear the part-of relationship so that this entity can become part-of the destination model tree.//
getAttributeSupportAccess().internalSetPartOfEntity(getAttributeSupport(), newPartOfEntity);
getAttributeSupport().setReflectionContext(destinationContext);
//Copy the cloned object's hash since its hash must be the same as the hash of the object it reflects (and it will reflect this object once it has been succesfully synchronized).//
//Note: Since the cloned object is being reused as a reflection upon successful synchronization, its hash must not change, otherwise hashmap lookups (as are often used in the view components) will fail.//
if(cloned != null) {
getAttributeSupport().setHash(((Entity) cloned).getAttributeSupport().getHash());
}//if//
//Link this model to the cloned model via a unique number within the context of the Reflection Context.//
getAttributeSupport().setModelMappingNumber(currentContext.createModelMapping(cloned));
if(newPartOfEntity == null) {
newPartOfEntity = this;
}//if//
//Iterate over the contents to dereflect reflections in this context, and to fix the objects if they are part of the list.//
for(int index = 0; index < getAttributeSupport().getAttributeCount(); index++) {
if(getAttributeSupport().getIsAttributeRealized(index)) {
Object value = getAttributeSupport().getAttributeValue(index);
if(value instanceof IReflectable) {
//If the value is a reflection in this context then de-reflect it.//
if(((IReflectable) value).zzrIsReflection(value, currentContext)) {
//
// Note:
//Don't worry about the value being part-of since that is impossible.
//It isn't possible because this object isn't a reflection in the current context (it is new which is why this is being called).
//So that means this value must be a reference to something else in this logical object that already exists in the reflected logical object.
//Or it is a reference to something not in the logical object that already exists in the destination context, or will soon.
//
value = ((IReflectable) value).getReflected();
//If the value is not a reflection in the destination context then make it one.//
if((destinationContext != null) && (!((IReflectable) value).zzrIsReflection(value, destinationContext))) {
value = destinationContext.createReflection((IReflectable) value);
}//if//
//If the value has changed then update this object.//
getAttributeSupportAccess().setAttributeValue(getAttributeSupport(), index, value, CONTEXT_UNTRUSTED, true);
}//if//
else if((((IReflectable) value).isReflection()) && (Orb.isLocal(((IReflectable) value).getReflected()))) {
//Note: Non-local reflections not of this context are allows since they are assumed to be non-view based reflections.//
Debug.log(new RuntimeException("Error: Detected a reflection in the wrong context."));
}//else if//
else if(getAttributeSupport().getIsAttributePartOf(index)) {
IReflectable clonedValue = (IReflectable) ((Entity) cloned).getAttributeSupport().getAttributeValue(index);
//If the value is part of this object then pre-synchronize the value.//
((IReflectable) value).zzrReflectionPreSynchronize(value, currentContext, destinationContext, newPartOfEntity, clonedValue, synchronizingContext);
}//else if//
}//if//
}//if//
}//for//
}//reflectionPreSynchronize()//
/**
* Creates a 'reflection safe' copy of this reflectable object.
* That means each attribute (or contained value in the case of collections) must be examined.
* If the value is an IReflectable and is a reflection then it must be dereferenced (the reflected reference used in its stead).
* If the value is an IReflectable and it isn't a reflection then it must have a safe copy made and used in place of the value.
* All other value types are simply referenced as is.
* <p>This is useful when synchronizing a reflection which references a new object which may reference other new objects or existing objects (as reflections).
* In such a case the synchronized object notices that it has a non-reflection value reference that should be a reflection and it assumes that it is new.
* The new value needs to be copied so that the copy doesn't have preconcieved notions of its monitor
* @param reflectionContext The reflection context for the copy.
* @return The safe copy of this reflection, which can either be the reflected value, or a copy of this non-reflected value.
*/
private IReflectable reflectionCreateSafeCopy(ReflectionContext reflectionContext) {
/* This code created a copy of this reflectable object. The problem with a copy (besides being inefficient, is that multiple references create multiple copies to the same object. This could be fixed by tracking the copies in the context, but it might be better to just modify the original object.
synchronized(getMonitor()) {
IReflectable result = null;
//If this isn't a reflection in the current context (it shouldn't be) then create a clone and adjust it to remove reflections in this context and to copy non-reflections which implement IReflectable and are part of this entity.//
if(isReflection(reflectionContext)) {
result = attributeSupport.getReflectedObject();
}//if//
else {
Entity clone = (Entity) clone();
result = clone;
clone.attributeSupport.completeCreateSafeCopy(reflectionContext);
}//else//
return result;
}//synchronized//
*/
return this;
}//reflectionCreateSafeCopy()//
/**
* Registers the type metadata from the class with the metadata service.
* @param type The class the metadata relates to.
* @param managementType The management type identifier. Must be one of the AO_MANAGEMENT_TYPE_xxx identifiers.
*/
public static void registerTypeMetadata(Class declaringType, int managementType) {
MetadataService.getSingleton().addTypeMetadata(declaringType, managementType);
}//registerTypeMetadata()//
/**
* Provides a simple mechanism for a class using enhanced events to register those events in the class's static initializers.
* <p>Warning: Only custom events should be registered. Attribute events are automatically registered for the class when the attribute is registered.</p>
* @param declaringType The class that is registering the custom event.
* @param name The name of the event which can be used to dynamically refer to the event.
* @return The unique (for the declaring type hierarchy) event identifier which can be used to fire or listen on the event.
*/
public static Event registerEvent(Class declaringType, String name) {
return MetadataService.getSingleton().addEvent(declaringType, name);
}//registerEvent()//
/**
* Provides a simple mechanism for a class using enhanced attributes to register those attributes in the class's static initializers.
* @param declaringType The class that is registering the attribute.
* @param name The name of the attribute which can be used to dynamically refer to the attribute.
* @return The unique (for the declaring type hierarchy) attribute identifier which can be used to access the attribute value.
*/
public static Attribute registerAttribute(Class declaringType, String name) {
return registerAttribute(declaringType, name, AO_REFERENCED);
}//registerAttribute()//
/**
* Provides a simple mechanism for a class using enhanced attributes to register those attributes in the class's static initializers.
* @param declaringType The class that is registering the attribute.
* @param name The name of the attribute which can be used to dynamically refer to the attribute.
* @param defaultValue The immutable value to be used if the attribute doesn't have a value. This should only be used for immutable objects such as BigDecimal, Integer, String, etc., or objects that will only be used in an immutable fashion (usually java.util.Date falls into this category).
* @return The unique (for the declaring type hierarchy) attribute identifier which can be used to access the attribute value.
*/
public static Attribute registerAttribute(Class declaringType, String name, Object defaultValue) {
return registerAttribute(declaringType, name, AO_REFERENCED, defaultValue);
}//registerAttribute()//
/**
* Provides a simple mechanism for a class using enhanced attributes to register those attributes in the class's static initializers.
* @param declaringType The class that is registering the attribute.
* @param name The name of the attribute which can be used to dynamically refer to the attribute.
* @param options The options associated with the registered attribute.
* @return The unique (for the declaring type hierarchy) attribute identifier which can be used to access the attribute value.
* @see #AO_REFERENCED
* @see #AO_PART_OF
* @see #AO_LAZY
* @see #AO_WEAK
*/
public static Attribute registerAttribute(Class declaringType, String name, int options) {
return AttributeSupport.registerAttribute(declaringType, name, options);
}//registerAttribute()//
/**
* Provides a simple mechanism for a class using enhanced attributes to register those attributes in the class's static initializers.
* @param declaringType The class that is registering the attribute.
* @param name The name of the attribute which can be used to dynamically refer to the attribute.
* @param options The options associated with the registered attribute.
* @param defaultValue The immutable value to be used if the attribute doesn't have a value. This should only be used for immutable objects such as BigDecimal, Integer, String, etc., or objects that will only be used in an immutable fashion (usually java.util.Date falls into this category).
* @return The unique (for the declaring type hierarchy) attribute identifier which can be used to access the attribute value.
* @see #AO_REFERENCED
* @see #AO_PART_OF
* @see #AO_LAZY
* @see #AO_WEAK
*/
public static Attribute registerAttribute(Class declaringType, String name, int options, Object defaultValue) {
return AttributeSupport.registerAttribute(declaringType, name, options, defaultValue);
}//registerAttribute()//
/**
* Provides a simple mechanism for a class using enhanced attributes to register those attributes in the class's static initializers.
* This variation of the register method sets up an attribute whose value will be the entity referencing the registering class as part-of if the referencing type is an instance of <code>boundType</code>.
* If the instance of this class is not part of another entity (directly or indirectly) or if the part-of entity is not of the given type, then the attribute will be assigned a null value.
* @param declaringType The class that is registering the attribute.
* @param name The name of the attribute which can be used to dynamically refer to the attribute.
* @param boundType The class of entity that the binding applies to. This variation will store the entity reference as the attribute value.
* @return The unique (for the declaring type hierarchy) attribute identifier which can be used to access the attribute value.
*/
public static Attribute registerAttribute(Class declaringType, String name, Class boundType) {
return AttributeSupport.registerAttribute(declaringType, name, boundType);
}//registerAttribute()//
/**
* Provides a simple mechanism for a class using enhanced attributes to register those attributes in the class's static initializers.
* This variation of the register method allows the value of the attribute to be bound to a specific attribute of another entity referencing the registering class as part-of (if the referencing type is an instance of <code>boundType</code>).
* If the instance of this class is not part of another entity (directly or indirectly) or if the part-of entity is not of the given type, then the attribute will be assigned a null value.
* <p><b>Warning:</b>
* This is very useful to setup automated key reference for static keys, but should only be used for static keys and any other use may cause unintended behavior.
* A static key is one that is assigned to an object when it is created in the repository or in the heap, and does not change ever.
* If the value does change then it won't be updated in the bound attribute until the binding object is saved to a repository.</p>
* @param declaringType The class that is registering the attribute.
* @param name The name of the attribute which can be used to dynamically refer to the attribute.
* @param relatedAttribute The attribute defined by this entity whose value will define the bound attribute.
* @param boundAttribute The related attribute's value's attribute whose value will be referenced by the attribute currently being defined.
* @return The unique (for the declaring type hierarchy) attribute identifier which can be used to access the attribute value.
*/
public static Attribute registerAttribute(Class declaringType, String name, Attribute relatedAttribute, Attribute boundAttribute) {
return AttributeSupport.registerAttribute(declaringType, name, relatedAttribute.getNumber(), boundAttribute.getNumber());
}//registerAttribute()//
/**
* Provides a simple mechanism for a class using enhanced collection attributes to register those attributes in the class's static initializers.
* @param declaringType The class that is registering the attribute.
* @param name The name of the attribute which can be used to dynamically refer to the attribute.
* @param options The options associated with the registered attribute.
* @return The unique (for the declaring type hierarchy) attribute identifier which can be used to access the attribute value.
* @see #AO_REFERENCED
* @see #AO_PART_OF
* @see #AO_LAZY
* @see #AO_WEAK
*/
public static Attribute registerCollection(Class declaringType, String name, int options) {
return AttributeSupport.registerCollection(declaringType, name, options);
}//registerCollection()//
/* (non-Javadoc)
* @see com.common.event.IEventEmitter#registerListener(int, com.common.event.IHandler, boolean)
*/
public void registerListener(int event, IHandler handler, boolean inline) {
getEventSupport().registerListener(event, handler, inline);
}//registerListener()//
/* (non-Javadoc)
* @see com.foundation.event.IEventEmitter#registerListener(com.foundation.metadata.Event, com.foundation.event.IHandler, boolean)
*/
public void registerListener(Event event, IHandler handler, boolean inline) {
getEventSupport().registerListener(event.getNumber(), handler, inline);
}//registerListener()//
/**
* @deprecated Call resetObjectChangeFlags() instead.
*/
public void resetChangeFlags() {
resetObjectChangeFlags();
}//resetChangeFlags()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#resetObjectChangeFlags()
*/
public void resetObjectChangeFlags() {
attributeSupport.resetObjectChangeFlags();
}//resetChangeFlags()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#resetVirtualObjectChangeFlags()
*/
public void resetVirtualObjectChangeFlags() {
attributeSupport.resetVirtualObjectChangeFlags();
}//resetVirtualObjectChangeFlags()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#reverseObjectChanges()
*/
public void reverseObjectChanges() {
getAttributeSupport().undoObjectChanges();
}//reverseObjectChanges()//
/* (non-Javadoc)
* @see com.foundation.common.IEntity#reverseVirtualObjectChanges()
*/
public void reverseVirtualObjectChanges() {
getAttributeSupport().undoVirtualObjectChanges();
}//reverseObjectChanges()//
/**
* Assigns a new value to an attribute.
* <p>Fires an event "[attributeName]Changed" but does not pass the value. It does not pass the value because the value may not be the same as the one returned by the getter method.
* @param attribute The unique number of the attribute being changed.
* @param value The new value for the attribute. Note that calling this method does not guarrentee that the value will be accessable to other threads until the calling thread exits a synchronization block or terminates.
* @return Whether the value was set. This will only be false if the old value and new values are considered equal.
*/
protected boolean setAttributeValue(Attribute attribute, Object value) {
if(attribute == null) {
Debug.log("Attribute reference is null; Most likely cause is you are instantiating a singleton instance from within the class and the singleton is defined before the attributes are defined.");
}//if//
return getAttributeSupportAccess().setAttributeValue(getAttributeSupport(), attribute.getNumber(), value, CONTEXT_UNTRUSTED, true);
}//setAttributeValue()//
/**
* Sets the transaction service's access object so that it may access protected methods of the entity objects.
* <p>This is necessary to allow the transaction service to tightly integrate with the objects it uses in transactions.
* @param transactionService A transaction service instance that will be given a TransactionServiceAccessObject instance.
*/
public static void setTransactionServiceAccessObject(TransactionService transactionService) {
if(transactionService != null) {
transactionService.setEntityObjectAccess(new TransactionServiceAccessObject());
}//if//
}//setTransactionServiceAccessObject()//
/**
* Called when the value being set is supposed to already be the value.
* <p>Do not call this method if the attributes value is changing and that change needs tracking.</p>
* <p>Warning: If the value has been changed since it was initially set then setting the trusted value will alter the original value and not the current value.</p>
* @param attribute The unique number of the attribute being changed.
* @param value The new value for the attribute. Note that calling this method does not guarrentee that the value will be accessable to other threads until the calling thread exits a synchronization block or terminates.
*/
protected void setTrustedAttributeValue(Attribute attribute, Object value) {
getAttributeSupportAccess().setAttributeValue(getAttributeSupport(), attribute.getNumber(), value, CONTEXT_TRUSTED, true);
}//setTrustedAttributeValue()//
/* (non-Javadoc)
* @see com.common.event.IEventEmitter#unregisterListener(int, com.common.event.IHandler)
*/
public void unregisterListener(int event, IHandler handler) {
getEventSupport().unregisterListener(event, handler);
}//unregisterListener()//
/* (non-Javadoc)
* @see com.foundation.event.IEventEmitter#unregisterListener(com.foundation.metadata.Event, com.foundation.event.IHandler)
*/
public void unregisterListener(Event event, IHandler handler) {
getEventSupport().unregisterListener(event.getNumber(), handler);
}//unregisterListener()//
/**
* Reads the object from a stream.
* @param in The input stream to read from.
*/
public void writeExternal(com.common.io.IObjectOutputStream out) throws java.io.IOException {
Monitor monitor = getMonitor();
lock(monitor);
try {
if(isReflection()) {
throw new java.io.IOException("Cannot serialize a reflection. You may serialize the reflected object however.");
}//if//
out.writeByte(0);
getAttributeSupport().writeExternal(out);
}//try//
finally {
unlock(monitor);
}//finally//
}//writeExternal()//
/**
* Writes the object to a stream.
* @param out The stream to write to.
*/
public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
Monitor monitor = getMonitor();
lock(monitor);
try {
if(isReflection()) {
throw new java.io.IOException("Cannot serialize a reflection. You may serialize the reflected object however.");
}//if//
out.writeByte(0);
getAttributeSupport().writeExternal(out);
}//try//
finally {
unlock(monitor);
}//finally//
}//writeExternal()//
/**
* Obtains a lock on the given object.
* <p><b>Warning: If the object reference does not implement ISupportsContainment (or is null) then the global monitor will be used for the lock.</b></p>
* <p><b>Warning: It is possible that the thread's lock on this object's monitor will be suspended if a deadlock situation is detected. The lock will be re-obtained when the passed object is unlocked.</b></p>
* <p><b>Warning: The caller must call this inside a try block and must call unlock inside the finally part of the try block. The caller should not lock more than one object at a time.</b></p>
* @param object The object to be locked.
*/
protected void lock(Object object) {
getAttributeSupport().lock(object);
}//lock()//
/**
* Releases the lock on the given object.
* If a previous lock was held and suspended when the given object was locked then that lock will be resumed automatically.
* @param object The object to be unlocked.
*/
protected void unlock(Object object) {
getAttributeSupport().unlock(object);
}//unlock()//
/**
* Blocks the thread for the given time period, or until notify is called.
* <p><b>Warning: This method actually calls wait on the calling thread's Thread object. If used, the application may not use thread objects for calling wait/notify/notifyAll otherwise unexpected results will ensue.</b></p>
* @param object The object whose monitor will be waited on (the calling thread must already have locked on this monitor).
* @param timeout The count of milliseconds before the wait ends, or zero if it will wait indefinately.
*/
protected void wait(Object object) {
getAttributeSupport().wait(object, 0);
}//wait()//
/**
* Blocks the thread for the given time period, or until notify is called.
* <p><b>Warning: This method actually calls wait on the calling thread's Thread object. If used, the application may not use thread objects for calling wait/notify/notifyAll otherwise unexpected results will ensue.</b></p>
* @param object The object whose monitor will be waited on (the calling thread must already have locked on this monitor).
* @param timeout The count of milliseconds before the wait ends, or zero if it will wait indefinately.
*/
protected void wait(Object object, long timeout) {
getAttributeSupport().wait(object, timeout);
}//wait()//
/**
* Notifies the longest blocked thread that it can run again.
* <p><b>Warning: This method actually calls wait on the calling thread's Thread object. If used, the application may not use thread objects for calling wait/notify/notifyAll otherwise unexpected results will ensue.</b></p>
* @param object The object whose monitor the waiting thread to be unblocked will have waited on.
*/
protected void notify(Object object) {
getAttributeSupport().notify(object);
}//notify()//
/**
* Notifies the longest blocked thread that it can run again.
* <p><b>Warning: This method actually calls wait on the calling thread's Thread object. If used, the application may not use thread objects for calling wait/notify/notifyAll otherwise unexpected results will ensue.</b></p>
* @param object The object whose monitor the waiting thread to be unblocked will have waited on.
*/
protected void notifyAll(Object object) {
getAttributeSupport().notifyAll(object);
}//notifyAll()//
}//Entity//