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

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

The newest version!
/**
    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.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;

import com.quinsoft.zeidon.Application;
import com.quinsoft.zeidon.AttributeInstance;
import com.quinsoft.zeidon.ObjectEngine;
import com.quinsoft.zeidon.Task;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.domains.Domain;
import com.quinsoft.zeidon.standardoe.ScalaHelper;
import com.quinsoft.zeidon.utils.PortableFileReader;
import com.quinsoft.zeidon.utils.PortableFileReader.PortableFileAttributeHandler;

/**
 * @author DG
 *
 */
public class AttributeDef implements PortableFileAttributeHandler, Serializable
{
    private static final long serialVersionUID = 1L;
    private final EntityDef   entityDef;
    private String       name;
    private String       erAttributeToken;
    private InternalType type = InternalType.STRING;
    private Integer      length;
    private String       xmlName;
    private String       xmlExtName;

    /**
     * Used to match up DATARECORDS with CHILDENTITY lines in the XOD.
     */
    private int          xvaAttrToken;
    private String       initialValue;

    // Derived operation fields.
    public static final int DERIVED_SET = 1;
    public static final int DERIVED_GET = 2;

    // Following fields are protected instead of private to allow for quicker access when
    // reference by inner classes.
    protected String          derivedOperationName;
    protected String          derivedOperationClassName;
    private   SourceFileType  derivedOperationsourceFileType = SourceFileType.VML;

    private AttributeHashKeyType hashKeyType = AttributeHashKeyType.NONE;

    /**
     * The unique number for the entity which is the index of the attribute for all
     * attributes.
     */
    private final int    attributeNumber;
    private boolean      hidden;
    private boolean      persistent;
    private boolean      activate;
    private boolean      key;
    private boolean      foreignKey;
    private boolean      autoSeq;
    private boolean      genKey;
    private boolean      update;
    private boolean      required;
    private boolean      debugChange;
    private Domain       domain;
    private EntityDef    hashKeyParent;
    private Boolean      isSequencingAscending = Boolean.TRUE;

    private boolean      isCaseSensitive = false;

    /**
     * If true then this attribute was created at runtime via EntityDef.createDynamicAttributeDef.
     */
    private boolean isDynamicAttribute = false;
    private DerivedOper derivedOperation;

    public AttributeDef(EntityDef entityDef)
    {
        super();
        this.entityDef = entityDef;
        attributeNumber = entityDef.getAttributeCount();
    }

    /**
     * Create a dynamic attribute.
     *
     * @param entityDef
     * @param config
     */
    public AttributeDef(EntityDef entityDef, DynamicAttributeDefConfiguration config )
    {
        this( entityDef );
        setName( config.getAttributeName() );
        setDomain( config.getDomainName() );
        setDynamicAttribute( true );

        erAttributeToken = ( entityDef.toString() + "." + getName() ).intern() ;
    }

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

