Java tutorial
/** Zeidon JOE is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Zeidon JOE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Zeidon JOE. If not, see <http://www.gnu.org/licenses/>. Copyright 2009-2015 QuinSoft */ package com.quinsoft.zeidon.standardoe; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import com.google.common.collect.MapMaker; import com.quinsoft.zeidon.ActivateFlags; import com.quinsoft.zeidon.AttributeInstance; import com.quinsoft.zeidon.CompareEntityOptions; import com.quinsoft.zeidon.CopyAttributesBuilder; import com.quinsoft.zeidon.CursorPosition; import com.quinsoft.zeidon.CursorResult; import com.quinsoft.zeidon.EntityConstraintType; import com.quinsoft.zeidon.EntityInstance; import com.quinsoft.zeidon.EntityIterator; import com.quinsoft.zeidon.EventNotification; import com.quinsoft.zeidon.HiddenAttributeException; import com.quinsoft.zeidon.MaxCardinalityException; import com.quinsoft.zeidon.RequiredAttributeException; import com.quinsoft.zeidon.RequiredEntityMissingException; import com.quinsoft.zeidon.SetMatchingFlags; import com.quinsoft.zeidon.SubobjectValidationException; import com.quinsoft.zeidon.TemporalEntityException; import com.quinsoft.zeidon.View; import com.quinsoft.zeidon.ZeidonException; import com.quinsoft.zeidon.domains.Domain; import com.quinsoft.zeidon.objectdefinition.AttributeDef; import com.quinsoft.zeidon.objectdefinition.AttributeHashKeyType; import com.quinsoft.zeidon.objectdefinition.DynamicAttributeDefConfiguration; import com.quinsoft.zeidon.objectdefinition.EntityDef; import com.quinsoft.zeidon.objectdefinition.EntityDef.LinkValidation; import com.quinsoft.zeidon.objectdefinition.LodDef; import com.quinsoft.zeidon.utils.JoeUtils; import com.quinsoft.zeidon.utils.KeyStringBuilder; /** * @author DG * */ class EntityInstanceImpl implements EntityInstance { // These are for compatibility with the old C OE. private static final int FLAG_HANGING_ENTITY = 0x00000001; private static final int FLAG_CREATED = 0x00000002; private static final int FLAG_UPDATED = 0x00000004; private static final int FLAG_DELETED = 0x00000008; private static final int FLAG_EXCLUDED = 0x00000010; private static final int FLAG_INCLUDED = 0x00000020; private static final int FLAG_HIDDEN = 0x00000400; private final ObjectInstance objectInstance; private final EntityDef entityDef; /** * Depth of this EI from the root. If this is root then depth = 1. */ private final int depth; private final long entityKey; private UUID uuid; private EntityInstanceImpl parentInstance; private EntityInstanceImpl prevHierInstance; private EntityInstanceImpl nextHierInstance; private EntityInstanceImpl prevTwinInstance; private EntityInstanceImpl nextTwinInstance; private boolean updated = false; private boolean deleted = false; private boolean created = false; private boolean included = false; private boolean hidden = false; private boolean excluded = false; private boolean dropped = false; /** * Map of persistent attributes. Linked entities will reference the same * persistentAttributes. * Key = ER Attribute token */ private Map<String, AttributeValue> persistentAttributes; /** * Map of work attributes. Every entity, even linked ones, will have their * own set of workAttributes. * Key = ER Attribute token */ private Map<String, AttributeValue> workAttributes; /** * List of instances linked with this one. This is a set of weak references; * when one of the entity instances is dropped the GC will remove it from * this list. The boolean value a dummy value required to make this a map. */ private volatile ConcurrentMap<EntityInstanceImpl, Boolean> linkedInstances; /** * This keeps track of attribute hash keys that are under this EI. Intended for use * by cursor.setFirst() processing. */ private AttributeHashKeyMap attributeHashkeyMap; /** * Temporal subobject information. */ private enum VersionStatus { /** * Entity is not versioned (may be result of accepted entity) */ NONE, /** * Entity is part of a versioned subobject and has not been accepted/canceled yet. */ UNACCEPTED, /** * Entity is root of versioned subobject and has not been accepted/canceled yet. */ UNACCEPTED_ROOT, /** * Entity created by createTemporalEntity(). */ UNACCEPTED_ENTITY, /** * This was a temporal entity but it was canceled. */ CANCELED, /** * This entity has been superseded by a newer temporal entity that was accepted. */ SUPERSEDED; } private EntityInstanceImpl prevVersion; private EntityInstanceImpl nextVersion; private VersionStatus versionStatus; private long versionNumber; // // "Meta" information. private String tag; // Used for merging OIs across servers. // Information used by commit methods. private long hierIndex; private boolean isWritten; private boolean isRecordOwner; /** * If true then children for this EI were activated with qualification. * This means it's possible not all children were loaded and therefore * a delete could possibly cause data integrity issues. */ private boolean incomplete; // ---------------------------------- // Following flags used during commit to DB. // ---------------------------------- boolean dbhCreated; boolean dbhDeleted; boolean dbhExcluded; boolean dbhUpdated; boolean dbhIncluded; boolean dbhNeedsInclude; // True if linked entity was created. boolean dbhSeqUpdated; boolean dbhGenKeyNeeded; boolean dbhNoGenKey; boolean dbhForeignKey; boolean dbhNeedsCommit; boolean dbhLoaded; // True if this EI was just loaded by the DBHandler. /** * This keeps track of child entity instances that have been loaded lazily. */ private Set<EntityDef> entitiesLoadedLazily; static EntityInstanceImpl createEntity(ObjectInstance oi, EntityInstanceImpl parent, EntityInstanceImpl relativeEntity, EntityDef entityDef, CursorPosition position) { // Create a new instance and initialize the attributes. EntityInstanceImpl newInstance = new EntityInstanceImpl(oi, entityDef, parent, relativeEntity, position); newInstance.setCreated(true); newInstance.workAttributes = new HashMap<>(entityDef.getWorkAttributeCount()); newInstance.persistentAttributes = new HashMap<>(entityDef.getPersistentAttributeCount()); newInstance.linkedInstances = null; return newInstance; } /** * Creates an empty entity instance. This gives us a way to create uninitialized * entity instances used for temporary traversal. */ EntityInstanceImpl(EntityDef entityDef) { super(); this.entityDef = entityDef; objectInstance = null; entityKey = NumberUtils.LONG_ZERO; depth = -1; } /** * Creates an entity instance and adds it to the OI. * * @param objectInstance * @param entityDef * @param parentInstance * @param relativeInstance * @param position */ EntityInstanceImpl(ObjectInstance objectInstance, EntityDef entityDef, EntityInstanceImpl parentInstance, EntityInstanceImpl relativeInstance, CursorPosition position) { this(objectInstance, entityDef, parentInstance); insertInstance(objectInstance, parentInstance, relativeInstance, position, null); } /** * Creates an entity instance without inserting it into the chain. * * @param objectInstance * @param entityDef * @param parentInstance * @param initAttributes */ EntityInstanceImpl(ObjectInstance objectInstance, EntityDef entityDef, EntityInstanceImpl parentInstance) { assert objectInstance.getLodDef() == entityDef.getLodDef(); this.objectInstance = objectInstance; this.entityDef = entityDef; // Set a unique identifier for this entity. We use a number that's unique across // all tasks in case the entity is included into another task. this.entityKey = getTask().getObjectEngine().getNextObjectKey(); // Copy some values from parentInstance if it isn't null. if (parentInstance != null) { this.setParent(parentInstance); this.depth = parentInstance.getDepth() + 1; if (parentInstance.isVersioned()) setVersionStatus(VersionStatus.UNACCEPTED); else { // If we get here then parent instance isn't versioned so status should always be none. assert parentInstance.versionStatus == VersionStatus.NONE; setVersionStatus(VersionStatus.NONE); } this.versionNumber = parentInstance.versionNumber; } else { setVersionStatus(VersionStatus.NONE); this.versionNumber = 0; this.depth = 1; } workAttributes = new HashMap<>(getEntityDef().getWorkAttributeCount()); } void initializeDefaultAttributes() { for (AttributeDef attributeDef : getEntityDef().getAttributes(true)) { Domain domain = attributeDef.getDomain(); if (!StringUtils.isBlank(attributeDef.getInitialValue())) { // Use the domain to convert the string to an internal value. Then // set the value using setInternalValue. This bypasses the restriction // on read-only attributes. Object internalValue = domain.convertExternalValue(getTask(), null, attributeDef, null, attributeDef.getInitialValue()); getAttribute(attributeDef).setInternalValue(internalValue, false); } else if (!attributeDef.isHidden()) { if (domain.hasInitialValue(getTask(), attributeDef)) domain.setInitialValue(getAttribute(attributeDef)); } } } public ObjectInstance getObjectInstance() { return objectInstance; } @Override public EntityDef getEntityDef() { return entityDef; } /** * If this EntityDef is recursive then this returns the recursive parent, * otherwise returns getEntityDef(). * * @return */ EntityDef getBaseEntityDef() { return entityDef.getBaseEntityDef(); } EntityInstanceImpl getLatestVersion() { return getLatestVersion(this); } private EntityInstanceImpl getLatestVersion(EntityInstanceImpl ei) { if (ei == null) return null; // Skip canceled entities UNLESS there is no prevVersion. When prevVersion is null // then this entity was created by createTemporalEntity and we will consider this // the latest version. This is necessary for repositioning after canceling the // temporal create. while (ei.versionStatus == VersionStatus.CANCELED && ei.prevVersion != null) ei = ei.prevVersion; while (ei.nextVersion != null) { assert ei.versionNumber < ei.nextVersion.versionNumber; ei = ei.nextVersion; } return ei; } @Override public EntityInstanceImpl getParent() { return getLatestVersion(parentInstance); } EntityInstanceImpl getPrevVersion() { return prevVersion; } // **************************************** // Assertion methods for asserting correctness of list pointers. // **************************************** private boolean assertNextTwin() { for (EntityInstanceImpl ei = getNextTwin(); ei != null; ei = ei.getNextTwin()) { if (ei == this) return false; } return true; } private boolean assertPrevTwin() { for (EntityInstanceImpl ei = getPrevTwin(); ei != null; ei = ei.getPrevTwin()) { if (ei == this) return false; } return true; } /** * Verifies that: * - 'this' is not repeated in the hier chain. * * @return */ private boolean assertNextHier() { for (EntityInstanceImpl ei = getNextHier(); ei != null; ei = ei.getNextHier()) { if (ei == this) return false; } return true; } private boolean assertPrevHier() { for (EntityInstanceImpl ei = getPrevHier(); ei != null; ei = ei.getPrevHier()) { if (ei == this) return false; } return true; } private boolean assertParent() { for (EntityInstanceImpl ei = getParent(); ei != null; ei = ei.getParent()) { if (ei == this) return false; } return true; } /** * For ease of use this can take a null argument. * * @param parent * @return False if 'child' is not a child of 'this'. */ private boolean validateParentage(EntityInstanceImpl child) { if (child == null) return true; for (EntityInstanceImpl scan = child; scan != null; scan = scan.getParent()) { if (scan == this) return true; } return false; } void setParent(EntityInstanceImpl parent) { this.parentInstance = getLatestVersion(parent); assert assertParent(); // We match by ER entity token to handle recursive cases. assert getEntityDef().getParent().getErEntityToken() == parent.getEntityDef() .getErEntityToken() : "Setting parent to mismatching EntityDef. Parent = " + parent.getEntityDef() + ", child = " + getEntityDef(); } EntityInstanceImpl getPrevHier() { return getLatestVersion(prevHierInstance); } void setPrevHier(EntityInstanceImpl prevHier) { this.prevHierInstance = getLatestVersion(prevHier); assert assertPrevHier(); } EntityInstanceImpl getNextHier() { return getLatestVersion(nextHierInstance); } void setNextHier(EntityInstanceImpl nextHier) { this.nextHierInstance = getLatestVersion(nextHier); assert assertNextHier(); } @Override public EntityInstanceImpl getPrevTwin() { return getLatestVersion(prevTwinInstance); } void setPrevTwin(EntityInstanceImpl prevTwin) { this.prevTwinInstance = getLatestVersion(prevTwin); assert assertPrevTwin(); assert prevTwin == null || prevTwin.getEntityDef() == getEntityDef(); } @Override public EntityInstanceImpl getNextTwin() { return getLatestVersion(nextTwinInstance); } void setNextTwin(EntityInstanceImpl nextTwin) { this.nextTwinInstance = getLatestVersion(nextTwin); assert assertNextTwin(); assert nextTwin == null || nextTwin.getEntityDef() == getEntityDef(); } void setNextVersion(EntityInstanceImpl nextVersion) { this.nextVersion = nextVersion; assert nextVersion == null || nextVersion.getEntityDef() == getEntityDef(); } @Override public boolean hasNextTwin() { for (EntityInstanceImpl ei = getNextTwin(); ei != null; ei = ei.getNextTwin()) { if (!ei.isHidden()) return true; } return false; } @Override public boolean hasPrevTwin() { for (EntityInstanceImpl ei = getPrevTwin(); ei != null; ei = ei.getPrevTwin()) { if (!ei.isHidden()) return true; } return false; } /** * Copy all the next/prev pointers. * * @param src */ private void copyAllPointers(EntityInstanceImpl src) { assert getEntityDef() == src.getEntityDef(); // Don't use the set...() methods (eg. setNextHier) because it uses getLatestVersion() and // we want to set it explicitly. parentInstance = src.getParent(); nextHierInstance = src.getNextHier(); prevHierInstance = src.getPrevHier(); nextTwinInstance = src.getNextTwin(); prevTwinInstance = src.getPrevTwin(); assert assertNextTwin(); assert assertPrevTwin(); assert assertNextHier(); assert assertPrevHier(); assert assertParent(); } LodDef getLodDef() { return objectInstance.getLodDef(); } long getInstanceFlags() { long flags = 0; if (isUpdated()) flags |= FLAG_UPDATED; if (isDeleted()) flags |= FLAG_DELETED; if (isCreated()) flags |= FLAG_CREATED; if (isIncluded()) flags |= FLAG_INCLUDED; if (isExcluded()) flags |= FLAG_EXCLUDED; if (isHidden()) flags |= FLAG_HIDDEN; return flags; } Iterable<AttributeDef> getNonNullAttributeList() { return new Iterable<AttributeDef>() { @Override public Iterator<AttributeDef> iterator() { return new Iterator<AttributeDef>() { private int attributeNumber = -1; private int nextAttributeNumber = -1; @Override public boolean hasNext() { if (nextAttributeNumber <= attributeNumber) { nextAttributeNumber = attributeNumber + 1; while (nextAttributeNumber < getEntityDef().getAttributeCount()) { AttributeDef attributeDef = getEntityDef().getAttribute(nextAttributeNumber); AttributeValue attrib = getInternalAttribute(attributeDef); if (!attrib.isNull(getTask(), attributeDef) || attrib.isUpdated()) break; nextAttributeNumber++; } } return nextAttributeNumber < getEntityDef().getAttributeCount(); } @Override public AttributeDef next() { attributeNumber = nextAttributeNumber; return getEntityDef().getAttribute(attributeNumber); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * Be careful using this. It does not call the derived operation so derived * attribute values may be stale. * * @param AttributeDef * @return */ AttributeValue getInternalAttribute(AttributeDef attributeDef) { AttributeDef va = validateMatchingEntities(attributeDef); Map<String, AttributeValue> attributes = getInstanceMap(va); if (!attributes.containsKey(va.getErAttributeToken())) attributes.put(va.getErAttributeToken(), new AttributeValue(va)); return attributes.get(va.getErAttributeToken()); } private Map<String, AttributeValue> getInstanceMap(AttributeDef attributeDef) { if (attributeDef.isPersistent()) return persistentAttributes; return workAttributes; } /** * Validate that the entity instance for attributeDef matches getEntityDef(). Check * to see if this is a recursive entity and if so return the * @param attributeDef * @return */ private AttributeDef validateMatchingEntities(AttributeDef attributeDef) { if (attributeDef.getEntityDef() == getEntityDef()) return attributeDef; // If attributeDef points to a recursive child, find the parent attribute. // TODO: Do we have to do this for the reciprocal situation as well? if (attributeDef.getEntityDef() == getEntityDef().getRecursiveParent()) { // Find the attribute in getEntityDef() that matches attributeDef. for (AttributeDef va : getEntityDef().getAttributes()) { if (va.getErAttributeToken() == attributeDef.getErAttributeToken()) return va; } } throw new ZeidonException("Mismatching entities. AttributeDefEntity: %s, EntityDef: %s", attributeDef.getEntityDef(), getEntityDef()); } @Override public int getDepth() { return depth; } /** * Returns the last child under the current entity instance. If there is no child * under 'this', then returns 'this'. * @return */ @Override public EntityInstanceImpl getLastChildHier() { if (getNextHier() == null) return this; if (getNextTwin() != null) { // If nextTwin.prevHier is null then we haven't set the prevHier pointer // for nextTwin yet. This is possible in the middle of a create entity. if (getNextTwin().getPrevHier() != null) return getNextTwin().getPrevHier(); } // If 'this' doesn't have a next hier or if the next hier has a depth that is // not below 'this' then return 'this' because it has no descendants. if (getNextHier() == null || getNextHier().getDepth() <= depth) return this; EntityInstanceImpl prev = null; EntityInstanceImpl search; for (search = this.getNextHier(); search != null && search.getDepth() > depth; prev = search, search = search.getNextHier()) { // We can short-circuit descendants by going to the last twin. search = search.getLastTwin(); } assert prev != null; // The logic should guarantee we get non-null 'prev'. return prev; } EntityInstanceImpl getLastTwin() { EntityInstanceImpl scanInst = this; while (scanInst.getNextTwin() != null) scanInst = scanInst.getNextTwin(); return scanInst; } EntityInstanceImpl getFirstTwin() { EntityInstanceImpl scanInst = this; while (scanInst.getPrevTwin() != null) scanInst = scanInst.getPrevTwin(); return scanInst; } /** * Finds the first child/descendant entity that matches entityDef. * @param entityDef * @return May return null. */ EntityInstanceImpl getFirstChildMatchingEntityDef(EntityDef searchEntityDef) { for (EntityInstanceImpl next = this.getNextHier(); next != null; next = next.getNextHier()) { if (next.getEntityDef() == searchEntityDef) return next; // We found what we're looking for. if (next.getDepth() <= this.getDepth()) // We've gone past the last hier so we're done. return null; // If we're looking at a descendant of 'e' then we can short-circuit // some of the entities by skipping to the last twin. if (next.getDepth() > searchEntityDef.getDepth()) next = next.getLastTwin().getLastChildHier(); } return null; } /** * Adds all child entities to 'this' EI to indicate they don't need to be lazy * loaded. Intended to be used by commit processing to indicate that an entity * that has been created doesn't need to have its children loaded. */ void flagAllChildrenAsLazyLoaded() { if (!getEntityDef().getLazyLoadConfig().hasLazyLoadChild()) return; // EI doesn't have any children who are lazy loaded. Set<EntityDef> set = getEntitiesLoadedLazily(); set.addAll(getEntityDef().getChildren()); // We'll just add all children regardless of whether they are lazyload. } boolean hasChildBeenLazyLoaded(EntityDef childEntityDef) { return getEntitiesLoadedLazily().contains(childEntityDef); } /** * This checks to see if the childEntityDef needs to be lazy loaded and, if so, loads it. * * @param view * @param childEntityDef */ void lazyLoadChild(ViewImpl view, EntityDef childEntityDef) { // Is lazyLoad enabled for this view? if (!view.isLazyLoad()) return; // Is the child entity instance loaded lazily? if (!childEntityDef.getLazyLoadConfig().isLazyLoad()) return; // Nope. // Has this entity been created? If so then nothing has been written to // the DB that can be lazy loaded. if (isCreated()) return; // Was this OI activated with the flag that indicates that entities marked // as lazy loaded should be loaded anyway? if (getObjectInstance().getActivateOptions() != null && getObjectInstance().getActivateOptions() .getActivateFlags().contains(ActivateFlags.fINCLUDE_LAZYLOAD)) { return; // Entities flagged as lazy load have already been loaded. } // Have we already loaded this child? if (hasChildBeenLazyLoaded(childEntityDef)) return; // Already loaded. Don't do it again. // Add the entity to the map now. This will prevent an infite loop when we try // to activate the EI from the DB. getEntitiesLoadedLazily().add(childEntityDef); view.dblog().debug("Lazy-loading %s for parent %s", childEntityDef.getName(), this.toString()); ActivateObjectInstance activator = new ActivateObjectInstance(view); activator.activate(view, childEntityDef); } private boolean assertNoTwin(EntityInstanceImpl parent, EntityInstanceImpl prevInstance) { if (parent != null && prevInstance == null) { // The calling code has indicated that there are no twins of the newly created // instance but we will verify because if there is then the chains will be thrown off. for (EntityInstanceImpl search : parent.getDirectChildren(false, false)) { if (search.getEntityDef() == getEntityDef()) return false; // We found a twin. } } return true; } private MaxCardinalityException checkMaxCardinality(boolean forAdd) { if (getParent() != null) { int twinCount = getTwinCount(); // If forAdd is true then we're making sure we can add a twin, so add // one to the count to account for the new one. if (forAdd) twinCount++; if (twinCount > entityDef.getMaxCardinality()) return new MaxCardinalityException(getEntityDef()); } return null; } /** * Validates that we can add one more twin to this entity instance. */ void validateMaxCardinality() { MaxCardinalityException e = checkMaxCardinality(true); if (e != null) throw e; } /** * Sets the prev/next/etc pointers of an instance to insert it into the OI chain. * * @param objectInstance - OI for this instance. * @param parent - Parent of the new instance. * @param prevInstance - New instance will be inserted before/after prevInstance, * depending on the value of position. * @param position - Indicates where the new instance will be inserted relative to * prevInstance. * @param lastHierChild Points to last child under 'this'. May be null in which case * this method will determine it. */ void insertInstance(ObjectInstance objectInstance, EntityInstanceImpl parent, EntityInstanceImpl prevInstance, CursorPosition position, EntityInstanceImpl lastChildHier) { assert assertNoTwin(parent, prevInstance) : "Internal error: calling code hasn't found a twin"; assert this.validateParentage(lastChildHier) : "Supplied lastChildHier is not a child of this"; if (objectInstance.getRootEntityInstance() == null) { assert this.getParent() == null : "Oops, first EI has a parent"; // This must be the first instance in the OI. objectInstance.setRootEntityInstance(this); return; } // prevInstance may be flagged as dropped, which means that we're inserting // 'this' using a cursor that hasn't been repositioned. We need to find an // instance that hasn't been dropped. if (prevInstance != null && prevInstance.isDropped()) { // Try finding a prev/next twin that hasn't been dropped. We have to pay // attention to the found instance to make sure we don't insert in the wrong // position. We don't care too much about FIRST or LAST because the logic later // on will take care of it. EntityInstanceImpl search; if (position == CursorPosition.PREV) { search = prevInstance.getNextTwin(); while (search != null && search.isDropped()) search = search.getNextTwin(); if (search == null) // Did we find one? { // No, so try finding one before prevInstance. search = prevInstance.getPrevTwin(); while (search != null && search.isDropped()) search = search.getPrevTwin(); // We found an instance *before* prevInstance. We need to change position // to be NEXT because we're going to change prevInstance to be an // instance before the current value. if (search != null) position = CursorPosition.NEXT; } } else { // FIRST, LAST, or NEXT search = prevInstance.getPrevTwin(); while (search != null && search.isDropped()) search = search.getPrevTwin(); if (search == null) // Did we find one? { // No, so try finding one after prevInstance. search = prevInstance.getNextTwin(); while (search != null && search.isDropped()) search = search.getNextTwin(); // We found an instance *after* prevInstance. If position is NEXT // then we need to change it to be PREV because we're going to change // prevInstance to be an instance after the current value. // We don't care if position is FIRST/LAST because that will be handled // later. if (search != null && position == CursorPosition.NEXT) position = CursorPosition.PREV; } } // Regardless of whether we found one we'll set prevInstance. This may // mean prevInstance gets set to null but that's OK; it just means that // the new instance has no twins. prevInstance = search; } // Before we set any twin pointers let's find the last child because once we set the // twin pointers we can no longer use getLastChildHier(). if (lastChildHier == null) lastChildHier = this.getLastChildHier(); // Set twin pointers. if (prevInstance != null) { switch (position) { case LAST: prevInstance = prevInstance.getLastTwin(); // Fall through... case NEXT: this.setPrevTwin(prevInstance); if (prevInstance.getNextTwin() != null) { this.setNextTwin(prevInstance.getNextTwin()); prevInstance.getNextTwin().setPrevTwin(this); } prevInstance.setNextTwin(this); break; case FIRST: prevInstance = prevInstance.getFirstTwin(); // Fall through... case PREV: this.setNextTwin(prevInstance); if (prevInstance.getPrevTwin() != null) { this.setPrevTwin(prevInstance.getPrevTwin()); prevInstance.getPrevTwin().setNextTwin(this); } else this.setPrevTwin(null); prevInstance.setPrevTwin(this); break; default: throw new ZeidonException("Internal error: we shouldn't have CursorPosition.NONE here"); } } EntityInstanceImpl nextTwin = this.getNextTwin(); EntityInstanceImpl prevTwin = this.getPrevTwin(); if (nextTwin == null && prevTwin == null && parent != null) { // We didn't find a twin. Find the last child under parent with a EntityDef // index < 'this' -- that will be the prev sibling. Find the first child // with index > 'this' to be the next sibling. for (EntityInstanceImpl child : parent.getDirectChildren(false, false)) { EntityDef ve = child.getEntityDef(); if (ve.getHierIndex() < this.getEntityDef().getHierIndex()) { prevTwin = child; } else if (ve.getHierIndex() > this.getEntityDef().getHierIndex()) { nextTwin = child; break; } } } // Now insert 'this' into the hier chain. There are three different ways we might have to do // this: // 1) We are inserting the new instance before an existing twin (nextTwin != null) // 2) We are inserting after an existing twin (prevTwin != null) // 3) There are no twins (both prevTwin and nextTwin are null). if (nextTwin != null) { // Simplest case is if new instance has a next twin. lastChildHier.setNextHier(nextTwin); // KJS - 02/04/2011 For some reason when I execute the following line from lTrnscpt_Object: // OrderEntityForView( lTrnscpt, "TranscriptGroup", "wCollegeYear A Semester A wLegacyTerm A wSortOrder A" ); // after executing the following line, if I try to go to the object browser everything is hung. I could have // sworn earlier that I got this issue when executing "this.getPrevHier().setNextHier( this );" so I changed it just to // see what would happen and now I get it on "this.setPrevHier( nextSibling.getPrevHier() );". I am confused, it doesn't // seem incorrect and I am wondering could it have anything to do with the fact that for TranscriptGroup the IDs are all NULL??? this.setPrevHier(nextTwin.getPrevHier()); if (this.getPrevHier() != null) { EntityInstanceImpl tempPrev = this.getPrevHier(); tempPrev.setNextHier(this); } nextTwin.setPrevHier(lastChildHier); if (nextTwin == getObjectInstance().getRootEntityInstance()) getObjectInstance().setRootEntityInstance(this); } else if (prevTwin != null) { // Since we know that nextTwin == null then we must be adding the new instance to the end // of the twins. EntityInstanceImpl lastHier = prevTwin.getLastChildHier(); EntityInstanceImpl nextHier = lastHier.getNextHier(); this.setPrevHier(lastHier); lastChildHier.setNextHier(nextHier); lastHier.setNextHier(this); if (nextHier != null) nextHier.setPrevHier(lastChildHier); } else { assert parent != null : "Internal error inserting instance: parent is null"; // There are no twins so we need to insert between the parent this.setPrevHier(parent); lastChildHier.setNextHier(parent.getNextHier()); parent.setNextHier(this); if (lastChildHier.getNextHier() != null) lastChildHier.getNextHier().setPrevHier(lastChildHier); } // Check to see if we need to set the root entity instance to this. if (this.getPrevHier() == null) { getObjectInstance().setRootEntityInstance(this); } } /** * Returns the next entity in heir order that is not a descendant of 'this'. * May return null. * * @return */ EntityInstanceImpl getNextNonDescendant() { // Quick check: a twin is always the next non descendant. if (this.getNextTwin() != null) return this.getNextTwin(); // Find the last child under 'this' and return its next hier. return this.getLastChildHier().getNextHier(); } @Override public CursorResult deleteEntity() { View view = null; if (entityDef.hasDeleteConstraint()) { view = getObjectInstance().createView(this); entityDef.executeEntityConstraint(view, EntityConstraintType.DELETE); } return deleteEntity(true, view); } /** * A simple wrapper around deleteEntity( boolean, view ). We use this to hide the use * of spawnRootDelete from users outside this class. * * @param view * @return */ CursorResult deleteEntity(View view) { if (entityDef.hasDeleteConstraint()) entityDef.executeEntityConstraint(view, EntityConstraintType.DELETE); return deleteEntity(true, view); } /** * Delete the entity. 'view' is passed to EventData listener. * * @param view * @return */ CursorResult deleteEntity(boolean spawnRootDelete, View view) { if (!getEntityDef().isDelete()) throw new ZeidonException("Entity is not flagged for delete.").prependEntityDef(getEntityDef()); if (isIncomplete() && getEntityDef().isPersistent()) throw new ZeidonException("This entity instance may not be deleted because it is incomplete. " + "One or more of its children were activated with qualification that limited results. " + "or a child entity was dropped. It might be possible to get around this error by " + "dropping %s instead of deleting it", getEntityDef().getName()).prependEntityInstance(this); // If checkRestrictedDelete is set, then make sure none of the child entities // have their parent-restrict delete flag set. if (getEntityDef().isCheckRestrictedDelete()) { for (EntityInstanceImpl child : this.getDirectChildren(false, false)) { if (child.getEntityDef().isRestrictParentDelete()) throw new ZeidonException("Can't delete %s because of restrict constraint on child entity %s", this, child); } } if (getEntityDef().getEventListener() != null) { EventDataImpl data = new EventDataImpl(getTask()).setEntityInstance(this).setView(view); getEntityDef().getEventListener().event(EventNotification.EntityDeleted, data); } // Keep track of child entities that are deleted. Later all we'll use them // to spawn the delete. ArrayList<EntityInstanceImpl> deletedEntities = new ArrayList<>(); // Run through the entity and all it's children and set the delete flag. // We start with 'this' because the logic below spawns the delete and // we want to spawn the deletes for all entities. int startLevel = getDepth(); EntityInstanceImpl scan = this; while (scan != null && (scan.getDepth() > startLevel || scan == this)) { // If the instance in question is already hidden, skip it // and all of its descendants since it may have been excluded or // moved BEFORE the Delete was issued. if (scan.isHidden()) { scan = scan.getNextNonDescendant(); continue; } if (!scan.getEntityDef().isParentDelete() && scan != this) { scan.excludeEntity(true); // This will also exclude linked instances. scan = scan.getNextNonDescendant(); // exclude() handled child entities so skip them. continue; } scan.setDeleted(true); // Spawn the delete for linked instances. We don't always want to spawn the // delete for instances linked to the root of the root ('this') because we // may be in a recursive call to delete. if (scan != this || spawnRootDelete) deletedEntities.add(scan); scan = scan.getNextHier(); } // Now go through and spwan the delete. We do it here instead of the main // loop because we want to process all the entities in the main OI first. EntitySpawner spawner = new EntitySpawner(this, view); for (EntityInstanceImpl ei : deletedEntities) spawner.spawnDelete(ei); dropIfDead(); return CursorResult.UNCHANGED; } private boolean isDead() { if (!isHidden()) return false; // Non-hidden instances are never dead. // Versioned instances are never dead because if we drop them it could screw // up previous versions if we ever need to cancel/accept. // DGC 2011-10-12 We allow versioned instances to be dropped. // DGC 2012-02-16 It seems we can't drop versioned instances because then if we later // do an accept then we don't know what's been deleted. The above comment // indicates that there was a need to drop versioned instances but I don't // remember why. if (isVersioned()) return false; if (!getEntityDef().isDerived() && !getEntityDef().isDerivedPath()) { // Entities that aren't flagged as created or included can't be dead. if (!isCreated() && !isIncluded() && (isDeleted() || isExcluded())) return false; } return true; } /** * Checks to see if an entity is "dead". A dead entity instance is an * instance that's been deleted but doesn't need to be saved to the database. * If a an entity is dead then it is removed from the OI entity instance chains. * @return true if instance is cleared. */ private boolean dropIfDead() { if (!isDead()) return false; // Entity instance is dead. Drop it from the chain. getTask().log().trace("Cleaning up dead entity %s for %s", this, entityDef); dropEntity(); return true; } boolean temporalVersionMatch(EntityInstanceImpl ei) { // If neither are versioned then they match. if (!this.isVersioned() && !ei.isVersioned()) return true; // At least one is versioned. They only match if they their version // numbers are the same. return this.versionNumber == ei.versionNumber; } void excludeEntity(boolean spawnExclude) { setExcluded(true); if (spawnExclude) { EntitySpawner spawner = new EntitySpawner(this); spawner.spawnExclude(); } hideChildren(); // Drop the instance if this is a dead entity. dropIfDead(); } @Override public CursorResult excludeEntity() { return excludeEntity(null); } CursorResult excludeEntity(View view) { if (entityDef.hasExcludeConstraint()) { if (view == null) view = getObjectInstance().createView(this); entityDef.executeEntityConstraint(view, EntityConstraintType.EXCLUDE); } // If there is no parent then excluding the entity is the same as dropping it. if (getParent() == null) { // For consistency's sake we won't allow excluding a root entity that is also versioned. if (isVersioned()) throw new ZeidonException("Invalid operation: cannot excluded a root entity that is versioned"); // Excluding a root entity is the same as dropping it. dropEntity(); return CursorResult.UNCHANGED; } // Validate the exclude. if (!getEntityDef().isExclude()) throw new ZeidonException("Entity is not flagged for exclude.").prependEntityDef(getEntityDef()); excludeEntity(true); return CursorResult.UNCHANGED; } private void hideChildren() { for (EntityInstanceImpl child : getChildrenHier()) child.setHidden(true); } void copyFlags(EntityInstanceImpl source) { setHidden(source.hidden); setDeleted(source.deleted); setCreated(source.created); setIncluded(source.included); setExcluded(source.excluded); setUpdated(source.updated); } @Override public boolean linkInstances(EntityInstance source) { assert source != null; if (source == this) return false; if (entityDef.getErEntityToken() != source.getEntityDef().getErEntityToken()) throw new ZeidonException("Unmatched ER tokens").appendMessage("Target = %s\nSource = %s", entityDef, source.getEntityDef()); EntityInstanceImpl s = (EntityInstanceImpl) source.getEntityInstance(); // If key1 is null then we are probably in the middle of creating the entity instance // and we already know we can link them, so skip the key check. // This happens, for example, when loading entities from the DB. String key1 = this.getKeyString(); if (key1 != null) { String key2 = s.getKeyString(); if (!StringUtils.equals(key2, key1)) throw new ZeidonException("Attempting to relink instances with different keys.") .appendMessage("Entity1 = %s.%s", getLodDef().getName(), getEntityDef().getName()) .appendMessage("Key1 = %s", key2) .appendMessage("Entity2 = %s.%s", s.getLodDef().getName(), s.getEntityDef().getName()) .appendMessage("Key2 = %s", key1); } if (isLinked(s)) return false; linkInternalInstances(s); return true; } /** * Validate that the source and target entities are OK to link. The error we're checking * for is that the source entity doesn't define all the attributes that the target has. If * that happens the attributes in target will be lost when it is linked to source. * * If the entities have the same ER date then we'll assume all is good because they were * built with the same ER and both entities should have all the required attributes. * * Some day we may handle checking the reverse (linking source to target). * * @param target * @param source * @return */ /** * Link this entity instance with 'source'. * * @param source */ void linkInternalInstances(EntityInstanceImpl sourceInstance) { assert sourceInstance != null; assert sourceInstance != this; assert entityDef.getErEntityToken() == sourceInstance.getEntityDef().getErEntityToken(); LinkValidation valid = getEntityDef().validateLinking(sourceInstance.getEntityDef()); if (valid != LinkValidation.SOURCE_OK) { // We should never get here because validateLinking() should throw an exception if // it doesn't return SOURCE_OK but just in case... throw new ZeidonException("Internal error. validateLinking returned something invalid."); } assert getEntityDef().getErEntityToken() == sourceInstance.getEntityDef() .getErEntityToken() : "Attempting to link view entities that are not the same ER entity"; sourceInstance.addLinkedInstance(this); } /** * Link newInstance with 'this'. Will create this.linkedInstances if necessary. * @param newInstance */ private void addLinkedInstance(EntityInstanceImpl newInstance) { synchronized (getTask()) { if (linkedInstances == null) { if (newInstance.linkedInstances != null) { // Make all the linked entities share the new source attributes. for (EntityInstanceImpl ei : newInstance.linkedInstances.keySet()) ei.persistentAttributes = persistentAttributes; linkedInstances = newInstance.linkedInstances; linkedInstances.putIfAbsent(this, Boolean.TRUE); return; } // Create a concurrent map that uses weak references for the keys. // This will allow the GC to clean up if a linked instance goes away. // Initialize it with firstInstance. linkedInstances = new MapMaker().concurrencyLevel(2).weakKeys().makeMap(); linkedInstances.put(this, Boolean.TRUE); } // Check to see if targetInstance is already linked and if it is remove // it. This can happen when merging an OI committed on a web server. if (newInstance.linkedInstances != null) newInstance.linkedInstances.remove(newInstance); linkedInstances.putIfAbsent(newInstance, Boolean.TRUE); newInstance.linkedInstances = linkedInstances; newInstance.persistentAttributes = persistentAttributes; assert assertLinkedInstances() : "Error with linked instances"; } } TaskImpl getTask() { return objectInstance.getTask(); } @Override public boolean isUpdated() { return updated; } @Override public boolean isDeleted() { return deleted; } @Override public boolean isCreated() { return created; } @Override public boolean isIncluded() { return included; } @Override public boolean isHidden() { return hidden; } @Override public boolean isExcluded() { return excluded; } boolean isDropped() { return dropped; } void setVersionedEntity() { setVersionStatus(VersionStatus.UNACCEPTED_ENTITY); } private void setVersionStatus(VersionStatus newStatus) { versionStatus = newStatus; } @Override public boolean isVersioned() { return versionStatus == VersionStatus.UNACCEPTED || versionStatus == VersionStatus.UNACCEPTED_ROOT || versionStatus == VersionStatus.UNACCEPTED_ENTITY; } /** * Returns true if any of the incremental update flags are true. */ private boolean isChanged() { if (isUpdated() || isCreated() || isDeleted() || isIncluded() || isExcluded()) return true; return false; } /** * Creates a temporal subobject with 'this' as the parent. A copy of every entity instance * is made and each new version has its own attributeList. The new versions point back * to the old ones and the old ones point to the new ones. * * It is expected that all subsequent cursor operations will first find the most recent * version before any action is taken. */ @Override public EntityInstanceImpl createTemporalSubobjectVersion() { for (EntityInstanceImpl ei : getChildrenHier(true)) { if (ei.versionStatus == VersionStatus.UNACCEPTED_ROOT) throw new TemporalEntityException(this, "Attempting to create a temporal subobject for an entity that " + "has an unaccepted temporal root as a child.").appendMessage("Temporal root: %s", ei.toString()); // See if any linked instances are already versioned. for (EntityInstanceImpl linked : ei.getAllLinkedInstances()) { // It's linked is a child of 'this' then it's ok. if (linked.hasAncestorOf(this)) continue; if (linked.isVersioned()) throw new TemporalEntityException(this, "Attempting to create a temporal subobject for an entity that " + "has a child entity linked to another temporal entity.") .appendMessage("Temporal root: %s", ei.toString()) .appendMessage("Linked instance: %s", linked.toString()); } } TemporalVersionCreator creator = new TemporalVersionCreator(getTask(), this); return creator.createTemporalVersion(); } void setIncrementalFlags(long flags) { if ((flags & FLAG_HANGING_ENTITY) != 0) throw new ZeidonException("Hanging Entity flags not currently supported"); setCreated((flags & FLAG_CREATED) != 0); setUpdated((flags & FLAG_UPDATED) != 0); setDeleted((flags & FLAG_DELETED) != 0); setExcluded((flags & FLAG_EXCLUDED) != 0); setIncluded((flags & FLAG_INCLUDED) != 0); setHidden((flags & FLAG_HIDDEN) != 0); /* We'll ignore some flags but we'll leave this code in for now to indicate that * we're ignoring it on purpose. if ( ( flags & FLAG_RECORD_OWNER ) != 0 ) ; // Ignore record owner if ( ( flags & FLAG_RELOWNER ) != 0 ) ; // Ignore rel owner if ( ( flags & FLAG_PREV_VERSION ) != 0 ) ; // Ignore prev version if ( ( flags & FLAG_PREV_VERSIONRT ) != 0 ) ; // Ignore prev version root */ if ((flags & (FLAG_CREATED | FLAG_UPDATED | FLAG_DELETED | FLAG_EXCLUDED | FLAG_HIDDEN)) != 0) getObjectInstance().setUpdated(true); } void setUpdated(boolean isUpdated) { setUpdated(isUpdated, true, true); } /** * Set the updated flag for this entity instance and the parent OI. * * @param isUpdated - New value of the EI update flag. * @param setLinked - If true, set update flag for all linked instances. * @param setPersistent - If true, set EI fl */ void setUpdated(boolean isUpdated, boolean setLinked, boolean setPersistent) { if (this.updated == isUpdated) return; if (getEntityDef().isDebugIncremental()) JoeUtils.sysMessageBox("Debug Incremental", "Changing update flag for " + toString()); // The isUpdated flag is only set for persistent attributes. if (setPersistent) this.updated = isUpdated; // We don't replicate the updated flag if it's being turned off. if (isUpdated) { if (!isVersioned()) { if (setPersistent) getObjectInstance().setUpdated(true); // Also sets updatedFile else getObjectInstance().setUpdatedFile(true); if (setLinked && setPersistent) { for (EntityInstanceImpl linked : getLinkedInstances()) linked.setUpdated(true, false, setPersistent); } } } } private void setObjectInstanceUpdated() { ObjectInstance oi = getObjectInstance(); if (!isVersioned()) // Don't update the OI yet if this entity is versioned. oi.setUpdated(true); } void setDeleted(boolean isDeleted) { this.deleted = isDeleted; if (isDeleted) { setObjectInstanceUpdated(); setHidden(true); } } void setCreated(boolean isCreated) { this.created = isCreated; if (isCreated) setObjectInstanceUpdated(); } void setIncluded(boolean isIncluded) { this.included = isIncluded; if (isIncluded) setObjectInstanceUpdated(); } void setHidden(boolean isHidden) { this.hidden = isHidden; if (isHidden) removeAllHashKeyAttributes(); } void setExcluded(boolean isExcluded) { this.excluded = isExcluded; if (isExcluded) { setObjectInstanceUpdated(); setHidden(true); } } private void acceptSubobjectEntity(final VersionStatus newStatus) { assert isVersioned() : "Unexpected version status: " + versionStatus; setVersionStatus(newStatus); // Check for a null pointer because it's possible this entity was // created and therefore won't have a prevVersion. if (prevVersion != null) { Collection<EntityInstanceImpl> linkedInstances = prevVersion.getLinkedInstances(); if (linkedInstances.size() > 0) // If size = 0 then 'this' is only linked with itself. { // Update the persistent attributes for each of the linked instances. for (EntityInstanceImpl linked : linkedInstances) { // We only care about instances that *don't* have a next version. If they // have a next version then they better be part of the current temporal // subobject that we're accepting, which means we've handled it already. if (linked.nextVersion != null) { assert linked.versionNumber == prevVersion.versionNumber : "Version numbers don't match"; continue; } // Update the attribute values of the linked instance to point to the new // attribute list. linked.removeAllHashKeyAttributes(); // If linked has attr hashkeys, remove them. linked.persistentAttributes = persistentAttributes; linked.addAllHashKeyAttributes(); // TODO: We could limit this to only EIs that have been updated. // The spawn logic should have correctly set most of the flags. The only one we have to // copy is the update flag. if (this.isUpdated()) linked.setUpdated(true); assert this.isDeleted() == linked.isDeleted() : "acceptSubobject flag logic is wrong"; assert this.isExcluded() == linked.isExcluded() : "acceptSubobject flag logic is wrong"; assert this.isUpdated() == linked.isUpdated() : "acceptSubobject flag logic is wrong"; assert this.isIncluded() == linked.isIncluded() : "acceptSubobject flag logic is wrong"; // Update the version number so that the linked instance has the same value. linked.versionNumber = this.versionNumber; if (linked.isChanged()) linked.getObjectInstance().setUpdated(true); } prevVersion.mergeLinkedInstances(this); } // DON'T null out prevVersion.nextVersion. That will allow any cursors pointing to the // superseded version to find the new one. The GC will eventually clean up. // Setting the dropped flag also tells the linked logic to ignore this entity. prevVersion.dropped = true; prevVersion.setVersionStatus(VersionStatus.SUPERSEDED); // If the previous version also has a previous version then we have multiple levels // of versioning. if (prevVersion.prevVersion != null) { EntityInstanceImpl grandfatherVersion = prevVersion.prevVersion; grandfatherVersion.setNextVersion(this); this.prevVersion = grandfatherVersion; } else prevVersion = null; } // If this entity has been changed then set the flag for the OI. if (isChanged()) getObjectInstance().setUpdated(true); assert assertLinkedInstances() : "Error with linked instances"; } /** * Add the linked instances from source to 'this'. Intended to be used by * Accept logic for merging linked instances from a new, accepted, entity. * * @param source */ private void mergeLinkedInstances(EntityInstanceImpl source) { // If source doesn't have any linkedInstances than there's nothing to do. if (source.linkedInstances == null) return; for (EntityInstanceImpl linked : source.linkedInstances.keySet()) addLinkedInstance(linked); assert assertLinkedInstances() : "Error with linked instances"; } void validateSubobject(View view, Collection<ZeidonException> list) { assert isHidden() == false : "Attempting to validate a hidden instance."; if (getEntityDef().hasAcceptConstraint()) { try { getEntityDef().executeEntityConstraint(view, EntityConstraintType.ACCEPT); } catch (Exception e) { list.add(ZeidonException.wrapException(e)); return; } } // // Make sure that all the required attributes have non-null values // if this entity has been changed in any way. // if (isCreated() || isUpdated() || isIncluded()) { for (AttributeDef attributeDef : getEntityDef().getAttributes()) { // Ignore hidden attributes. if (attributeDef.isHidden()) continue; // Genkeys will have their value created so ignore it. if (attributeDef.isGenKey()) continue; if (!attributeDef.isRequired()) continue; if (!getAttribute(attributeDef).isNull()) continue; list.add(new RequiredAttributeException(attributeDef)); } } // // Validate max cardinality with the parent. // MaxCardinalityException e = checkMaxCardinality(false); if (e != null) list.add(e); // // Make sure there is at least one instance of all required child entities. // for (EntityDef childEntity : getEntityDef().getChildren()) { if (childEntity.getMinCardinality() == 0) continue; // Child entities aren't required so ignore this one. // If the child is being lazy-loaded and 'this' EI hasn't been created // then we'll assume the child just hasn't been loaded and we're good. if (childEntity.getLazyLoadConfig().isLazyLoad()) { if (!isCreated() && !isIncluded()) continue; } // Make sure there is at least one child instance that matches this. int hidden = 0; int nonhidden = 0; for (EntityInstance ei : getChildren(childEntity, true)) { if (ei.isHidden()) hidden++; else nonhidden++; } if (isIncomplete() && hidden == 0) { // The parent of childEntity is incomplete which means not all of it's children // were loaded. We only need to validate min cardinality if the user // deleted/excluded and of the children. If we get here then they // didn't so we're good. continue; } if (childEntity.getMinCardinality() > nonhidden) list.add(new RequiredEntityMissingException(childEntity)); } // Now run this on all direct children. for (EntityInstanceImpl childInstance : getDirectChildren(false, false)) childInstance.validateSubobject(view, list); } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#validateSubobject() */ Collection<ZeidonException> validateSubobject(View view) { Collection<ZeidonException> list = new ArrayList<ZeidonException>(); validateSubobject(view, list); if (list.size() == 0) return null; return list; } /** * Calls validateSubobject() and throws SubobjectValidationException if any * errors were found. * * @throws SubobjectValidationException */ void validateSubobjectThrowException(View view) { Collection<ZeidonException> list = validateSubobject(view); if (list == null || list.size() == 0) return; throw new SubobjectValidationException(list); } @Override public EntityInstanceImpl acceptSubobject() { return acceptSubobject(getObjectInstance().createView(this)); } EntityInstanceImpl acceptSubobject(View view) { // If the entity is a temporal entity created via createTemporal we'll // accept it. if (versionStatus == VersionStatus.UNACCEPTED_ENTITY) { acceptTemporalEntity(view); return this; } // Before we change any version pointers let's validate the entity and attribute values. validateSubobjectThrowException(view); // If this EI isn't versioned then we're done. if (versionStatus == VersionStatus.NONE) return this; if (versionStatus != VersionStatus.UNACCEPTED_ROOT) throw new TemporalEntityException(this, "Entity is not a root of a temporal subobject root"); assert prevVersion != null : "Unaccepted root has null prevVersion"; // Make sure none of the child EIs are an unaccepted root. for (final EntityInstanceImpl ei : getChildrenHier(false, false, false)) // Loop through all children, including excluded. { if (ei.versionStatus == VersionStatus.UNACCEPTED_ROOT) throw new TemporalEntityException(this, "Entity has children that are unaccepted version roots"); } // Get the versionStatus of the previous version. We'll set the status of all children to be // this status. We need to do this in case there are multiple layers of versioned subobjects. final VersionStatus newStatus = prevVersion.versionStatus; assert newStatus == VersionStatus.UNACCEPTED || newStatus == VersionStatus.NONE : "Internal error: unsupported double versioning " + newStatus; // Spawn the changes to linked instances. EntitySpawner spawner = new EntitySpawner(this); spawner.spawnAccept(); for (final EntityInstanceImpl ei : getChildrenHier(true, false)) // Loop through all, including excluded. ei.acceptSubobjectEntity(newStatus); // Set next/prev pointers the in non-versioned instances surrounding 'this'. if (getPrevHier() == null) getObjectInstance().setRootEntityInstance(this); else getPrevHier().setNextHier(this); if (getPrevTwin() != null) getPrevTwin().setNextTwin(this); if (getNextTwin() != null) getNextTwin().setPrevTwin(this); // this.getNextHier() still points to the old, superseded entity. final EntityInstanceImpl lastHier = this.getLastChildHier(); if (lastHier.getNextHier() != null) lastHier.getNextHier().setPrevHier(lastHier); // Now go through and drop any dead instances. We do it here after all the // chain pointers have been changed. EntityInstanceImpl search = getNextHier(); while (search != null && search.getDepth() > this.getDepth()) { if (search.isDead()) { // Get the last hier now before we drop it. This way we can skip over // dropping all the child entities. EntityInstanceImpl t = search.getLastChildHier().getNextHier(); search.dropEntity(); search = t; } else search = search.getNextHier(); } getObjectInstance().decrementVersionedCount(); return this; } /** * Cancels the temporal entity. * * Note that we don't change this.prevVersion because it is used by cursor logic to * find the correct version. */ private void cancelSubobjectEntity() { assert isVersioned() : "Unexpected version status"; setVersionStatus(VersionStatus.CANCELED); // prevVersion should only be null if the current entity was created/inserted. if (prevVersion != null) { prevVersion.setNextVersion(null); prevVersion.addAllHashKeyAttributes(); } else assert isCreated() || isIncluded(); dropped = true; } @Override public EntityInstanceImpl cancelSubobject() { return cancelSubobject(null); } /** * Cancels the temporal subobject. The view that is passed in is used to call * the entity constraint (if it exists). If view is null then a temporary view * will be created. * * @return */ EntityInstanceImpl cancelSubobject(ViewImpl view) { // If the entity is a temporal entity created via createTemporal we'll cancel it. if (versionStatus == VersionStatus.UNACCEPTED_ENTITY) { cancelTemporalEntity(); return this.prevVersion; } if (versionStatus != VersionStatus.UNACCEPTED_ROOT) throw new TemporalEntityException(this, "Entity is not the root of a temporal subobject"); if (entityDef.hasCancelConstraint()) { if (view == null) view = getObjectInstance().createView(this); entityDef.executeEntityConstraint(view, EntityConstraintType.CANCEL); } // All we need to do is go through all of the entities in the subobject and set their // pointers to null. Everything else will be taken care of by normal processing. // The getChildrenHier uses an iterator that needs the nextHier pointers so we'll // create a temporary list before nulling them out. List<EntityInstanceImpl> list = new ArrayList<EntityInstanceImpl>(); for (EntityInstanceImpl ei : this.getChildrenHier(true, false, false)) list.add(ei); // Now we can set the pointers to null without a problem. for (EntityInstanceImpl ei : list) ei.cancelSubobjectEntity(); getObjectInstance().decrementVersionedCount(); return this.prevVersion; } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#acceptTemporalEntity() */ @Override public void acceptTemporalEntity() { acceptTemporalEntity(getObjectInstance().createView(this)); } void acceptTemporalEntity(View view) { if (versionStatus != VersionStatus.UNACCEPTED_ENTITY) throw new TemporalEntityException(this, "Entity is not the root of a temporal entity"); // Before we change any version pointers let's validate the entity and attribute values. validateSubobjectThrowException(view); for (EntityInstanceImpl ei : getChildrenHier(true, false, false)) ei.setVersionStatus(VersionStatus.NONE); EntitySpawner spawner = new EntitySpawner(this); spawner.spawnCreate(); getObjectInstance().setUpdated(true); } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#cancelTemporalEntity() */ @Override public CursorResult cancelTemporalEntity() { if (versionStatus != VersionStatus.UNACCEPTED_ENTITY) throw new TemporalEntityException(this, "Entity is not the root of a temporal entity"); setVersionStatus(VersionStatus.CANCELED); // The only thing we have to do is drop the entity. dropEntity(); return CursorResult.UNCHANGED; } /** * Removes the entity and its children from the OI chain. */ @Override public CursorResult dropEntity() { // DGC 2011.10.12 - We're allowing versioned instances to be dropped. Shouldn't be a problem. // DGC 2012.02.16 - See comment in dropIfDead(). if (isVersioned()) throw new ZeidonException("Invalid operation: can't drop a temporal entity."); dropped = true; // If we're dropping this entity without first having deleted/excluded it then // this OI is potentially incomplete. Set the flag. if (getParent() != null && !isHidden() && !isCreated()) getParent().setIncomplete(getEntityDef()); // Following comments may no longer be apropos now that we don't allow dropping // versioned objects. 2011-04-12. // Do we have to figure out what to do with instances that have other versions still // pointing to it? If we delete them then any cursor still pointing to it won't // know what to do. Should we just throw an error if a cursor is pointing to an // old version of a dropped entity? Is this even a problem? // Set the hidden flag for this entity. That way any cursors that still point // to this entity will throw a null entity exception. if (!isHidden()) setHidden(true); EntityInstanceImpl lastChild = removeEntityFromChains(); // We reset the next hier pointer so when we're looping through the EIs in hier // order we skip past the ones we just removed. setNextHier(lastChild.getNextHier()); // We don't have to remove the entity from the linkedInstance map because they // are weak references. return CursorResult.UNCHANGED; } /** * Sets the next/prev pointers to remove 'this' entity from the OI. * * Returns the last hier child. */ EntityInstanceImpl removeEntityFromChains() { EntityInstanceImpl lastChild = getLastChildHier(); if (getPrevHier() != null) getPrevHier().setNextHier(lastChild.getNextHier()); else getObjectInstance().setRootEntityInstance(getNextTwin()); //if ( getNextHier() != null ) // getNextHier().setPrevHier( getPrevHier() ); if (lastChild.getNextHier() != null) lastChild.getNextHier().setPrevHier(getPrevHier()); if (getPrevTwin() != null) getPrevTwin().setNextTwin(getNextTwin()); if (getNextTwin() != null) getNextTwin().setPrevTwin(getPrevTwin()); return lastChild; } /** * Sets all the prev/next pointers to null so that this entity doesn't refer to * any other EIs. This is intended to be used in commit processing when an EI is * being superseded. This also sets the 'dropped' flag to indicate that it should * not be considered in linked processing. * * NOTE: This does NOT reset the pointers of twin instances. Use removeEntityFromChains() * for that. */ void setEiPointersToNull() { parentInstance = null; prevHierInstance = null; prevTwinInstance = null; nextHierInstance = null; nextTwinInstance = null; dropped = true; // Don't set nextVersion! } String getTag() { return tag; } void setTag(String tag) { this.tag = tag; } @Override public long getEntityKey() { return entityKey; } @Override public synchronized UUID getEntityUuid() { if (uuid == null) uuid = getTask().getObjectEngine().generateUuid(); return uuid; } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#getObjectInstanceId() */ @Override public UUID getObjectInstanceUuid() { return getObjectInstance().getUuid(); } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#getObjectInstanceId() */ @Override public long getObjectInstanceId() { return getObjectInstance().getId(); } /** * Returns the linked instances. Note this included 'this'. * * @return */ Collection<EntityInstanceImpl> getAllLinkedInstances() { return getLinkedInstances(false, false); } /** * Returns an iterable list of entities linked with 'this'. If there * are no linked entities it will return an empty list. * Does NOT include 'this'. * * @return */ @Override public Collection<EntityInstanceImpl> getLinkedInstances() { return getLinkedInstances(false, true); } /** * Get the list of linked instances, including those that have been dropped * but still unclaimed by the GC. Includes 'this'. * * @param includeDropped * @return */ Collection<EntityInstanceImpl> getAllLinkedInstances(boolean includeDropped) { return getLinkedInstances(includeDropped, false); } /** * Validate that all the linked instances have correct data. */ private boolean assertLinkedInstances() { if (linkedInstances == null) return true; for (EntityInstanceImpl ei : linkedInstances.keySet()) { if (ei == null) continue; assert ei.isLinked(this) : "Linked EIs have different persistentAttributes"; } return true; } /** * Returns the linked instances if there are any. If 'excludeSource' is true * then 'source' will be ignored when building the return list. This allows * the caller to get a list of all other linked instances. * * Note that entities that have been dropped but not reclaimed by the GC * will still show up in this list. We can skip most of those by ignoring * ones flagged as dropped. * * @param source * Ignore this EI when creating the list. * * @return List of linked instances. If there are none, an empty list is * returned. */ private Collection<EntityInstanceImpl> getLinkedInstances(boolean includeDropped, boolean excludeSource) { // If linkedInstances == null then this EI has never been linked to // anything. if (linkedInstances == null) { if (excludeSource) return Collections.emptyList(); List<EntityInstanceImpl> list = new ArrayList<EntityInstanceImpl>(); list.add(this); return list; } // We'll use a hash set instead of a list because some callers will use // .contains(...) // and a hash set is faster. HashSet<EntityInstanceImpl> list = new HashSet<EntityInstanceImpl>(linkedInstances.size()); for (EntityInstanceImpl ei : linkedInstances.keySet()) { if (ei == null) continue; if (ei.isDropped() && !includeDropped) continue; if (excludeSource && ei == this) continue; list.add(ei); } assert list.size() > 0 : "getLinkedInstances returned empty list"; return list; } /** * Used by commit/activate processing to store each entity's index * from the root. This is different from getHierPosition() in that it * includes hidden entities. * * @return */ long getHierIndex() { return hierIndex; } void setHierIndex(long hierIndex) { this.hierIndex = hierIndex; } boolean isWritten() { return isWritten; } void setWritten(boolean isWritten) { this.isWritten = isWritten; } boolean isRecordOwner() { return isRecordOwner; } void setRecordOwner(boolean isRecordOwner) { this.isRecordOwner = isRecordOwner; } /** * Determines if 'this' has an ancestor of 'ancestor'. Will return true if * 'this' == ancestor * * @param ancestor * @return */ private boolean hasAncestorOf(EntityInstanceImpl ancestor) { for (EntityInstanceImpl parent = this; parent != null; parent = parent.getParent()) { if (parent == ancestor) return true; } return false; } /** * Find the parent entity instance of 'this' that has parentEntityDef as its * entity definition. Must work for recursive subobjects so the logic is a bit * more complicated than just doing a simple comparison. * * Used in commit processing to set foreign keys. * * @param parentEntityDef * @return */ EntityInstanceImpl findMatchingParent(EntityDef parentEntityDef) { EntityInstanceImpl searchInstance = getParent(); while (searchInstance != null && searchInstance.getEntityDef() != parentEntityDef) { EntityDef ed = searchInstance.getEntityDef(); // If the parent entity we are looking for is a recursive parent, // then it's possible that the entity instance we are looking for // has an EntityDef that is the recursive child entity. if (parentEntityDef.isRecursiveParent() && parentEntityDef.getRecursiveChild() == ed) break; searchInstance = searchInstance.getParent(); } return searchInstance; } /** * This is used to find an entity instance in the OI by searching for the matching * hier index. Assumes: * - Hier indexes have been set properly. * * @param index * @return */ /* EntityInstanceImpl findByHierIndex( long index ) { List<Long> lst = new ArrayList<Long>(); EntityInstanceImpl search = this; while ( search != null ) { while ( search.getNextTwin() != null && search.getNextTwin().getHierIndex() <= index ) search = search.getNextTwin(); if ( search.getHierIndex() == index ) return search; if ( search.getHierIndex() > index ) { EntityInstanceImpl returnInst = null; search = search.getParent(); if ( lst.indexOf( search.getHierIndex() ) >= 0 ) { // we have a problem (infinite loop in process) ... get to the top and display the hierarchy while ( search.getParent() != null ) { search = search.getParent(); } while ( search != null ) { EntityInstanceImpl parent = search.getParent(); getTask().log().info( "Entity Index: %d Parent Index: %d", search.getHierIndex(), parent == null ? -1 : parent.getHierIndex() ); if ( search.getHierIndex() == index ) { returnInst = search; } search = search.getNextHier(); } return returnInst; } else { lst.add( search.getHierIndex() ); } continue; } search = search.getNextHier(); } return search; } */ EntityInstanceImpl findByHierIndex(long index) { EntityInstanceImpl search = this; while (search != null) { EntityInstanceImpl next = search.getNextTwin(); while (next != null && next.getHierIndex() <= index) { search = next; next = search.getNextTwin(); } if (search.getHierIndex() == index) return search; if (next != null && next.getHierIndex() > index) { while ((search = search.getNextHier()) != null) { if (search.getHierIndex() == index) { return search; } } } else { search = search.getNextHier(); } } // Try brute force. getTask().log().info("Searching for Entity Index: %d by brute force", index); for (search = this; search != null; search = search.getNextHier()) { if (search.getHierIndex() == index) return search; } return null; } void copyAttributes(EntityInstanceImpl sourceInstance, boolean copyPersist, boolean copyUpdateFlags) { for (AttributeDef attributeDef : sourceInstance.getNonNullAttributeList()) { if (attributeDef.isPersistent() && !copyPersist) continue; AttributeValue source = sourceInstance.getInternalAttribute(attributeDef); AttributeValue target = getInternalAttribute(attributeDef); Object internalValue = source.getInternalValue(); target.setInternalValue(getTask(), attributeDef, internalValue, true); addHashKeyAttributeToMap(attributeDef); if (copyUpdateFlags) target.copyUpdateFlags(source); else target.setUpdated(true); } } /** * Loops through all the direct EI children of 'this'. * * @param allowHidden * @return */ @Override public EntityIterator<EntityInstanceImpl> getDirectChildren() { return getDirectChildren(false, false); } /** * Loops through all the direct EI children of 'this'. * * @param allowHidden * @return */ @Override public EntityIterator<EntityInstanceImpl> getDirectChildren(boolean allowHidden) { return getDirectChildren(allowHidden, true); } @Override public EntityIterator<EntityInstanceImpl> getDirectChildren(boolean allowHidden, boolean allowLazyLoad) { return new IteratorBuilder(getObjectInstance()).forDirectChildren(this).setLazyLoad(allowLazyLoad).build(); } @Override public EntityIterator<? extends EntityInstance> getChildren(EntityDef childEntityDef) { return new IteratorBuilder(getObjectInstance()).withScoping(this).forEntityDef(childEntityDef).build(); } @Override public EntityIterator<? extends EntityInstance> getChildren(EntityDef childEntityDef, boolean allowHidden) { return new IteratorBuilder(getObjectInstance()).allowHidden(allowHidden).withScoping(this) .forEntityDef(childEntityDef).build(); } private void addAllLinks(HashSet<EntityInstanceImpl> allEntities) { allEntities.add(this); allEntities.add(parentInstance); allEntities.add(nextTwinInstance); allEntities.add(nextHierInstance); allEntities.add(prevHierInstance); allEntities.add(prevTwinInstance); allEntities.add(prevVersion); allEntities.add(nextVersion); } void countAllVersions(HashSet<EntityInstanceImpl> allEntities) { for (EntityInstanceImpl ei = this; ei != null; ei = ei.prevVersion) ei.addAllLinks(allEntities); for (EntityInstanceImpl ei = this.nextVersion; ei != null; ei = ei.nextVersion) ei.addAllLinks(allEntities); } /** * Iterates through all the hier children of this entity instance. * * @return */ EntityIterator<EntityInstanceImpl> getChildrenHier() { return getChildrenHier(false); } /** * Loop through the children. Will not load lazy entities. * * @param includeParent * @param excludeHidden * @return */ EntityIterator<EntityInstanceImpl> getChildrenHier(boolean includeParent, boolean excludeHidden) { return getChildrenHier(includeParent, excludeHidden, false); } /** * Loop through the children. * * @param includeParent * @param excludeHidden * @return */ @Override public EntityIterator<EntityInstanceImpl> getChildrenHier(boolean includeParent, boolean excludeHidden, boolean loadLazyEntities) { EntityIterator<EntityInstanceImpl> iterable = new IteratorBuilder(getObjectInstance()).withScoping(this) .allowHidden(!excludeHidden).setLazyLoad(loadLazyEntities).build(); if (!includeParent) iterable.next(); return iterable; } /** * Iterates through all the children of 'this' in heir order. If includeParent * is true, then the iteration includes 'this' at the beginning. This will force * lazy load. * * @param includeParent If true, include 'this'. * * @return */ @Override public EntityIterator<EntityInstanceImpl> getChildrenHier(boolean includeParent) { return getChildrenHier(includeParent, true, true); } /** * Returns a string representation of the key values of this entity. If all keys are * null then returns null. * * @return */ @Override public String getKeyString() { KeyStringBuilder builder = new KeyStringBuilder(); for (AttributeDef key : getEntityDef().getKeys()) builder.appendKey(getAttribute(key)); if (builder.isNull()) return null; return builder.toString(); } /** * Returns the value of all the keys. */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(getEntityDef().toString()).append(" Keys: "); for (AttributeDef key : getEntityDef().getKeys()) { builder.append(key.getName()).append("="); builder.append(getInternalAttribute(key).toString()).append("; "); } return builder.toString(); } @Override public EntityIterator<? extends EntityInstance> getChildren(String childEntityName) { EntityDef childEntityDef = getLodDef().getEntityDef(childEntityName); if (childEntityDef.getParent() != getEntityDef()) throw new ZeidonException("%s is not a direct child of %s", childEntityName, getEntityDef()); return getChildren(childEntityDef); } @Override public CopyAttributesBuilder copyAttributes() { return new CopyAttributesBuilder().to(this); } @Override public int setMatchingAttributesByName(EntityInstance sourceInstance) { return setMatchingAttributesByName(sourceInstance, SetMatchingFlags.DEFAULT); } @Override public int setMatchingAttributesByName(EntityInstance sourceInstance, EnumSet<SetMatchingFlags> control) { EntityDef sourceEntityDef = sourceInstance.getEntityDef(); // Loop through the target attributes and set their value from the source entity. for (AttributeDef targetAttr : this.getEntityDef().getAttributes()) { if (targetAttr.isHidden()) continue; if (targetAttr.isDerived()) continue; if (!targetAttr.isUpdate()) continue; // If target entity was not created (this means that the entity // has been committed to the database) then the attribute cannot // be updated if it is a key. if (!this.isCreated() && targetAttr.isKey()) continue; if (targetAttr.isKey() && !control.contains(SetMatchingFlags.fSET_KEYS)) continue; // Check to see if user wants to over-write non-null values. if (!control.contains(SetMatchingFlags.fSET_NOTNULL)) { // The user does NOT want attributes in the target entity that // already have values (i.e. they are not null) to be over-written // with values from the source entity. Check the target entity's // value. If it is not null, then continue looping. // We can pass null for the view because we've already determined that // target is not a derived attribute. if (!getInternalAttribute(targetAttr).isNull(getTask(), targetAttr)) continue; } AttributeDef sourceAttr = sourceEntityDef.getAttribute(targetAttr.getName(), false); if (sourceAttr == null) continue; // No matching attribute by name. // Source attr cannot be hidden or derived either. if (sourceAttr.isHidden() || sourceAttr.isDerived()) continue; // Ignore if both are null. if (getInternalAttribute(targetAttr).isNull(getTask(), targetAttr) && sourceInstance.getAttribute(sourceAttr).isNull()) continue; if (control.contains(SetMatchingFlags.fSET_SRCNOTNULL)) { // User doesn't want NULL source attributes to be copied. if (sourceInstance.getAttribute(sourceAttr).isNull()) continue; } Object sourceValue = sourceInstance.getAttribute(sourceAttr).getValue(); getAttribute(targetAttr).setValue(sourceValue); } // for each target AttributeDef... return 0; } @Override public boolean isDbhCreated() { return dbhCreated; } @Override public boolean isDbhDeleted() { return dbhDeleted; } @Override public boolean isDbhExcluded() { return dbhExcluded; } @Override public boolean isDbhUpdated() { return dbhUpdated; } @Override public boolean isDbhIncluded() { return dbhIncluded; } @Override public boolean isDbhNeedsInclude() { return dbhNeedsInclude; } @Override public boolean isDbhSeqUpdated() { return dbhSeqUpdated; } @Override public boolean isDbhGenKeyNeeded() { return dbhGenKeyNeeded; } @Override public boolean isDbhNoGenKey() { return dbhNoGenKey; } @Override public boolean isDbhForeignKey() { return dbhForeignKey; } /** * Validate the context name. If the context name is null or "", returns the default context. * @param attributeDef * @param contextName * @return */ private String checkContextName(AttributeDef attributeDef, String contextName) { Domain domain = attributeDef.getDomain(); return domain.getContext(getTask(), contextName).getName(); } AttributeHashKeyMap getAttributeHashkeyMap() { if (attributeHashkeyMap == null) attributeHashkeyMap = new AttributeHashKeyMap(objectInstance); return attributeHashkeyMap; } /** * Get the correct AttributeHaskeyMap depending on the type. * * @param attributeDef * @return */ AttributeHashKeyMap getAttributeHashkeyMap(AttributeDef attributeDef) { if (attributeDef.getHashKeyType() == AttributeHashKeyType.GLOBAL) return getObjectInstance().getAttributeHashkeyMap(); if (attributeDef.getHashKeyType() == AttributeHashKeyType.UNDER_PARENT) { EntityInstanceImpl parent = findMatchingParent(attributeDef.getHashKeyParent()); return parent.getAttributeHashkeyMap(); } throw new ZeidonException("Unsupported AttributeHashKeyType %s", attributeDef.getHashKeyType()); } /** * We've updated an attribute. Check to see if we need to update the attribute hashkey map. * * @param attributeDef * @param oldValue */ void updateHashKeyAttributeToMap(AttributeDef attributeDef, Object oldValue) { if (attributeDef.getHashKeyType() == AttributeHashKeyType.NONE) return; // Nothing to do. getAttributeHashkeyMap(attributeDef).updateHashKey(oldValue, attributeDef, this); } private void addHashKeyAttributeToMap(AttributeDef attributeDef) { if (attributeDef.getHashKeyType() == AttributeHashKeyType.NONE) return; // Nothing to do. getAttributeHashkeyMap(attributeDef).addHashKey(attributeDef, this); } private void removeHashKeyAttributeToMap(AttributeDef attributeDef) { if (attributeDef.getHashKeyType() == AttributeHashKeyType.NONE) return; // Nothing to do. getAttributeHashkeyMap(attributeDef).removeHashKey(attributeDef, this); } private void removeAllHashKeyAttributes() { if (getEntityDef().getHashKeyAttributes() == null) return; for (AttributeDef attributeDef : getEntityDef().getHashKeyAttributes()) removeHashKeyAttributeToMap(attributeDef); } void addAllHashKeyAttributes() { if (getEntityDef().getHashKeyAttributes() == null) return; for (AttributeDef attributeDef : getEntityDef().getHashKeyAttributes()) addHashKeyAttributeToMap(attributeDef); } /** * Temporary object used to create a temporal subobject. * * @author DGC * */ private static class TemporalVersionCreator { private final EntityInstanceImpl originalRoot; private final long version; private final TaskImpl task; /** * Keeps track of all the new entity instances. */ private final List<EntityInstanceImpl> newInstanceList; private TemporalVersionCreator(TaskImpl task, EntityInstanceImpl originalRoot) { this.originalRoot = originalRoot; this.task = task; version = originalRoot.getTask().getNextVersionNumber(); newInstanceList = new ArrayList<EntityInstanceImpl>(); } private EntityInstanceImpl createTemporalVersion() { final EntityInstanceImpl newRoot = createTemporalSubobject(originalRoot); originalRoot.getObjectInstance().incrementVersionedCount(); assert !originalRoot.getLinkedInstances() .contains(newRoot) : "New temporal entity should not be part of prev's linked EIs"; return newRoot; } private EntityInstanceImpl createTemporalSubobject(EntityInstanceImpl root) { // Create temporal entities for the root and all its children. final EntityInstanceImpl newRoot = createTemporalEntity(root); newRoot.setVersionStatus(VersionStatus.UNACCEPTED_ROOT); for (final EntityInstanceImpl ei : root.getChildrenHier(false, true, true)) createTemporalEntity(ei); // We've created all the new entity versions but their prev/next pointers // are pointing to the original instances. Go through them all and set // their pointers to the new ones. // Now set the original EI's to have a new version. for (final EntityInstanceImpl ei : newInstanceList) { EntityInstanceImpl prevVsn = ei.prevVersion; prevVsn.setNextVersion(ei); assert prevVsn.getEntityDef() == ei.getEntityDef(); } // Now we can use getLatestVersion to set the other pointers. for (EntityInstanceImpl ei : newInstanceList) { // Each of the 'get' methods (ie. getNextHier) will use getLatestVersion to get the latest version. ei.copyAllPointers(ei); } return newRoot; } /** * Creates a temporal entity for prevVersion and copies attributes and flag information. * The next/prev pointers will be the same as prevVersion. * * This does not set prevVersionEi.nextVersion. This will be done after all of the new * versions are created. * * @param prevVersionEi * @return */ private EntityInstanceImpl createTemporalEntity(final EntityInstanceImpl prevVersionEi) { // We're going to replace the current version in the attribute hashkey map with // a new version. The hashkey values will be added as we copy attribute values // so all we need to do is remove the old ones. prevVersionEi.removeAllHashKeyAttributes(); final EntityInstanceImpl newInstance = new EntityInstanceImpl(prevVersionEi.getObjectInstance(), prevVersionEi.getEntityDef(), prevVersionEi.getParent()); newInstance.versionNumber = version; newInstance.setVersionStatus(VersionStatus.UNACCEPTED); // Keep track of all the new instances. When we're all done we'll reset all // the next/prev pointers to the other new instances. newInstanceList.add(newInstance); // Copy the next/prev pointers from the previous version. When we're done creating // new instances we'll go back and reset them to the new version. We don't set // prevVersion.nextVersion now because if there's an error we won't have to go // back and change them. newInstance.copyAllPointers(prevVersionEi); newInstance.prevVersion = prevVersionEi; newInstance.copyFlags(prevVersionEi); // Check to see if this instance is linked to another instance that // is versioned. If one is found we'll copy its persistent attributes to newInstance. // We can do this by checking to see if any linked instances have the same version number. EntityInstanceImpl linkedSourceInstance = null; for (EntityInstanceImpl linked : prevVersionEi.getLinkedInstances()) { if (linked.versionNumber == version) { linkedSourceInstance = linked; break; } } // This adds the newInstance to the linked instances list. newInstance.newTemporalAttributeList(prevVersionEi, linkedSourceInstance); return newInstance; } } // private static class TemporalVersionCreator /** * Creates a new AttributeListInstance for a temporal entity. The work * attributes are copied from prevInstanceAttributeList. Persistent * attributes are copied from prevInstanceAttributeList unless * linkedAttributeList is not null. * * If linkedAttributeList is not null then temporalInstance is linked to * another instance so we'll just use the linked instance's persistent * attribute. * * @param sourceInstance - source EI for the temporal instance. * @param linkedSourceInstnce - If non-null, then the temporal instance will be linked to this one. */ void newTemporalAttributeList(EntityInstanceImpl sourceInstance, EntityInstanceImpl linkedSourceInstance) { workAttributes = new HashMap<>(entityDef.getWorkAttributeCount()); if (linkedSourceInstance == null) { persistentAttributes = new HashMap<>(entityDef.getPersistentAttributeCount()); // Copy work and persistent attributes. copyAttributes(sourceInstance, true, true); linkedInstances = null; } else { linkedSourceInstance.addLinkedInstance(this); // Copy just work attributes. copyAttributes(sourceInstance, false, true); } } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#logEntity() */ @Override public void logEntity() { logEntity(true, 0); } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#logEntity(boolean) */ @Override public void logEntity(boolean logChildren) { logEntity(logChildren, 0); } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#logChildren(boolean, int) */ @Override public void logEntity(boolean logChildren, int indentN) { EntityDef entityDef = getEntityDef(); String indent = StringUtils.repeat(" ", indentN); StringBuilder flagStr = new StringBuilder(); if (isUpdated()) flagStr.append(" Updated"); if (isCreated()) flagStr.append(" Created"); if (isIncluded()) flagStr.append(" Included"); if (isDeleted()) flagStr.append(" Deleted"); if (isExcluded()) flagStr.append(" Excluded"); getTask().log().info("%s[%s] %s (%x:%d)", indent, entityDef.getName(), flagStr, hashCode(), getEntityKey()); for (AttributeDef attributeDef : getNonNullAttributeList()) { AttributeInstanceImpl attrib = getAttribute(attributeDef); String value = attrib.getString(null); // if ( ( flags & View.DISPLAY_EMPTY_ATTRIBS ) == 0 && StringUtils.isBlank( value ) ) // continue; // // if ( ( flags & View.DISPLAY_HIDDEN ) == 0 && attributeDef.isHidden() ) // continue; getTask().log().info("%s (%s)%s: %s", indent, attrib.isUpdated() ? "#" : "~", attributeDef.getName(), value); } if (logChildren) { for (EntityInstance ei : getDirectChildren()) ei.logEntity(logChildren, indentN + 1); } } /** * @deprecated Use logEntity instead. */ @Deprecated @Override public void displayEntity() { logEntity(true, 0); } /** * @deprecated Use logEntity instead. */ @Deprecated @Override public void displayEntity(boolean logChildren) { logEntity(logChildren, 0); } /** * @deprecated Use logEntity instead. */ @Deprecated @Override public void displayEntity(boolean logChildren, int indentN) { logEntity(logChildren, indentN); } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#getEntityInstance() */ @Override public EntityInstance getEntityInstance() { return this; } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#getHierPosition() */ @Override public long getHierPosition() { long position = 0; for (EntityInstanceImpl ei = this.getPrevHier(); ei != null; ei = ei.getPrevHier()) { if (!ei.isHidden()) position++; } return position; } @Override public long getPosition() { long position = 0; for (EntityInstanceImpl ei = getPrevTwin(); ei != null; ei = ei.getPrevTwin()) { if (!ei.isHidden()) position++; } return position; } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#isLinked(com.quinsoft.zeidon.EntityInstance) */ @Override public boolean isLinked(EntityInstance ei) { if (ei == null) return false; EntityInstanceImpl otherInstance = (EntityInstanceImpl) ei.getEntityInstance(); return getUniqueLinkedObject() == otherInstance.getUniqueLinkedObject(); } /** * This returns an object that can be used to compare two EIs to see if they * are linked. E.g. ei1 and ei2 are linked if: * * ei1.getUniqueLinkedObject() == ei2.getUniqueLinkedObject() * * This can be used to create hashmaps. */ Object getUniqueLinkedObject() { return persistentAttributes; } /** * @return The total number of non-hidden twins for this entity, including this one. */ int getTwinCount() { int count = 0; for (EntityInstanceImpl search = this; search != null; search = search.getNextTwin()) { if (!search.isHidden()) count++; } for (EntityInstanceImpl search = this.getPrevTwin(); search != null; search = search.getPrevTwin()) { if (!search.isHidden()) count++; } return count; } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#setIncrementalFlags(java.util.EnumSet) */ @Override public EntityInstanceImpl setIncrementalFlags(EnumSet<IncrementalEntityFlags> flags) { created = flags.contains(IncrementalEntityFlags.fCREATED); deleted = flags.contains(IncrementalEntityFlags.fDELETED); updated = flags.contains(IncrementalEntityFlags.fUPDATED); included = flags.contains(IncrementalEntityFlags.fINCLUDED); excluded = flags.contains(IncrementalEntityFlags.fEXCLUDED); hidden = deleted || excluded; return this; } /* (non-Javadoc) * @see com.quinsoft.zeidon.EntityInstance#setIncrementalFlags(com.quinsoft.zeidon.standardoe.IncrementalEntityFlags[]) */ @Override public EntityInstance setIncrementalFlags(IncrementalEntityFlags flag) { return setIncrementalFlags(EnumSet.of(flag)); } /** * Return the set of entities that have been lazy loaded. * * This needs to be synchronized because two different views could be * trying to access the same OI at the same time. * * @return the entitiesLoadedLazily */ synchronized Set<EntityDef> getEntitiesLoadedLazily() { // We should only get here if this entity instance can have children that can be // lazy loaded. assert getEntityDef().getLazyLoadConfig() .hasLazyLoadChild() : "Attempting to get lazy load set for entity without LazyLoad children"; if (entitiesLoadedLazily == null) entitiesLoadedLazily = new HashSet<EntityDef>(); return entitiesLoadedLazily; } /** * Returns true if this EI has attempted to load any lazy-load children. * @return */ boolean hasLoadedLazyChildren() { return entitiesLoadedLazily != null && entitiesLoadedLazily.size() > 0; } @Override public AttributeInstance getAttribute(String attributeName) { AttributeDef attributeDef = getEntityDef().getAttribute(attributeName); if (attributeDef.isHidden()) throw new HiddenAttributeException(attributeDef); return getAttribute(null, attributeDef); } @Override public AttributeInstanceImpl getAttribute(AttributeDef attributeDef) { return getAttribute(null, attributeDef); } AttributeInstanceImpl getAttribute(View view, AttributeDef attributeDef) { AttributeInstanceImpl attributeInstance = new AttributeInstanceImpl(attributeDef, getInternalAttribute(attributeDef), view, this); return attributeInstance; } @Override public AttributeInstance createDynamicAttributeDef(DynamicAttributeDefConfiguration config) { AttributeDef attributeDef = getEntityDef().createDynamicAttributeDef(config); return getAttribute(attributeDef); } /** * @returns true if 'this' entity instance does not have all its children loaded. * This can happen if an OI was activated with a RESTRICTING clause or if a child * was dropped via dropEntity(). An incomplete EI cannot be deleted. */ @Override public boolean isIncomplete() { return incomplete; } boolean isParentOf(EntityInstance child) { child = child.getEntityInstance(); // In case child is an EntityCursor. while (child != null) { if (child == this) return true; child = child.getParent(); } return false; } boolean isChildOf(EntityInstance parent) { EntityInstanceImpl p = (EntityInstanceImpl) parent.getEntityInstance(); return p.isParentOf(this); } /** * This method is called if 'this' entity instance does not have all its children. * This can happen if an OI was activated with a RESTRICTING clause or if a child * was dropped via dropEntity(). * * In this situation we want to set a flag to indicate that 'this' entity cannot * be deleted. * * @param childEntity */ void setIncomplete(EntityDef childEntity) { // If we've already set the flag then return. if (incomplete) return; // If childEntity is null then we are performing a root-only activate // so we don't need to check the children. if (childEntity != null) { // If the child entity isn't deleted when 'this' entity is deleted then // we're good. if (!childEntity.isParentDelete()) return; if (childEntity.isDerived()) return; // If this entity is already deleted then we'll assume the user knows what // she's doing. if (isDeleted()) return; } incomplete = true; // If there is a parent entity then we'll set its incomplete flag. if (getParent() != null) getParent().setIncomplete(getEntityDef()); } @Override public List<AttributeInstance> getAttributes() { return getAttributes(true); } @Override public List<AttributeInstance> getAttributes(boolean includeNullValues) { List<AttributeInstance> list = new ArrayList<>(); if (includeNullValues) { for (AttributeDef attributeDef : getEntityDef().getAttributes()) list.add(getAttribute(attributeDef)); } else { for (AttributeDef attributeDef : getNonNullAttributeList()) list.add(getAttribute(attributeDef)); } return list; } @Override public void copyAttributes(CopyAttributesBuilder flags) { EntityInstance sourceInstance = flags.getSourceInstance(); EntityDef sourceEntityDef = sourceInstance.getEntityDef(); // Loop through the target attributes and set their value from the source entity. for (AttributeDef targetAttr : this.getEntityDef().getAttributes()) { if (targetAttr.isHidden() && !flags.isCopyHidden()) continue; if (targetAttr.isPersistent() && !flags.isCopyPersistent()) continue; if (!targetAttr.isPersistent() && !flags.isCopyWork()) continue; if (targetAttr.isDerived()) continue; if (!targetAttr.isUpdate()) continue; // If target entity was not created (this means that the entity // has been committed to the database) then the attribute cannot // be updated if it is a key. if (!this.isCreated() && targetAttr.isKey()) { if (flags.isCopyKeys()) throw new ZeidonException( "Attempting to copy a key value to an entity instance that already has a key") .appendMessage("Target instance = %s", this.toString()) .appendMessage("Source instance = %s", sourceInstance.toString()); continue; } if (targetAttr.isKey() && !flags.isCopyKeys()) continue; // Check to see if user wants to over-write non-null values. if (flags.isKeepNonNull()) { // The user does NOT want attributes in the target entity that // already have values (i.e. they are not null) to be over-written // with values from the source entity. Check the target entity's // value. If it is not null, then continue looping. if (!getInternalAttribute(targetAttr).isNull(getTask(), targetAttr)) continue; } AttributeDef sourceAttr = sourceEntityDef.getAttribute(targetAttr.getName(), false); if (sourceAttr == null) continue; // No matching attribute by name. // Source attr cannot be hidden or derived either. if ((sourceAttr.isHidden() && !flags.isCopyHidden()) || sourceAttr.isDerived()) continue; // Ignore if both are null. if (getInternalAttribute(targetAttr).isNull(getTask(), targetAttr) && sourceInstance.getAttribute(sourceAttr).isNull()) continue; if (flags.isCopyNulls()) { // User doesn't want NULL source attributes to be copied. if (sourceInstance.getAttribute(sourceAttr).isNull()) continue; } Object sourceValue = sourceInstance.getAttribute(sourceAttr).getValue(); getAttribute(targetAttr).setValue(sourceValue); } // for each target AttributeDef... } @Override public boolean compareEntity(EntityInstance sourceInstance) { return compareEntity(sourceInstance, CompareEntityOptions.DEFAULT_OPTIONS); } @Override public boolean compareEntity(EntityInstance sourceInstance, CompareEntityOptions options) { EntityInstance sourceEi = sourceInstance.getEntityInstance(); if (isLinked(sourceEi)) return true; // If the EIs are linked then they are the same. List<AttributeDef> attribs = this.getEntityDef().getAttributes(true); EntityDef rightDef = sourceEi.getEntityDef(); for (AttributeDef attrib : attribs) { if (attrib.isDerived()) // Skip derived attributes? { if (!options.isCompareDeriviedAttributes()) continue; } else if (!attrib.isPersistent()) // Skip work attributes? { if (!options.isCompareWorkAttributes()) continue; } AttributeInstance lai = this.getAttribute(attrib); if (lai.isNull() && options.isIgnoreNullAttributes()) continue; AttributeDef rightAttrib = rightDef.getAttribute(attrib.getName(), false); if (rightAttrib == null || rightAttrib.isHidden()) { if (!options.isCommonAttributesOnly()) { getTask().log().trace("CompareEntity: Attribute missing = %s", attrib.getName()); return false; } continue; } AttributeInstance rai = sourceEi.getAttribute(rightAttrib); if (lai.compare(rai) != 0) { getTask().log().trace("CompareEntity: Attribute is different = %s", attrib.getName()); lai.compare(rai); return false; } } // each attrib. // If we get here then we didn't find any differences. return true; } }