EJB3AssociationEndFacadeLogicImpl.java

package org.andromda.cartridges.ejb3.metafacades;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;
import org.andromda.cartridges.ejb3.EJB3Globals;
import org.andromda.cartridges.ejb3.EJB3Profile;
import org.andromda.metafacades.uml.AssociationEndFacade;
import org.andromda.metafacades.uml.AttributeFacade;
import org.andromda.metafacades.uml.ClassifierFacade;
import org.andromda.metafacades.uml.Entity;
import org.andromda.metafacades.uml.EntityMetafacadeUtils;
import org.andromda.metafacades.uml.MetafacadeUtils;
import org.andromda.metafacades.uml.ModelElementFacade;
import org.andromda.metafacades.uml.TaggedValueFacade;
import org.andromda.metafacades.uml.TypeMappings;
import org.andromda.metafacades.uml.UMLMetafacadeProperties;
import org.andromda.metafacades.uml.UMLMetafacadeUtils;
import org.andromda.metafacades.uml.UMLProfile;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;

/**
 * <p/>
 * Represents an EJB association end. </p>
 * MetafacadeLogic implementation for org.andromda.cartridges.ejb3.metafacades.EJB3AssociationEndFacade.
 *
 * @see EJB3AssociationEndFacade
 */
public class EJB3AssociationEndFacadeLogicImpl
    extends EJB3AssociationEndFacadeLogic
{
    private static final long serialVersionUID = 34L;
    /**
     * The default composite association cascade property
     */
    private static final String ENTITY_DEFAULT_COMPOSITE_CASCADE = "entityCompositeCascade";

    /**
     * The default aggregation association cascade property
     */
    private static final String ENTITY_DEFAULT_AGGREGATION_CASCADE = "entityAggregationCascade";

    /**
     * The namespace property storing default collection type for associations
     */
    private static final String ASSOCIATION_COLLECTION_TYPE = "associationCollectionType";

    /**
     * A flag indicating whether or not specific (java.util.Set, java.util.List,
     * etc) collection interfaces should be used in assocation mutators and
     * accessors or whether the generic java.util.Collection interface should be
     * used.
     */
    private static final String SPECIFIC_COLLECTION_INTERFACES = "specificCollectionInterfaces";

    /**
     * The property that defines the default collection interface, this is the
     * interface used if the property defined by
     * {@link #SPECIFIC_COLLECTION_INTERFACES} is false.
     */
    private static final String DEFAULT_COLLECTION_INTERFACE = "defaultCollectionInterface";

    /**
     * Stores the default collection index name.
     */
    private static final String COLLECTION_INDEX_NAME = "associationEndCollectionIndexName";

    /**
     * Stores the default collection index type.
     */
    private static final String COLLECTION_INDEX_TYPE = "associationEndCollectionIndexType";

    /**
     * Represents the EJB3 <code>ALL</code> cascade option and fully qualified representation.
     */
    private static final String ENTITY_CASCADE_ALL = "ALL";
    private static final String ENTITY_CASCADE_ALL_FQN = "javax.persistence.CascadeType.ALL";

    /**
     * Represents the EJB3 <code>PERSIST</code> cascade option.
     */
    private static final String ENTITY_CASCADE_PERSIST = "PERSIST";
    private static final String ENTITY_CASCADE_PERSIST_FQN = "javax.persistence.CascadeType.PERSIST";

    /**
     * Represents the EJB3 <code>MERGE</code> cascade option.
     */
    private static final String ENTITY_CASCADE_MERGE = "MERGE";
    private static final String ENTITY_CASCADE_MERGE_FQN = "javax.persistence.CascadeType.MERGE";

    /**
     * Represents the EJB3 <code>REMOVE</code> cascade option.
     */
    private static final String ENTITY_CASCADE_REMOVE = "REMOVE";
    private static final String ENTITY_CASCADE_REMOVE_FQN = "javax.persistence.CascadeType.REMOVE";

    /**
     * Represents the EJB3 <code>REFRESH</code> cascade option.
     */
    private static final String ENTITY_CASCADE_REFRESH = "REFRESH";
    private static final String ENTITY_CASCADE_REFRESH_FQN = "javax.persistence.CascadeType.REFRESH";

    /**
     * Represents the value used to represents NO cascade option.
     */
    private static final String ENTITY_CASCADE_NONE = "NONE";

    /**
     * Stores the cascade map of fully qualified cascade types
     */
    private static final Map<String, String> cascadeTable = new Hashtable<String, String>();

    static
    {
        cascadeTable.put(ENTITY_CASCADE_ALL, ENTITY_CASCADE_ALL_FQN);
        cascadeTable.put(ENTITY_CASCADE_PERSIST, ENTITY_CASCADE_PERSIST_FQN);
        cascadeTable.put(ENTITY_CASCADE_MERGE, ENTITY_CASCADE_MERGE_FQN);
        cascadeTable.put(ENTITY_CASCADE_REMOVE, ENTITY_CASCADE_REMOVE_FQN);
        cascadeTable.put(ENTITY_CASCADE_REFRESH, ENTITY_CASCADE_REFRESH_FQN);
    }

    /**
     * Value for set
     */
    private static final String COLLECTION_TYPE_SET = "set";

    /**
     * Value for map
     */
    private static final String COLLECTION_TYPE_MAP = "map";

    /**
     * Value for list
     */
    private static final String COLLECTION_TYPE_LIST = "list";

    /**
     * Value for collections
     */
    private static final String COLLECTION_TYPE_COLLECTION = "bag";

    /**
     * Stores the valid collection types
     */
    private static final Collection<String> collectionTypes = new ArrayList<String>();

    static
    {
        collectionTypes.add(COLLECTION_TYPE_SET);
        collectionTypes.add(COLLECTION_TYPE_MAP);
        collectionTypes.add(COLLECTION_TYPE_LIST);
        collectionTypes.add(COLLECTION_TYPE_COLLECTION);
    }

    /**
     * Stores the property indicating whether or not composition should define
     * the eager loading strategy and aggregation define lazy loading strategy.
     */
    private static final String COMPOSITION_DEFINES_EAGER_LOADING = "compositionDefinesEagerLoading";

    /**
     * The property that stores whether relationship collection caching is enabled.
     */
    private static final String HIBERNATE_ASSOCIATION_ENABLE_CACHE = "hibernateEnableAssociationsCache";

    /**
     * Stores the default cache strategy for relationship Collections.
     */
    private static final String HIBERNATE_ASSOCIATION_CACHE = "hibernateAssociationCache";

    /**
     * The 'list' type implementation to use.
     */
    private static final String LIST_TYPE_IMPLEMENTATION = "listTypeImplementation";

    /**
     * The 'set' type implementation to use.
     */
    private static final String SET_TYPE_IMPLEMENTATION = "setTypeImplementation";

    /**
     * The 'map' type implementation to use.
     */
    private static final String MAP_TYPE_IMPLEMENTATION = "mapTypeImplementation";

    // ---------------- constructor -------------------------------

    /**
     * @param metaObject
     * @param context
     */
    public EJB3AssociationEndFacadeLogicImpl(final Object metaObject, final String context)
    {
        super (metaObject, context);
    }

    // --------------- methods ---------------------

    /**
     * @see AssociationEndFacade#getGetterSetterTypeName()
     */
    @Override public String getGetterSetterTypeName()
    {
        String getterSetterTypeName = null;

        if (this.isMany())
        {
            final boolean specificInterfaces =
                Boolean.valueOf(
                    ObjectUtils.toString(this.getConfiguredProperty(SPECIFIC_COLLECTION_INTERFACES))).booleanValue();

            final TypeMappings mappings = this.getLanguageMappings();
            if (mappings != null)
            {
                if (this.isMap())
                {
                    getterSetterTypeName = mappings.getTo(UMLProfile.MAP_TYPE_NAME);
                }
                else if (specificInterfaces)
                {
                    if (this.isSet())
                    {
                        getterSetterTypeName = mappings.getTo(UMLProfile.SET_TYPE_NAME);
                    }
                    else if (this.isList())
                    {
                        getterSetterTypeName = mappings.getTo(UMLProfile.LIST_TYPE_NAME);
                    }
                    else if (this.isCollection())
                    {
                        getterSetterTypeName = mappings.getTo(UMLProfile.COLLECTION_TYPE_NAME);
                    }
                }
                else
                {
                    getterSetterTypeName = this.getDefaultCollectionInterface();
                }
            }
            else
            {
                getterSetterTypeName = this.getDefaultCollectionInterface();
            }
        }
        else
        {
            final ClassifierFacade type = this.getType();
            if (type instanceof EJB3EntityFacade)
            {
                final String typeName = ((EJB3EntityFacade)type).getFullyQualifiedEntityName();
                if (StringUtils.isNotBlank(typeName))
                {
                    getterSetterTypeName = typeName;
                }
            }
        }

        if (getterSetterTypeName == null)
        {
            getterSetterTypeName = super.getGetterSetterTypeName();
        }
        else if (this.isMany())
        {
            /**
             * Set this association end's type as a template parameter if required
             */
            if ("true".equals(this.getConfiguredProperty(UMLMetafacadeProperties.ENABLE_TEMPLATING)))
            {
                getterSetterTypeName += '<'
                    + (this.isMap() ? this.getCollectionIndexType() + ", " : "")
                    + this.getType().getFullyQualifiedName() + '>';
            }
        }
        return getterSetterTypeName;
    }

    /**
     * Overridden to provide handling of inheritance.
     *
     * @see AssociationEndFacade#isRequired()
     */
    @Override
    public boolean isRequired()
    {
        // TODO: Fix UML Association isRequired to use getOtherEnd
        boolean required = super.isRequired();
        Object type = this.getOtherEnd().getType();

        if ((type != null) && EJB3EntityFacade.class.isAssignableFrom(type.getClass()))
        {
            EJB3EntityFacade entity = (EJB3EntityFacade)type;

            /**
             * Exclude ONLY if single table inheritance exists
             */
            if (entity.isRequiresGeneralizationMapping() && entity.isInheritanceSingleTable()
                    && !entity.isEmbeddableSuperclassGeneralizationExists())
            {
                required = false;
            }
        }

        return required;
    }

    /**
     * @return fetch type
     * @see EJB3AssociationEndFacade#getFetchType()
     */
    @Override
    protected String handleGetFetchType()
    {
        String fetchType = (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_FETCH_TYPE);

        if (StringUtils.isBlank(fetchType))
        {
            // check whether or not composition defines eager loading is turned on
            final boolean compositionDefinesEagerLoading =
                Boolean.valueOf(String.valueOf(this.getConfiguredProperty(COMPOSITION_DEFINES_EAGER_LOADING)))
                       .booleanValue();

            if (compositionDefinesEagerLoading)
            {
                if (this.getOtherEnd().isComposition())
                {
                    fetchType = EJB3Globals.FETCH_TYPE_EAGER;
                }
                else if (this.getOtherEnd().isAggregation())
                {
                    fetchType = EJB3Globals.FETCH_TYPE_LAZY;
                }
            }
        }

        /**
         * Go for defaults if blank
         */
        if (StringUtils.isBlank(fetchType))
        {
            if (this.getOtherEnd().isOne2Many() || this.getOtherEnd().isMany2Many())
            {
                fetchType = EJB3Globals.FETCH_TYPE_LAZY;
            }
            else
            {
                fetchType = EJB3Globals.FETCH_TYPE_EAGER;
            }
        }
        return fetchType;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsEager()
     */
    @Override
    protected boolean handleIsEager()
    {
        boolean isEager = false;
        if (StringUtils.isNotBlank(this.getFetchType()))
        {
            if (EJB3Globals.FETCH_TYPE_EAGER.equalsIgnoreCase(this.getFetchType()))
            {
                isEager = true;
            }
        }
        return isEager;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsLazy()
     */
    @Override
    protected boolean handleIsLazy()
    {
        boolean isLazy = false;
        if (StringUtils.isNotBlank(this.getFetchType()))
        {
            if (EJB3Globals.FETCH_TYPE_LAZY.equalsIgnoreCase(this.getFetchType()))
            {
                isLazy = true;
            }
        }
        return isLazy;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsOwning()
     */
    @Override
    protected boolean handleIsOwning()
    {
        return !UMLMetafacadeUtils.isOwningEnd(this);
        /*boolean owning = false;
        if (this.isAggregation() || this.isComposition() || !this.getOtherEnd().isNavigable()
            || BooleanUtils.toBoolean(
                ObjectUtils.toString(this.findTaggedValue(
                        EJB3Profile.TAGGEDVALUE_PERSISTENCE_ASSOCIATION_END_PRIMARY))))
        {
            owning = true;
        }
        // See if this end or the other end is tagged as the association owner
        else if (BooleanUtils.toBoolean(
            ObjectUtils.toString(this.getOtherEnd().findTaggedValue(
                    EJB3Profile.TAGGEDVALUE_PERSISTENCE_ASSOCIATION_END_PRIMARY))))
        {
            owning = false;
        }
        // If other end not tagged, the many side of 1:M owns the bidirectional relationship
        else if (this.isMany() && !this.getOtherEnd().isMany())
        {
            owning = true;
        }
        // Other side: aggregation/composition owns the bidirectional relationship
        else if (this.getOtherEnd().isAggregation() || this.getOtherEnd().isComposition())
        {
            owning = false;
        }
        // Other side: the many side of 1:M owns the bidirectional relationship if no composition/aggregation
        else if (!this.isMany() && this.getOtherEnd().isMany())
        {
            owning = false;
        }
        // If bidirectional 1:1 or M:M, choose the side with the longest type name because it typically indicates a composition relationship
        //else if (this.getOtherEnd().getType().getName().length()
        //        > this.getType().getName().length())
        else if (this.getName().length()
            > this.getOtherEnd().getName().length())
        {
            owning = true;
        }
        // If length is the same, alphabetically earliest is the owner
        else if (this.getName().compareTo(
                this.getOtherEnd().getName()) < 0)
        {
            owning = true;
        }
        //System.out.println("handleIsOwning=" + owning + " for " + this.getName() + " OName=" + this.getOtherEnd().getName() + " Aggregation=" + this.isAggregation() + " Aggregation=" + this.isAggregation() + " Composition=" + this.isComposition() + " !Navigable=" + !this.isNavigable() + " Many=" + this.isMany() + " OMany=" + this.getOtherEnd().isMany() + " Upper=" + this.getUpper() + " OUpper=" + this.getOtherEnd().getUpper() + " OAggregation=" + this.getOtherEnd().isAggregation() + " OComposition=" + this.getOtherEnd().isComposition() + " ONavigable=" + this.getOtherEnd().isNavigable() + this);
        return owning;*/
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsOptional()
     */
    @Override
    protected boolean handleIsOptional()
    {
        boolean optional = true;
        String optionalString = (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_OPTIONAL);

        if (StringUtils.isBlank(optionalString))
        {
            optional = !this.isRequired();
        }
        else
        {
            optional = Boolean.valueOf(optionalString).booleanValue();
        }
        return optional;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetOrderByClause()
     */
    @Override
    protected String handleGetOrderByClause()
    {
        return (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_ORDERBY);
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetColumnDefinition()
     */
    @Override
    protected String handleGetColumnDefinition()
    {
        return (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_COLUMN_DEFINITION);
    }

    /**
     * Returns true if the tagged name exists for this association end.
     *
     * @param name The tagged name to lookup.
     * @return boolean True if the tagged name exists.  False otherwise.
     *
     * @see EJB3AssociationEndFacadeLogic#handleHasTaggedValue(String)
     */
    @Override
    protected boolean handleHasTaggedValue(String name)
    {
        boolean exists = false;
        if (StringUtils.isNotBlank(name))
        {
            // trim to remove leading/trailing spaces
            name = StringUtils.trimToEmpty(name);

            // loop over tagged values and match the argument tagged value name
            for (TaggedValueFacade taggedValue : this.getTaggedValues())
            {
                // return with true on the first match found
                //if (name.equals(taggedValue.getName()))
                String tagName = taggedValue.getName();
                if (name.equals(tagName) || MetafacadeUtils.getEmfTaggedValue(name).equals(tagName)
                    || MetafacadeUtils.getUml14TaggedValue(name).equals(tagName))
                {
                    exists = true;
                    break;
                }
            }
        }
        return exists;
    }

    /**
     * Resolves a comma separated list of cascade types from andromda.xml

     * @param cascadesString
     * @return fully qualified cascade type sequence
     */
    private String getFullyQualifiedCascadeTypeList(final String cascadesString)
    {
        StringBuilder buf = null;
        if (StringUtils.isNotBlank(cascadesString))
        {
            String[] ct = cascadesString.split(",");
            for (int i = 0; i < ct.length; i++)
            {
                final String value = ct[i].trim();
                if (StringUtils.isNotBlank(value))
                {
                    if (buf == null)
                    {
                        buf = new StringBuilder();
                    }
                    else
                    {
                        buf.append(", ");
                    }

                    buf.append(cascadeTable.get(value));
                }
            }
        }
        return buf == null ? null : buf.toString();
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCascadeType()
     */
    @Override
    protected String handleGetCascadeType()
    {
        String cascade = null;
        final Collection<Object> taggedValues = this.findTaggedValues(EJB3Profile.TAGGEDVALUE_PERSISTENCE_CASCADE_TYPE);
        if (taggedValues != null && !taggedValues.isEmpty())
        {
            StringBuilder buf = null;
            for (Object value : taggedValues)
            {
                if (buf == null)
                {
                    buf = new StringBuilder();
                }
                else
                {
                    buf.append(", ");
                }
                if (StringUtils.isNotBlank((String)value))
                {
                    buf.append(cascadeTable.get(value));
                }
            }
            if (buf != null)
            {
                cascade = buf.toString();
            }
        }
        else if ((this.getOtherEnd() != null) &&
                 (this.getOtherEnd().isAggregation() || this.getOtherEnd().isComposition()))
        {
            cascade = cascadeTable.get(ENTITY_CASCADE_REMOVE);
            if (this.getOtherEnd().isComposition())
            {
                if (StringUtils.isBlank(this.getCompositionCascadeType()))
                {
                    if (this.getType() instanceof EJB3EntityFacade)
                    {
                        EJB3EntityFacade entity = (EJB3EntityFacade)this.getType();
                        cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(entity.getDefaultCascadeType()) ?
                                null : this.getFullyQualifiedCascadeTypeList(entity.getDefaultCascadeType()));
                    }
                }
                else
                {
                    cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(this.getCompositionCascadeType()) ?
                            null : this.getFullyQualifiedCascadeTypeList(this.getCompositionCascadeType()));
                }
            }
            else if (this.getOtherEnd().isAggregation())
            {
                if (StringUtils.isBlank(this.getAggregationCascadeType()))
                {
                    if (this.getType() instanceof EJB3EntityFacade)
                    {
                        EJB3EntityFacade entity = (EJB3EntityFacade)this.getType();
                        cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(entity.getDefaultCascadeType()) ?
                                null : this.getFullyQualifiedCascadeTypeList(entity.getDefaultCascadeType()));
                    }
                }
                else
                {
                    cascade = (ENTITY_CASCADE_NONE.equalsIgnoreCase(this.getAggregationCascadeType()) ?
                            null : this.getFullyQualifiedCascadeTypeList(this.getAggregationCascadeType()));
                }
            }
        }
        else if (this.isComposition())
        {
            /*
             * On the composite side of the relationship, always enforce no cascade delete
             * property indicating no cascadable propagation - overriding a default cascade
             * value
             */
            // TODO cascade can only be null at this point - anything else to change?
            //cascade = null;
        }
        else if (this.isAggregation())
        {
            /*
             * On the aggregation side of the relationship, always enforce no cascade delete
             * property indicating no cascadable propagation - overriding a default cascade
             * value
             */
            // TODO cascade can only be null at this point - anything else to change?
            //cascade = null;
        }
        return cascade;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCompositionCascadeType()
     */
    @Override
    protected String handleGetCompositionCascadeType()
    {
        return StringUtils.trimToEmpty(
                ObjectUtils.toString(this.getConfiguredProperty(ENTITY_DEFAULT_COMPOSITE_CASCADE)));
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetAggregationCascadeType()
     */
    @Override
    protected String handleGetAggregationCascadeType()
    {
        return StringUtils.trimToEmpty(
                ObjectUtils.toString(this.getConfiguredProperty(ENTITY_DEFAULT_AGGREGATION_CASCADE)));
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionType()
     */
    @Override
    protected String handleGetCollectionType()
    {
        String collectionType = this.getSpecificCollectionType();
        if (!collectionTypes.contains(collectionType))
        {
            if (this.isOrdered())
            {
                collectionType = COLLECTION_TYPE_LIST;
            }
            else
            {
                collectionType =
                    (String)this.getConfiguredProperty(ASSOCIATION_COLLECTION_TYPE);
            }
        }
        return collectionType;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionTypeImplemenationClass()
     */
    @Override
    protected String handleGetCollectionTypeImplemenationClass()
    {
        String collectionTypeImplementationClass = null;
        if (this.isMany())
        {
            if (this.isSet())
            {
                collectionTypeImplementationClass = String.valueOf(
                        this.getConfiguredProperty(SET_TYPE_IMPLEMENTATION));
            }
            else if (this.isMap())
            {
                collectionTypeImplementationClass = String.valueOf(
                        this.getConfiguredProperty(MAP_TYPE_IMPLEMENTATION));
            }
            else if (this.isList() || this.isCollection())
            {
                collectionTypeImplementationClass = String.valueOf(
                        this.getConfiguredProperty(LIST_TYPE_IMPLEMENTATION));
            }
        }
        return collectionTypeImplementationClass;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionTypeImplementation()
     */
    @Override
    protected String handleGetCollectionTypeImplementation()
    {
        return this.getCollectionTypeImplementation(null);
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionTypeImplementation(String)
     */
    @Override
    protected String handleGetCollectionTypeImplementation(final String arg)
    {
        StringBuilder implementation = new StringBuilder();
        if (this.isMany())
        {
            implementation.append("new ");
            implementation.append(this.getCollectionTypeImplemenationClass());

            // set this association end's type as a template parameter if required
            if (Boolean.valueOf(String.valueOf(this.getConfiguredProperty(UMLMetafacadeProperties.ENABLE_TEMPLATING)))
                       .booleanValue())
            {
                implementation.append("<");
                if (this.isMap())
                {
                    implementation.append(this.getCollectionIndexType());
                    implementation.append(", ");
                }
                implementation.append(this.getType().getFullyQualifiedName());
                implementation.append(">");
            }
            implementation.append("(");
            if (StringUtils.isNotBlank(arg))
            {
                implementation.append(arg);
            }
            implementation.append(")");
        }

        return implementation.toString();
    }

    /**
     * Gets the collection type defined on this association end.
     *
     * @return the specific collection type.
     */
    private String getSpecificCollectionType()
    {
        return ObjectUtils.toString(
            this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_COLLECTION_TYPE));
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionIndexType()
     */
    @Override
    protected String handleGetCollectionIndexType()
    {
        Object value = this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_INDEX_TYPE);
        if (value == null)
        {
            Object name = this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_INDEX);
            if (name == null)
            {
                ClassifierFacade facade = this.getOtherEnd().getType();
                if (facade instanceof EJB3EntityFacade)
                {
                    // Find the identifier
                    ModelElementFacade identifier = ((EJB3EntityFacade)facade).getIdentifier();
                    if (identifier instanceof AttributeFacade)
                    {
                        value = ((AttributeFacade)identifier).getType().getFullyQualifiedName();
                        return value.toString();
                    }
                    else if (identifier instanceof AssociationEndFacade)
                    {
                        value = ((AssociationEndFacade)identifier).getType().getFullyQualifiedName();
                        return value.toString();
                    }
                }
            }
            // Find the attribute corresponding to name
            Collection<AttributeFacade> attributes = ((EJB3EntityFacade)this.getOtherEnd().getType()).getAttributes();
            for (AttributeFacade attrib : attributes)
            {
                EJB3EntityAttributeFacade attribute = (EJB3EntityAttributeFacade)attrib;
                if (attribute.getName().equals(name))
                {
                    value = attribute.getType().getFullyQualifiedName();
                    return value.toString();
                }
            }

            // value can only be null at this point
            value = this.getConfiguredProperty(COLLECTION_INDEX_TYPE);
            if (StringUtils.isBlank(ObjectUtils.toString(value)))
            {
                value = null;
            }
         }

        if (value != null)
        {
            if (value instanceof String)
            {
                value = this.getRootPackage().findModelElement((String)value);
            }
            if (value instanceof EJB3TypeFacade)
            {
                value = ((EJB3TypeFacade)value).getFullyQualifiedEJB3Type();
            }
        }
        return (value != null) ? ObjectUtils.toString(value) : null;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCollectionIndexName()
     */
    @Override
    protected String handleGetCollectionIndexName()
    {
        Object value = this.findTaggedValue(EJB3Profile.TAGGEDVALUE_ASSOCIATION_INDEX);
        if ((value == null) && this.isConfiguredProperty(COLLECTION_INDEX_NAME))
        {
            value = this.getConfiguredProperty(COLLECTION_INDEX_NAME);
            if (StringUtils.isBlank(ObjectUtils.toString(value)))
            {
                value = null;
            }
        }

        if (value != null)
        {
            return ObjectUtils.toString(value);
        }
        final String otherEntityName = ((EJB3EntityFacade)this.getOtherEnd().getType()).getEntityName();
        final Object separator = this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR);
        return EntityMetafacadeUtils.toSqlName(
            otherEntityName,
            separator) + separator + EntityMetafacadeUtils.toSqlName(
            this.getName(),
            separator) + separator + "IDX";
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsMap()
     */
    @Override
    protected boolean handleIsMap()
    {
        boolean isMap = COLLECTION_TYPE_MAP.equalsIgnoreCase(this.getCollectionType());
        if (isMap && StringUtils.isBlank(this.getSpecificCollectionType()))
        {
            isMap = !this.isOrdered();
        }
        return isMap;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsList()
     */
    @Override
    protected boolean handleIsList()
    {
        boolean isList = COLLECTION_TYPE_LIST.equalsIgnoreCase(this.getCollectionType());
        if (!isList && StringUtils.isBlank(this.getSpecificCollectionType()))
        {
            isList = this.isOrdered();
        }
        return isList;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsSet()
     */
    @Override
    protected boolean handleIsSet()
    {
        boolean isSet = COLLECTION_TYPE_SET.equalsIgnoreCase(this.getCollectionType());
        if (isSet && StringUtils.isBlank(this.getSpecificCollectionType()))
        {
            isSet = !this.isOrdered();
        }
        return isSet;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsCollection()
     */
    @Override
    protected boolean handleIsCollection()
    {
        boolean isCollection = COLLECTION_TYPE_COLLECTION.equalsIgnoreCase(this.getCollectionType());
        if (!isCollection && StringUtils.isBlank(this.getSpecificCollectionType()))
        {
            isCollection = this.isOrdered();
        }
        return isCollection;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetLabelName()
     */
    @Override
    protected String handleGetLabelName()
    {
        String labelNamePattern = (this.isMany() ?
                (String)this.getConfiguredProperty(EJB3Globals.LABEL_COLLECTION_NAME_PATTERN) :
                    (String)this.getConfiguredProperty(EJB3Globals.LABEL_SINGLE_NAME_PATTERN));

        return MessageFormat.format(
                labelNamePattern,
                StringUtils.trimToEmpty(this.getName()));
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetGetterLabelName()
     */
    @Override
    protected String handleGetGetterLabelName()
    {
        return UMLMetafacadeUtils.getGetterPrefix(this.getType()) + StringUtils.capitalize(this.getLabelName());
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetSetterLabelName()
     */
    @Override
    protected String handleGetSetterLabelName()
    {
        return "set" + StringUtils.capitalize(this.getLabelName());
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetCacheType()
     */
    @Override
    protected String handleGetCacheType()
    {
        String cacheType = (String)super.findTaggedValue(EJB3Profile.TAGGEDVALUE_HIBERNATE_ASSOCIATION_CACHE);
        if (StringUtils.isBlank(cacheType))
        {
            cacheType = String.valueOf(this.getConfiguredProperty(HIBERNATE_ASSOCIATION_CACHE));
        }
        return StringUtils.trimToEmpty(cacheType);
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsAssociationCacheEnabled()
     */
    @Override
    protected boolean handleIsAssociationCacheEnabled()
    {
        return BooleanUtils.toBoolean(String.valueOf(this.getConfiguredProperty(HIBERNATE_ASSOCIATION_ENABLE_CACHE)));
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsForeignKeyConstraintDefined()
     */
    @Override
    protected boolean handleIsForeignKeyConstraintDefined()
    {
        boolean fkConstraintDefined = false;
        if (super.findTaggedValue(UMLProfile.TAGGEDVALUE_PERSISTENCE_FOREIGN_KEY_CONSTRAINT_NAME) != null)
        {
            fkConstraintDefined = true;
        }
        return fkConstraintDefined;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetForeignKeyConstraintName(String)
     */
    @Override
    protected String handleGetForeignKeyConstraintName(final String suffix)
    {
        String constraintName;

        final Object taggedValueObject = super.findTaggedValue(
                UMLProfile.TAGGEDVALUE_PERSISTENCE_FOREIGN_KEY_CONSTRAINT_NAME);

        /**
         * Construct our own foreign key constraint name here
         */
        StringBuilder buffer = new StringBuilder();

        if (taggedValueObject == null)
        {
            final ClassifierFacade type = this.getOtherEnd().getType();
            if (type instanceof Entity)
            {
                //Entity entity = (Entity)type;
                //Instead of using the entity name, use the association end name to avoid duplication of
                //FK constraint names which causes failures during table creation for some DBs (MySQL)
                buffer.append(
                        EntityMetafacadeUtils.toSqlName(
                                this.getOtherEnd().getName(),
                                this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR)));
            }
            else
            {
                // should not happen
                buffer.append(type.getName().toUpperCase());
            }

            buffer.append(this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR));

            /**
             * Add the suffix - which is the name of the identifier pk column if not blank
             * otherwise use the column name of the relationship
             */
            if (StringUtils.isNotBlank(suffix))
            {
                buffer.append(suffix);
            }
            else
            {
                buffer.append(this.getColumnName());
            }
            constraintName = buffer.toString();

            final String constraintSuffix =
                ObjectUtils.toString(this.getConfiguredProperty(UMLMetafacadeProperties.CONSTRAINT_SUFFIX)).trim();

            /**
             * we take into consideration the maximum length allowed
             */
            final String maxLengthString = (String)super.getConfiguredProperty(UMLMetafacadeProperties.MAX_SQL_NAME_LENGTH);
            final short maxLength = (short)(Short.valueOf(maxLengthString).shortValue() - constraintSuffix.length());
            final String method = (String)super.getConfiguredProperty(UMLMetafacadeProperties.SHORTEN_SQL_NAMES_METHOD);
            buffer = new StringBuilder(
                    EntityMetafacadeUtils.ensureMaximumNameLength(constraintName, Short.valueOf(maxLength),method));
            buffer.append(constraintSuffix);
        }
        else
        {
            // use the tagged value
            buffer.append(taggedValueObject.toString());
        }

        return buffer.toString();
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetForeignKeyName(String)
     */
    @Override
    protected String handleGetForeignKeyName(String suffix)
    {
        String columnName = null;
        // prevent ClassCastException if the association isn't an Entity
        if (this.getType() instanceof Entity)
        {
            if (StringUtils.isNotBlank(suffix))
            {
                suffix = new String(
                        this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR) +
                        suffix +
                        this.getForeignKeySuffix());
            }
            else
            {
                suffix = this.getForeignKeySuffix();
            }

            final String columnNamePrefix =
                this.isConfiguredProperty(UMLMetafacadeProperties.COLUMN_NAME_PREFIX)
                ? ObjectUtils.toString(this.getConfiguredProperty(UMLMetafacadeProperties.COLUMN_NAME_PREFIX)) : null;
            columnName =
                EntityMetafacadeUtils.getSqlNameFromTaggedValue(
                    columnNamePrefix,
                    this,
                    UMLProfile.TAGGEDVALUE_PERSISTENCE_COLUMN,
                    ((Entity)this.getType()).getMaxSqlNameLength(),
                    suffix,
                    this.getConfiguredProperty(UMLMetafacadeProperties.SQL_NAME_SEPARATOR),
                    this.getConfiguredProperty(UMLMetafacadeProperties.SHORTEN_SQL_NAMES_METHOD));
        }
        return columnName;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetDefaultCollectionInterface()
     */
    @Override
    protected String handleGetDefaultCollectionInterface()
    {
        return ObjectUtils.toString(this.getConfiguredProperty(DEFAULT_COLLECTION_INTERFACE));
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsCollectionInterfaceSortedSet()
     */
    @Override
    protected boolean handleIsCollectionInterfaceSortedSet()
    {
        boolean isInterfaceSortedSet = false;
        if (this.getGetterSetterTypeName().startsWith(EJB3Globals.COLLECTION_INTERFACE_SORTED_SET))
        {
            isInterfaceSortedSet = true;
        }
        return isInterfaceSortedSet;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleGetHibernateCascadeType()
     */
    @Override
    protected String handleGetHibernateCascadeType()
    {
        return (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_HIBERNATE_CASCADE);
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsHibernateCascadeExists()
     */
    @Override
    protected boolean handleIsHibernateCascadeExists()
    {
        return StringUtils.isNotBlank(this.getHibernateCascadeType()) ? true : false;
    }

    /**
     * @see EJB3AssociationEndFacadeLogic#handleIsColumnNullable()
     */
    @Override
    protected boolean handleIsColumnNullable()
    {
        boolean nullable = true;
        String nullableString = (String)this.findTaggedValue(EJB3Profile.TAGGEDVALUE_PERSISTENCE_COLUMN_NULLABLE);

        if (StringUtils.isBlank(nullableString))
        {
            nullable = (this.isIdentifier() || this.isUnique()) ? false : !this.isRequired();
        }
        else
        {
            nullable = Boolean.valueOf(nullableString).booleanValue();
        }
        return nullable;
    }
}