        switch ( attributeName )
        {
            case "AUTO_SEQ":
                autoSeq = true;
                entityDef.setAutoSeq( this );
                break;

            case "CASESENS":
                isCaseSensitive = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            // DERIVEDC is the name of the Java class that declares the derived function.
            // Java only (e.g. not in the C OE).
            case "DERIVEDC":
                derivedOperationClassName = reader.getAttributeValue().intern();
                if ( ! derivedOperationClassName.contains( "." ) )
                    derivedOperationClassName = getApplication().getPackage() + "." + derivedOperationClassName;
                break;

            case "DERIVEDF":
                derivedOperationName = reader.getAttributeValue().intern();
                break;

            case "DRSRCTYPE":
                derivedOperationsourceFileType = SourceFileType.parse( reader.getAttributeValue() );
                break;

            case "DOMAIN":
                setDomain( reader.getAttributeValue().intern() );
                break;

            case "DEBUGCHG":
                debugChange = StringUtils.startsWithIgnoreCase( reader.getAttributeValue(), "Y" );
                break;

            case "ERATT_TOK":
                erAttributeToken = reader.getAttributeValue().intern();
                break;

            case "FORKEY":
                foreignKey = reader.getAttributeValue().toUpperCase().startsWith( "Y" );
                break;

            case "GENKEY":
                genKey = true;
                entityDef.setGenKey( this );
                entityDef.getLodDef().setHasGenKey( true );
                break;

            case "HASHKEY":
                if ( hashKeyParent == null )
                    hashKeyParent = getEntityDef().getParent();

                hashKeyType = AttributeHashKeyType.valueOf( reader.getAttributeValue() );
                if ( hashKeyType != AttributeHashKeyType.NONE )
                    entityDef.addHashKeyAttribute( this );
                break;

            case "HASHKEY_PARENT":
                String entityName = reader.getAttributeValue();
                for ( hashKeyParent = getEntityDef().getParent();
                      hashKeyParent != null;
                      hashKeyParent = hashKeyParent.getParent() )
                {
                    if ( hashKeyParent.getName().equals( entityName ) )
                        break;
                }

                if ( hashKeyParent == null )
                    throw new ZeidonException("Unknown hashkey parent %s", entityName );
                break;

            case "HIDDEN":
                hidden = reader.getAttributeValue().toUpperCase().startsWith( "Y" );
                break;

            case "INIT":
                initialValue = reader.getAttributeValue();
                entityDef.setHasInitializedAttributes( true );
                break;

            case "KEY":
                key = true;
                entityDef.addKey( this );
                break;

            case "LTH":
                length = Integer.parseInt( reader.getAttributeValue() );
                break;

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

            case "XML_SIMPLE_NAME":
                xmlName = reader.getAttributeValue().intern();
                break;

            case "XML_NAME_EXTERNAL":
                xmlExtName = reader.getAttributeValue().intern();
                break;

            case "PERSIST":
                persistent = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "REQUIRED":
                required = reader.getAttributeValue().startsWith( "Y" );
                break;

            case "SEQUENCING":
                int position = Integer.parseInt( reader.getAttributeValue() );

                // Find the first parent that can have multiple children.  If a parent has
                // max cardinality of 1 then it can't be ordered.
                EntityDef search = entityDef;
                while ( search.getMaxCardinality() == 1 )
                    search = search.getParent();

                search.addSequencingAttribute( this, position );
                break;

            case "SEQ_AD":
                isSequencingAscending = reader.getAttributeValue().toUpperCase().startsWith( "A" );
                break;

            case "TYPE":
                type = InternalType.mapCode( reader.getAttributeValue() );
                break;

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

            case "XVAATT_TOK":
                xvaAttrToken = Integer.parseInt( reader.getAttributeValue() );
                break;
        }
    }

    /**
     * Perform any final initializing that can only be done after the attribute
     * has been loaded.
     */
    void finishAttributeLoading()
    {
        // If this attribute is automatically set by the OE then it isn't required to be
        // set before commit.
        if ( entityDef.getDbCreatedTimestamp() == this || entityDef.getDbUpdatedTimestamp() == this )
        {
            required = false;

            // If the entity is updatable then we'll force this attribute to be updateable even
            // if it's a hidden attribute.  This will allow us to update it later.
            if ( entityDef.isUpdate() || entityDef.isCreate() )
            {
                update = true;
            }
        }

        // Persistent, non-hidden attributes should always be activated.
        setActivate( persistent && ( ! hidden || isKey() || isForeignKey() || isAutoSeq() ) );
    }

    private Application getApplication()
    {
        return getEntityDef().getLodDef().getApplication();
    }

    AttributeDef setDomain( String domainName )
    {
        Application app = entityDef.getLodDef().getApplication();
        domain = app.getDomain( domainName );
        return this;
    }

    public EntityDef getEntityDef()
    {
        return entityDef;
    }

    private AttributeDef setName( String name )
    {
        this.name = name;

        switch ( name.toLowerCase() )
        {
            case "dbcreatedtimestamp":
                entityDef.setDbCreatedTimestamp( this );
                break;

            case "dbupdatedtimestamp":
                entityDef.setDbUpdatedTimestamp( this );
                break;
        }

        return this;
    }

    public String getName()
    {
        return name;
    }

    public String getErAttributeToken()
    {
        return erAttributeToken;
    }

    public InternalType getType()
    {
        return type;
    }

    /**
     * @return Returns max length of this attribute (if applicable). May return null
     *          in which case the max length defaults to the domain definition.
     */
    public Integer getLength()
    {
        return length;
    }

    AttributeDef setLength( int length )
    {
        this.length = length;
        return this;
    }

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

    int getXvaAttrToken()
    {
        return xvaAttrToken;
    }

    public boolean isHidden()
    {
        return hidden;
    }

    public boolean isPersistent()
    {
        return persistent;
    }

    /**
     * Return true if this attribute should be loaded from the DB.  This should
     * be true for all persistent, non-hidden attributes.  It may also be true
     * for hidden attributes if this entity is expected to be linked to other
     * instances.  This allows the DB handler to load attributes that might
     * be hidden in this entity but non-hidden in the linked instance.
     *
     * @return
     */
    public boolean isActivate()
    {
        return activate;
    }

    void setActivate( boolean act )
    {
        assert act ? persistent : true : "Internal error: attributes with activate flag must be persistent.";
        activate = act;
    }

    public boolean isRequired()
    {
        return required;
    }

    public int getAttributeNumber()
    {
        return attributeNumber;
    }

    public String getDerivedOperationClassName()
    {
        return derivedOperationClassName;
    }

    public String getDerivedOperationName()
    {
        return derivedOperationName;
    }

    private synchronized DerivedOper getDerivedOperation( Task task )
    {
        if ( derivedOperation != null )
            return derivedOperation;

        try
        {
            switch ( derivedOperationsourceFileType )
            {
                case VML:
                    derivedOperation = new VmlDerivedOper( task );
                    break;

                case JAVA:
                    derivedOperation = new JavaDerivedOper( task );
                    break;

                case SCALA:
                    derivedOperation = new ScalaDerivedOper( task );
                    break;

                default:
                    throw new ZeidonException( "Unsupported source file type for derived operations: %s",
                                               derivedOperationsourceFileType );
            }


            return derivedOperation;
        }
        catch( Exception e )
        {
            throw ZeidonException.prependMessage( e, "Error loading Derived operation '%s.%s' for %s",
                                                  derivedOperationClassName, derivedOperationName, toString() );
        }
    }

    public boolean isKey()
    {
        return key;
    }

    public boolean isForeignKey()
    {
        return foreignKey;
    }

    public boolean isAutoSeq()
    {
        return autoSeq;
    }

    public boolean isGenKey()
    {
        return genKey;
    }

    public boolean isUpdate()
    {
        return update;
    }

    public boolean isDerived()
    {
        return derivedOperationClassName != null;
    }

    public boolean isDebugChange()
    {
        return debugChange;
    }

    public AttributeDef setDebugChange( boolean c )
    {
        debugChange = c;
        return this;
    }

    public Domain getDomain()
    {
        return domain;
    }

    /**
     * This calls the derived attribute function to set the internal attribute value.
     * The attribute value when then be retrieved by a get-attr method.
     *
     * @param attributeInstance
     */
    public void executeDerivedAttributeForGet( AttributeInstance attributeInstance )
    {
        getDerivedOperation( attributeInstance.getTask() ).getDerivedValue( attributeInstance );
    }

    static private final Class[] ARGUMENT_TYPES_VML     = new Class[] { View.class, String.class, String.class, Integer.class };
    static private final Class[] ARGUMENT_TYPES_JAVA    = new Class[] { AttributeInstance.class };
    static private final Class[] CONSTRUCTOR_ARG_VML    = new Class[] { View.class };
    static private final Class[] CONSTRUCTOR_ARG_JAVA   = new Class[] { };

    /**
     * Call a derived operation.
     *
     * @author dg
     *
     */
    private abstract class DerivedOper
    {
        protected final Method method;
        protected final Constructor constructor;

        private DerivedOper( Task task, Class[] constructorArgs, Class[] argumentTypes ) throws Exception
        {
            if ( constructorArgs != null )
            {
                ObjectEngine oe = task.getObjectEngine();
                ClassLoader classLoader = oe.getClassLoader( derivedOperationClassName );
                Class operationsClass;
                operationsClass = classLoader.loadClass( derivedOperationClassName );

                constructor = operationsClass.getConstructor( constructorArgs );
                method = operationsClass.getMethod( derivedOperationName, argumentTypes );
            }
            else
            {
                constructor = null;
                method = null;
            }
        }

        private void getDerivedValue( AttributeInstance attributeInstance )
        {
            callDerivedOperation( attributeInstance );
        }

        abstract void callDerivedOperation( AttributeInstance attributeInstance );

        @Override
        public String toString()
        {
            return derivedOperationClassName + "." + derivedOperationName;
        }
    }

    /**
     * Call a derived operation as it's defined in Java generated from VML.
     */
    private class VmlDerivedOper extends DerivedOper
    {
        private VmlDerivedOper( Task task ) throws Exception
        {
            super( task, CONSTRUCTOR_ARG_VML, ARGUMENT_TYPES_VML );
        }

        @Override
        void callDerivedOperation( AttributeInstance attributeInstance )
        {
            try
            {
                View view = attributeInstance.getView();
                Object oper = constructor.newInstance( view );
                Object[] argList = new Object[] { view, getEntityDef().getName(), getName(), DERIVED_GET };
                method.invoke( oper, argList );
            }
            catch ( InvocationTargetException e )
            {
                Throwable target = e.getTargetException();
                if ( target instanceof ZeidonException )
                    throw (ZeidonException) target;

                target = e.getCause();
                if ( target instanceof ZeidonException )
                    throw (ZeidonException) target;

                throw ZeidonException.prependMessage( target, "Oper: %s", this )
                                     .prependAttributeDef( AttributeDef.this  );
            }
            catch ( Throwable e )
            {
                throw ZeidonException.prependMessage( e, "Oper: %s", this )
                                     .prependAttributeDef( AttributeDef.this  );
            }
        }
    }

    /**
     * Call a derived operation as it's defined in hand-coded Java.
     */
    private class JavaDerivedOper extends DerivedOper
    {
        private JavaDerivedOper( Task task ) throws Exception
        {
            super( task, CONSTRUCTOR_ARG_JAVA, ARGUMENT_TYPES_JAVA );
        }

        @Override
        void callDerivedOperation( AttributeInstance attributeInstance )
        {
            try
            {
                Object oper = constructor.newInstance( );
                Object[] argList = new Object[] { attributeInstance };
                method.invoke( oper, argList );
            }
            catch ( Throwable e )
            {
                throw ZeidonException.prependMessage( e, "Oper: %s", this )
                                     .prependAttributeDef( AttributeDef.this  );
            }
        }
    }

    /**
     * Call a derived operation using the ScalaHelper.
     *
     */
    private class ScalaDerivedOper extends DerivedOper
    {
        private ScalaDerivedOper( Task task ) throws Exception
        {
            super( task, null, null );
        }

        @Override
        void callDerivedOperation( AttributeInstance attributeInstance )
        {
            try
            {
                ScalaHelper scalaHelper = attributeInstance.getTask().getScalaHelper();
                scalaHelper.calculateDerivedAttribute( attributeInstance );
            }
            catch ( Throwable e )
            {
                throw ZeidonException.prependMessage( e, "Oper: %s", this )
                                     .prependAttributeDef( AttributeDef.this  );
            }
        }
    }

    public AttributeDef getNextAttributeDef()
    {
        int idx = getAttributeNumber() + 1;
        if ( idx == entityDef.getAttributeCount() )
            return null;

        return entityDef.getAttribute( idx );
    }

    /**
     * @return the initialValue
     */
    public String getInitialValue()
    {
        return initialValue;
    }

    /**
     * @return the hashKeyType
     */
    public AttributeHashKeyType getHashKeyType()
    {
        return hashKeyType;
    }

    /**
     * For attributes that are a hashkey under a parent, this returns the parent.
     * Otherwise returns null.
     *
     * @return
     */
    public EntityDef getHashKeyParent()
    {
        return hashKeyParent;
    }

    /**
     * Returns true if this AttributeDef is a sequencing attribute in ascending order, false
     * if it's descending, and null if it's not a sequencing attribute.  See
     * EntityDef.getSequencingAttributes() for more.
     *
     * @return
     */
    public Boolean isSequencingAscending()
    {
        return isSequencingAscending;
    }

    public boolean isDynamicAttribute()
    {
        return isDynamicAttribute;
    }

    void setDynamicAttribute( boolean isDynamicAttribute )
    {
        this.isDynamicAttribute = isDynamicAttribute;
    }

    public boolean isCaseSensitive()
    {
        return isCaseSensitive;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy