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

com.quinsoft.zeidon.objectdefinition.EntityDef Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/**
    This file is part of the Zeidon Java Object Engine (Zeidon JOE).

    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 .

    Copyright 2009-2015 QuinSoft
 */
/**
 *
 */
package com.quinsoft.zeidon.objectdefinition;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.MapMaker;
import com.quinsoft.zeidon.CacheMap;
import com.quinsoft.zeidon.EntityConstraintType;
import com.quinsoft.zeidon.EventListener;
import com.quinsoft.zeidon.ObjectEngine;
import com.quinsoft.zeidon.UnknownAttributeDefException;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.objectdefinition.LazyLoadConfig.LazyLoadFlags;
import com.quinsoft.zeidon.objectdefinition.RelRecord.RelationshipType;
import com.quinsoft.zeidon.utils.CacheMapImpl;
import com.quinsoft.zeidon.utils.EventStackTrace;
import com.quinsoft.zeidon.utils.PortableFileReader;
import com.quinsoft.zeidon.utils.PortableFileReader.PortableFileAttributeHandler;

/**
 * @author DG
 *
 */
public class EntityDef implements PortableFileAttributeHandler
{
    private LodDef     lodDef;
    private EntityDef  prevHier;
    private EntityDef  nextHier;
    private EntityDef  parent;
    private EntityDef  prevSibling;
    private EntityDef  nextSibling;
    private String     name;
    private String     erEntityToken;
    private String     erRelToken;
    private boolean    erRelLink;  // RelLink direction.  True = '1' from the XOD file.
    private final int        depth;
    private final int        entityNumber;
    private List children;
    private List childrenHier;
    private EventListener eventListener;
    private ArrayList activateOrdering;
    private Integer    activateLimit;
    private EntityDefLinkInfo linkInfo;

    /**
     * List of the attributes in the order they are defined in the XOD file.
     */
    private final List attributes = Collections.synchronizedList( new ArrayList() );

    /**
     * List of the non-hidden attributes in the order they are defined in the XOD file.
     */
    private volatile List nonHiddenAttributes = Collections.synchronizedList( new ArrayList() );;

    /**
     * Map of attributes by attribute name.  This is a concurrent map because this can be increased
     * with dynamic attributes.
     */
    private final ConcurrentMap attributeMap = new MapMaker().concurrencyLevel( 2 ).makeMap();

    /**
     * Map of attributes by ER attribute token.
     */
    private final Map erAttributeMap = new HashMap<>();

    /**
     * This map keeps track of entities that have been checked to see if 'this' entity
     * is an attribute superset.
     * 	Key = LodDef of 'child' entity.
     *  Value = true if 'this' entity is a superset.
     *
     *  This is maintained at run-time which is why we need it to be a concurrent map.
     */
    private final ConcurrentMap attributeSuperset = new MapMaker().concurrencyLevel( 2 ).weakKeys().makeMap();

    private CacheMap cacheMap;

    // Permission flags.
    private boolean    create  = false;
    private boolean    include = false;
    private boolean    exclude = false;
    private boolean    delete  = false;
    private boolean    update  = false;
    private boolean    includeSrc = false;

    private final List keys;
    private AttributeDef genKey;
    private AttributeDef autoSeq;
    private Collection hashKeyAttributes;
    private boolean    parentDelete = false;
    private boolean    restrictParentDelete = false;
    private boolean    checkRestrictedDelete = false;
    private boolean    derived = false;
    private boolean    derivedPath = false;
    private boolean    recursive = false;
    private boolean    duplicateEntity = false;
    private boolean    autoloadFromParent;
    private int        persistentAttributeCount;
    private int        workAttributeCount;
    private DataRecord dataRecord;
    /**
     * If not null, this is the relationship that is used to version this entity.
     */
    private DataField  versioningDataField;
    private String     versioningAttributeTok;

    private EntityDef  recursiveParent;
    private int        minCardinality;
    private int        maxcardinality;
    private boolean    hasInitializedAttributes = false;

    /**
     * If this entity is a recursive parent then recursiveChild references the child.
     */
    private EntityDef recursiveChild;

    /**
     * This is true if this EntityDef has a parent that is a Recursive parent.
     */
    private boolean recursivePath;

    // Flags to help debug OI.
    private boolean    debugIncrementalFlag; // If true, then pop up a message when we change an incremental flag.

    private final LazyLoadConfig lazyLoadConfig;

    /**
     * SourceFileType for the entity constraint.
     */
    private SourceFileType sourceFileType = SourceFileType.VML;
    private String         sourceFileName;
    private String         constraintOper;

    private boolean hasCancelConstraint;
    private boolean hasAcceptConstraint;
    private boolean hasExcludeConstraint;
    private boolean hasIncludeConstraint;
    private boolean hasDeleteConstraint;
    private boolean hasCreateConstraint;
    private AttributeDef dbCreatedTimestamp;
    private AttributeDef dbUpdatedTimestamp;

    public EntityDef( LodDef lodDef, int level )
    {
        this.lodDef = lodDef;
        this.entityNumber = lodDef.getEntityCount();
        this.depth = level;
        keys = new ArrayList();
        lazyLoadConfig = new LazyLoadConfig();
    }

    @Override
    public void setAttribute(PortableFileReader reader)
    {
        String attributeName = reader.getAttributeName();

        switch ( attributeName )
        {
            case "ACT_LIMIT":
                activateLimit = Integer.parseInt( reader.getAttributeValue() );
                break;

            case "AUTOLOADFROMPARENT":
                autoloadFromParent = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "CREATE":
                create = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "CARDMAX":
                maxcardinality = Integer.parseInt( reader.getAttributeValue() );
                break;

            case "CARDMIN":
                minCardinality = Integer.parseInt( reader.getAttributeValue() );
                break;

            case "DELETE":
                delete = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "DERIVED":
                derived = true;
                derivedPath = true;
                break;

            case "DEBUGCHG":
                // Set up a listener to write stack trace when an entity is
                // changed.
                if ( StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" ) )
                    eventListener = new EventStackTrace();
                break;

            case "DEBUGINCRE":
                debugIncrementalFlag = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            case "DUPENTIN":
                duplicateEntity = true;
                lodDef.setHasDuplicateInstances( true );
                break;

            case "ECESRCFILE":
                sourceFileName = reader.getAttributeValue();
                if ( !sourceFileName.contains( "." ) )
                    sourceFileName = getLodDef().getApplication().getPackage() + "." + sourceFileName;
                break;

            case "ECESRCTYPE":
                sourceFileType = SourceFileType.parse( reader.getAttributeValue() );
                break;

            case "ECEOPER":
                constraintOper = reader.getAttributeValue().intern();
                break;

            case "ECCR":
                hasCreateConstraint = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            case "ECDEL":
                hasDeleteConstraint = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            case "ECINC":
                hasIncludeConstraint = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );

                // Include constraints take some work. Since nobody appears to
                // use them let's not
                // worry about implementing them for now.
                throw new UnsupportedOperationException( "Include constraints not supported yet." );

            case "ECEXC":
                hasExcludeConstraint = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            case "ECACC":
                hasAcceptConstraint = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            case "ECCAN":
                hasCancelConstraint = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            case "ERENT_TOK":
                erEntityToken = reader.getAttributeValue().intern();
                break;

            case "ERREL_TOK":
                erRelToken = reader.getAttributeValue().intern();
                break;

            case "ERREL_LINK":
                erRelLink = reader.getAttributeValue().equals( "1" );
                break;

            case "EXCLUDE":
                exclude = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "INCLUDE":
                include = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "INCLSRC":
                includeSrc = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "LAZYLOAD":
                getLazyLoadConfig().setFlag( LazyLoadFlags.IS_LAZYLOAD );
                if ( getParent() == null )
                    throw new ZeidonException( "LAZYLOAD is invalid for root entity" );

                LazyLoadConfig parentConfig = getParent().getLazyLoadConfig();
                parentConfig.setFlag( LazyLoadFlags.HAS_LAZYLOAD_CHILD );
                getLodDef().setHasLazyLoadEntities( true );

                break;

            case "NAME":
                name = reader.getAttributeValue().intern();
                break;

            case "PDELETE":
                switch ( reader.getAttributeValue().charAt( 0 ) )
                {
                    case 'D':
                        parentDelete = true;
                        break;

                    case 'R':
                        restrictParentDelete = true;
                        getParent().setCheckRestrictedDelete( true );
                        break;

                    // It looks like we're supposed to ignore 'E'.
                }
                break;

            case "RECURSIVE":
                // Check to see if this entity is recursive.
                for ( EntityDef search = parent; search != null; search = search.getParent() )
                {
                    if ( search.getErEntityToken() == erEntityToken )
                    {
                        search.recursiveChild = this;
                        this.recursive = true;
                        this.recursiveParent = search;

                        for ( EntityDef child = search;
                              child != null && child.getDepth() > this.getDepth();
                              child = child.getNextHier() )
                        {
                            child.recursivePath = true;
                        }

                        break;
                    }
                }

                if ( ! recursive )
                    throw new ZeidonException( "Internal error: Recursive flag is set but no recursive parent found. %s",
                                               this );
                break;

            case "UPDATE":
                update = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "VERSIONINGATTRIBUTETOK":
                versioningAttributeTok = reader.getAttributeValue();
                break;
        }
    }

    /**
     * This is called after the entity def has been completely loaded.  We'll validate that
     * the entity def is configured correctly.
     *
     */
    void validateEntityDef( )
    {
        DataRecord childRecord = getDataRecord();
        if ( childRecord != null )
        {
            // Make sure SINGLE SELECT is configured properly.
            if ( childRecord.isActivateWithSingleSelect() )
            {
                if ( childRecord.isJoinable() )
                    throw new ZeidonException( "EntityDef shouldn't be JOIN and ACTIVATONE" );

                RelRecord relRecord = childRecord.getRelRecord();
                RelationshipType relType = relRecord.getRelationshipType();

                // We don't support m-to-1 relationships because it is too hard
                // to figure out where the children go.  In most cases this doesn't
                // matter because m-to-1 children should be joined with the parent.
                if ( relType.isManyToOne() )
                    throw new ZeidonException( "m-to-1 relationships not supported in a single select." );

                // We can only handle it if there is a single key between child
                // and parent.
                if ( relType.isOneToMany() && relRecord.getRelFields().size() > 1 )
                    throw new ZeidonException( "Only single keys supported in a single select." );

                if ( relType.isManyToMany() && relRecord.getRelFields().size() > 2 )
                    throw new ZeidonException( "Only single keys supported in a single select." );

                // Can't load children of recursive entities.
                for ( EntityDef ve = this; ve != null; ve = ve.getParent() )
                {
                    if ( ve.isRecursive() )
                        throw new ZeidonException( "Recursive entities not supported in a single select." );
                }
            }
        }

        // Everything looks good.  Lets check to see if there's a versioning relationship.
        if ( versioningAttributeTok != null )
            setVersioningRel();
    }

    private void setVersioningRel()
    {
        if ( isDerived() )
            return;

        if ( getKeys().size() != 1 )
            throw new ZeidonException( "Automatic Entity Versioning only supported for entities with a single key" );

        AttributeDef versioningAttribute = getAttributeByErToken( versioningAttributeTok );

        // The versioning attribute should be a foreign key, which means it should be hidden.
        assert versioningAttribute.isHidden() : "Versioning attribute is not hidden.";

        versioningDataField  = getDataRecord().getDataField( versioningAttribute );
    }

    /**
     * Name of the entity  This string has been intern() so it is safe to use ==
     * instead of equals().
     *
     * @return internal string.
     */
    public String getName()
    {
        return name;
    }

    /**
     * A string ID that uniquely defines this entity from the ER.  This string has
     * been intern() so it is safe to use == instead of equals().
     *
     * @return internal string.
     */
    public String getErEntityToken()
    {
        return erEntityToken;
    }

    public LodDef getLodDef()
    {
        return lodDef;
    }

    void setLodDef(LodDef lodDef)
    {
        this.lodDef = lodDef;
    }

    public EntityDef getPrevHier()
    {
        return prevHier;
    }

    public void setPrevHier(EntityDef prevHier)
    {
        this.prevHier = prevHier;
    }

    public EntityDef getNextHier()
    {
        return nextHier;
    }

    public void setNextHier(EntityDef nextHier)
    {
        this.nextHier = nextHier;
    }

    public EntityDef getParent()
    {
        return parent;
    }

    void setParent(EntityDef parent)
    {
        this.parent = parent;
        if ( parent.derivedPath )
            derivedPath = true;

        if ( parent.children == null )
            parent.children = new ArrayList();

        LazyLoadConfig parentConfig = parent.getLazyLoadConfig();
        if ( parentConfig.isLazyLoad() )
        {
            getLazyLoadConfig().setFlag( LazyLoadFlags.HAS_LAZYLOAD_PARENT );
            getLazyLoadConfig().setLazyLoadParent( getParent() );
        }
        else
        if ( parentConfig.hasLazyLoadParent() )
        {
            getLazyLoadConfig().setFlag( LazyLoadFlags.HAS_LAZYLOAD_PARENT );
            getLazyLoadConfig().setLazyLoadParent( parentConfig.getLazyLoadParent() );
        }

        if ( parent.recursivePath || parent.isRecursive() || parent.isRecursiveParent() )
            recursivePath = true;

        parent.children.add( this );
    }

    public EntityDef getPrevSibling()
    {
        return prevSibling;
    }

    public void setPrevSibling(EntityDef prevSibling)
    {
        this.prevSibling = prevSibling;
    }

    public EntityDef getNextSibling()
    {
        return nextSibling;
    }

    public void setNextSibling(EntityDef nextSibling)
    {
        this.nextSibling = nextSibling;
    }

    /**
     * Returns the depth of this EntityDef.  The root has a depth of 1.
     *
     * @return entity depth.
     */
    public int getDepth()
    {
        return depth;
    }

    void addAttributeDef( AttributeDef attributeDef )
    {
        attributes.add( attributeDef );
        if ( ! attributeDef.isHidden() )
            nonHiddenAttributes.add( attributeDef );
        attributeMap.put( attributeDef.getName(), attributeDef );
        attributeMap.put( attributeDef.getName().toLowerCase(), attributeDef );

        if ( ! attributeDef.isDynamicAttribute() )
            erAttributeMap.put( attributeDef.getErAttributeToken(), attributeDef );
    }

    public AttributeDef createDynamicAttributeDef( DynamicAttributeDefConfiguration config )
    {
        if ( attributeMap.containsKey( config.getAttributeName() ) )
        {
            if ( config.canExist() )
                return attributeMap.get( config.getAttributeName() );

            throw new ZeidonException( "Attribute already exists with name: %s", config.getAttributeName() );
        }

        AttributeDef dynamicAttrib = new AttributeDef( this, config );
        addAttributeDef( dynamicAttrib );
        return dynamicAttrib;
    }

    public int getAttributeCount()
    {
        return attributes.size();
    }

    public AttributeDef getAttribute( String attribName )
    {
        return getAttribute( attribName, true, false );
    }

    public AttributeDef getAttribute( String attribName, boolean required )
    {
        return getAttribute( attribName, required, false );
    }

    public AttributeDef getAttribute( String attribName, boolean required, boolean ignoreCase )
    {
        String searchName = attribName;
        if ( ignoreCase )
            searchName = searchName.toLowerCase();

        AttributeDef attrib = attributeMap.get( searchName );
        if ( attrib == null && required )
            throw new UnknownAttributeDefException( this, attribName );

        return attrib;
    }

    public AttributeDef getAttribute( int index )
    {
        if ( index >= attributes.size() )
            throw new ZeidonException("Attribute index %d out of range for %s.",
                                      index, lodDef.getName() );

        return attributes.get( index );
    }

    public AttributeDef getAttributeByErToken( String erToken )
    {
        return erAttributeMap.get( erToken );
    }

    /**
     * @return all the attributes, including non-hidden ones.
     */
    public List getAttributes()
    {
        return Collections.unmodifiableList( attributes );
    }

    public List getAttributes( boolean excludeHidden )
    {
        if ( ! excludeHidden )
            return getAttributes();

        return Collections.unmodifiableList( nonHiddenAttributes );
    }

    public int getHierIndex()
    {
        return entityNumber;
    }

    @Override
    public String toString()
    {
        return lodDef.toString() + "." + name;
    }

    /**
     * If true then this entity will be deleted if its parent is deleted, otherwise
     * this entity will be excluded if it's parent is excluded.
     *
     * @return
     */
    public boolean isParentDelete()
    {
        return parentDelete ;
    }

    /**
     * If true, then the parent of this entity cannot be deleted when this entity
     * instance exists.
     *
     * @return
     */
    public boolean isRestrictParentDelete()
    {
        return restrictParentDelete ;
    }

    /**
     * If this flag is true, make sure none of the child entities have
     * the parent delete restrict flag set when the entity is being deleted.
     *
     * @return
     */
    public boolean isCheckRestrictedDelete()
    {
        return checkRestrictedDelete;
    }

    private void setCheckRestrictedDelete( boolean b )
    {
        checkRestrictedDelete = b;
    }

    public int getChildCount()
    {
        return children == null ? 0 : children.size();
    }

    protected void setSiblingsForChildren()
    {
        if ( children == null )
            return;

        for ( int i = 0; i < children.size(); i++ )
        {
            if ( i > 0 )
                children.get( i ).setPrevSibling( children.get( i - 1 ) );

            if ( i < children.size() - 1 )
                children.get( i ).setNextSibling( children.get( i + 1 ) );

            children.get( i ).setSiblingsForChildren();
        }
    }

    public boolean isDerived()
    {
        return derived;
    }

    public boolean isPersistent()
    {
        return ( ! isDerived() ) && ( ! isDerivedPath() );
    }

    /**
     * Returns a list of the direct children of this entity.
     * @return
     */
    public List getChildren()
    {
        if ( children == null )
            return Collections.emptyList();

        return children;
    }

    /**
     * Returns a list of all the children under the current entity
     * in hier order.
     * @return
     */
    public synchronized List getChildrenHier()
    {
        if ( children == null )
            return Collections.emptyList();

        if ( childrenHier != null )
            return childrenHier;

        List list = new ArrayList();
        for ( EntityDef child = this.getNextHier();
              child != null && child.getDepth() > this.getDepth();
              child = child.getNextHier() )
        {
            list.add( child );
        }

        childrenHier = list;

        return list;
    }

    /**
     * Returns the last child hierarchically under this entity.  If this entity has
     * no children, then returns itself.
     *
     * @return
     */
    public EntityDef getLastChildHier()
    {
        if ( children == null )
            return this;

        List list = getChildrenHier();
        return list.get( list.size() - 1 );
    }

    /**
     * A string ID that uniquely defines the relationship between this EntityDef
     * and its parent.  This string has been intern() so it is safe to use ==
     * instead of equals().
     *
     * @return internal string.
     */
    public String getErRelToken()
    {
        return erRelToken;
    }

    public boolean isErRelLink()
    {
        return erRelLink;
    }

    public int getPersistentAttributeCount()
    {
        return persistentAttributeCount;
    }

    void setPersistentAttributeCount(int persistentAttributeCount)
    {
        this.persistentAttributeCount = persistentAttributeCount;
    }

    public int getWorkAttributeCount()
    {
        return workAttributeCount;
    }

    void setWorkAttributeCount(int workAttributeCount)
    {
        this.workAttributeCount = workAttributeCount;
    }

    public boolean isCreate()
    {
        return create;
    }

    public DataRecord getDataRecord()
    {
        return dataRecord;
    }

    void setDataRecord(DataRecord dataRecord)
    {
        getLodDef().setHasPhysicalMappings( true );
        this.dataRecord = dataRecord;
    }

    public AttributeDef getGenKey()
    {
        return genKey;
    }

    public boolean isDerivedPath()
    {
        return derivedPath;
    }

    public boolean isInclude()
    {
        return include;
    }

    public boolean isIncludeSource()
    {
        return includeSrc;
    }

    public boolean isExclude()
    {
        return exclude;
    }

    public boolean isDelete()
    {
        return delete;
    }

    public boolean isUpdate()
    {
        return update;
    }

    void setGenKey(AttributeDef attributeDef)
    {
        genKey = attributeDef;
    }

    public boolean isRecursive()
    {
        return recursive;
    }

    public boolean isRecursiveParent()
    {
        return recursiveChild != null;
    }

    public boolean isRecursivePath()
    {
        return recursivePath;
    }

    /**
     * Returns true if 'this' is an ancestor of entityDef.
     *
     * @param entityDef
     * @return true if 'this' is an ancestor of entityDef.
     */
    public boolean isAncestorOf( EntityDef entityDef )
    {
        for ( EntityDef t = entityDef.getParent(); t != null; t = t.getParent() )
        {
            if ( t == this )
                return true;
        }

        return false;
    }

    /**
     * Returns true if 'this' is a descendant of entityDef.
     *
     * @param entityDef
     * @return true if 'this' is a descendant of entityDef.
     */
    public boolean isDescendantOf( EntityDef entityDef )
    {
        return entityDef.isAncestorOf( this );
    }

    /**
     * If this EntityDef is a recursive parent then this returns the recursive child.
     *
     * @return
     */
    public EntityDef getRecursiveChild()
    {
        return recursiveChild;
    }

    public boolean isDebugIncremental()
    {
        return debugIncrementalFlag;
    }

    void setAutoSeq(AttributeDef autoSeq)
    {
        this.autoSeq = autoSeq;
    }

    public AttributeDef getAutoSeq()
    {
        return autoSeq;
    }

    public List getKeys()
    {
        return keys;
    }

    void addKey( AttributeDef key )
    {
        keys.add( key );
    }

    /**
     * If this entity is recursive, this returns the recursive parent, null otherwise.
     *
     * @return
     */
    public EntityDef getRecursiveParent()
    {
        return recursiveParent;
    }

    /**
     * If this EntityDef is recursive then this returns the recursive parent,
     * otherwise returns 'this'.
     *
     * @return
     */
    public EntityDef getBaseEntityDef()
    {
        if ( getRecursiveParent() != null )
            return getRecursiveParent();

        return this;
    }

    public int getMinCardinality()
    {
        return minCardinality;
    }

    public int getMaxCardinality()
    {
        return maxcardinality;
    }

    public synchronized CacheMap getCacheMap()
    {
        if ( cacheMap == null )
            cacheMap = new CacheMapImpl();

        return cacheMap;
    }

    /**
     * If true, then there are other entities in this LodDef that have duplicate relationships
     * that may need to be relinked after activation.
     */
    public boolean isDuplicateEntity()
    {
        return duplicateEntity;
    }

    /**
     * @return the eventListener
     */
    public EventListener getEventListener()
    {
        return eventListener;
    }

    /**
     * @param eventListener the eventListener to set
     */
    public void setEventListener( EventListener eventListener )
    {
        this.eventListener = eventListener;
    }

    /**
     * @return the hasInitializedAttributes
     */
    public boolean hasInitializedAttributes()
    {
        return hasInitializedAttributes;
    }

    /**
     * @param hasInitializedAttributes the hasInitializedAttributes to set
     */
    public void setHasInitializedAttributes( boolean hasInitializedAttributes )
    {
        this.hasInitializedAttributes = hasInitializedAttributes;
    }

    /**
     * Returns true if 'this' EntityDef has all the persistent attributes of
     * 'otherEntity'.  Intended to be used by includeProcessing.
     *
     * @param otherEntity
     * @return
     */
    public boolean isAttributeSuperset( EntityDef otherEntity )
    {
    	if ( getErEntityToken() != otherEntity.getErEntityToken() )
    		throw new ZeidonException( "Entities do not have matching ER Entity Tokens." )
    						.prependEntityDef(this)
    						.prependMessage("Other entity = %s", otherEntity );

    	// Have we already determined the superset status for this entity?
    	if ( attributeSuperset.containsKey( otherEntity ) )
    		return attributeSuperset.get( otherEntity );  // Yes, so return it.

    	// Determine if 'this' entity is a superset.
    	Boolean isSuperset = Boolean.TRUE;
    	for ( AttributeDef attributeDef : otherEntity.getAttributes() )
    	{
    		if ( attributeDef.isPersistent() )
    		{
    			if ( getAttribute( attributeDef.getName(), false ) == null )
    			{
    				isSuperset = Boolean.FALSE;  // Use the constant value to preclude concurrency issues.
    				break;
    			}
    		}
    	}

    	// Store the answer in a concurrent map.  We don't care if there's thread
    	// collision because the answer will be the same in either case.
    	attributeSuperset.put( otherEntity, isSuperset );

    	return isSuperset;
    }

    /**
     * @return true if this EntityDef has hashkey attributes.
     */
    public boolean hasAttributeHashKeys()
    {
        return hashKeyAttributes != null;
    }

    /**
     * @return the hashKeyAttribute
     */
    public Collection getHashKeyAttributes()
    {
        return hashKeyAttributes;
    }

    /**
     * @param hashKeyAttribute the hashKeyAttribute to set
     */
    void addHashKeyAttribute( AttributeDef hashKeyAttribute )
    {
        if ( hashKeyAttributes == null )
            hashKeyAttributes = new ArrayList();

        hashKeyAttributes.add( hashKeyAttribute );
    }

    /**
     * @return the loadIncrementally
     */
    public LazyLoadConfig getLazyLoadConfig()
    {
        return lazyLoadConfig;
    }

    public List getSequencingAttributes()
    {
        return activateOrdering;
    }

    /**
     * Add the AttributeDef to the list of ordering attributes in the position
     * 'position'.  Note, position is 1-based.
     *
     * @param attributeDef
     * @param position
     */
    void addSequencingAttribute( AttributeDef attributeDef, int position )
    {
        if ( activateOrdering == null )
            activateOrdering = new ArrayList();

        while ( activateOrdering.size() < position )
            activateOrdering.add( null );

        activateOrdering.set( position - 1, attributeDef );
    }

    /**
     * @return the activateLimit
     */
    public Integer getActivateLimit()
    {
        return activateLimit;
    }

    public SourceFileType getSourceFileType()
    {
        return sourceFileType;
    }

    public String getSourceFileName()
    {
        return sourceFileName;
    }

    public String getConstraintOper()
    {
        return constraintOper;
    }

    public boolean hasCancelConstraint()
    {
        return hasCancelConstraint;
    }

    public void setHasCancelConstraint( boolean hasCancelConstraint )
    {
        this.hasCancelConstraint = hasCancelConstraint;
    }

    public boolean hasAcceptConstraint()
    {
        return hasAcceptConstraint;
    }

    public void setHasAcceptConstraint( boolean hasAcceptConstraint )
    {
        this.hasAcceptConstraint = hasAcceptConstraint;
    }

    public boolean hasExcludeConstraint()
    {
        return hasExcludeConstraint;
    }

    public void setHasExcludeConstraint( boolean hasExcludeConstraint )
    {
        this.hasExcludeConstraint = hasExcludeConstraint;
    }

    public boolean hasIncludeConstraint()
    {
        return hasIncludeConstraint;
    }

    public void setHasIncludeConstraint( boolean hasIncludeConstraint )
    {
        this.hasIncludeConstraint = hasIncludeConstraint;
    }

    public boolean hasDeleteConstraint()
    {
        return hasDeleteConstraint;
    }

    public void setHasDeleteConstraint( boolean hasDeleteConstraint )
    {
        this.hasDeleteConstraint = hasDeleteConstraint;
    }

    public boolean hasCreateConstraint()
    {
        return hasCreateConstraint;
    }

    public void setHasCreateConstraint( boolean hasCreateConstraint )
    {
        this.hasCreateConstraint = hasCreateConstraint;
    }

    /**
     * Execute the entity constraint for the view.
     *
     * @param view
     * @param type
     * @return
     */
    public int executeEntityConstraint( View view, EntityConstraintType type )
    {
        switch ( sourceFileType )
        {
            case VML:
                return executeVmlConstraint( view, type );

            case SCALA:
                return executeScalaConstraint( view, type );

            case JAVA:
            default:
                throw new ZeidonException( "Unsupported Entity Constraint SourceFileType: %s", type );
        }
    }

    static private final Class[] VML_CONSTRUCTOR_ARG_TYPES  = new Class[] { View.class };
    static private final Class[] VML_ARGUMENT_TYPES = new Class[] { View.class, String.class, Integer.class, Integer.class };

    private int executeVmlConstraint( View view, EntityConstraintType type )
    {
        ObjectEngine oe = view.getObjectEngine();
        String className = getSourceFileName();
        try
        {
            ClassLoader classLoader = oe.getClassLoader( className );
            Class operationsClass;
            operationsClass = classLoader.loadClass( className );
            Constructor constructor = operationsClass.getConstructor( VML_CONSTRUCTOR_ARG_TYPES );
            Object object = constructor.newInstance( view );
            Method method = object.getClass().getMethod( getConstraintOper(), VML_ARGUMENT_TYPES );
            return (Integer) method.invoke( object, view, this.getName(), type.toInt(), 0 );
        }
        catch ( Exception e )
        {
            throw ZeidonException.wrapException( e )
                                 .prependEntityDef( this )
                                 .appendMessage( "EntityConstraint class = %s", className )
                                 .appendMessage( "Constraint oper = %s", getConstraintOper() )
                                 .appendMessage( "See inner exception for more info." );
        }
    }

    private int executeScalaConstraint( View view, EntityConstraintType type )
    {
        try
        {
            return view.getTask().getScalaHelper().executeEntityConstraint( view, this, type );
        }
        catch ( Exception e )
        {
            if ( e instanceof InvocationTargetException )
                throw ZeidonException.wrapException( ((InvocationTargetException) e).getTargetException() );
            else
                throw ZeidonException.wrapException( e );
        }
    }

    /**
     * Returns true if this entity can activating by getting the attribute data
     * from the parent instead of going to the DB.   This occurs when the entity
     * has only keys and the foreign keys are in the parent.
     *
     * @return true if this entity can be autoloaded from parent.
     */
    public boolean isAutoloadFromParent()
    {
        return autoloadFromParent;
    }

    /**
     * Checks to see if all the attributes in targetEi as sourceEi.
     *
     * @return null if all attributes exist otherwise AttributeDef of first missing attribute found.
     */
    private AttributeDef checkForAllPersistentAttributes( EntityDef source, EntityDef target )
    {
        for ( AttributeDef attr : target.getAttributes() )
        {
            if ( ! attr.isActivate() )
                continue;

            // It's ok if autoseq is missing because it's maintained by the relationship.
            if ( attr.isAutoSeq() )
                continue;

            // Check to see if the attribute exists in the source.
            if ( source.getAttribute( attr.getName(), false ) == null )
                return attr;
        }

        return null;
    }

    /**
     * Validate that the 'source' EntityDef can be linked with 'this'.
     *
     * @param source
     * @return
     */
    public LinkValidation validateLinking( EntityDef source )
    {
        assert getErEntityToken() == source.getErEntityToken() : "Trying to link mismatching ER tokens";

        if ( this == source )
            return LinkValidation.SOURCE_OK;

        AttributeDef missingAttributeDef = null;

        // If they have the same ER date we'll assume all is good.
        if ( source.getLodDef().getErDate().equals( getLodDef().getErDate() ) )
            return LinkValidation.SOURCE_OK;

        // Check to see if it's ok for target to be linked to source.
        EntityDefLinkInfo sourceInfo = getLinkInfo( source );
        Boolean sourceOk = sourceInfo.mayBeLinked.get( this );
        if ( sourceOk == Boolean.TRUE )
            return LinkValidation.SOURCE_OK;

        /*  This leads to a NPE.  Some day we may try to fix it.
        // Check to see if source can be linked to 'this'.
        EntityDefLinkInfo targetInfo = getEntityDefLinkInfo( entityDef );
        Boolean targetOk = targetInfo.mayBeLinked.get( entityDef );
        if ( targetOk == Boolean.TRUE )
        {
            source.attributeList = AttributeListInstance.newSharedAttributeList( source, source.attributeList,
                                                                                 this, this.attributeList );
            return;
        }
        */

        if ( sourceOk == null ) // If it's null we've never checked this one.
        {
            missingAttributeDef = checkForAllPersistentAttributes( source, this );
            sourceOk = missingAttributeDef == null;
            sourceInfo.mayBeLinked.putIfAbsent( this, sourceOk );
            if ( sourceOk == Boolean.TRUE )
                return LinkValidation.SOURCE_OK;
        }

        /*  This leads to a NPE.  Some day we may try to fix it.
        if ( targetOk == null ) // If it's null we've never checked this one.
        {
            missingAttributeDef = checkForAllPersistentAttributes( source, this );
            targetOk = missingAttributeDef == null;
            targetInfo.mayBeLinked.putIfAbsent( sourceEntityDef, targetOk );
            if ( targetOk == Boolean.TRUE )
            {
                source.attributeList = AttributeListInstance.newSharedAttributeList( source, source.attributeList,
                                                                                     this, this.attributeList );
                return;
            }
        }
        */

        ZeidonException ex = new ZeidonException( "Attempting to link instances that don't have matching attributes.  "
                                                + "You probably need to re-save the target LOD." );
        ex.appendMessage( "Source instance = %s", source );
        ex.appendMessage( "Target instance type = %s", this );
        ex.appendMessage( "Missing attribute = %s.%s.%s",
                          missingAttributeDef.getEntityDef().getLodDef().getName(),
                          missingAttributeDef.getEntityDef().getName(),
                          missingAttributeDef.getName() );

        throw ex;
    }

    private synchronized EntityDefLinkInfo getLinkInfo( EntityDef entityDef )
    {
        if ( linkInfo == null )
            linkInfo = new EntityDefLinkInfo();

        return linkInfo;
    }

    public AttributeDef getDbCreatedTimestamp()
    {
        return dbCreatedTimestamp;
    }

    void setDbCreatedTimestamp( AttributeDef dbCreatedTimestamp )
    {
        this.dbCreatedTimestamp = dbCreatedTimestamp;
    }

    public AttributeDef getDbUpdatedTimestamp()
    {
        return dbUpdatedTimestamp;
    }

    void setDbUpdatedTimestamp( AttributeDef dbUpdatedTimestamp )
    {
        this.dbUpdatedTimestamp = dbUpdatedTimestamp;
    }

    public DataField getVersioningDataField()
    {
        return versioningDataField;
    }

    public enum LinkValidation
    {
        SOURCE_OK;
    }

    /**
     * This keeps track of whether two view entities can be validly linked together.
     */
    private class EntityDefLinkInfo
    {
        public final ConcurrentMap mayBeLinked = new MapMaker().concurrencyLevel( 4 ).makeMap();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy