Package org.jpox.jpa.metadata

Source Code of org.jpox.jpa.metadata.JPAAnnotationReader

/**********************************************************************
Copyright (c) 2006 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contributors:
    ...
**********************************************************************/
package org.jpox.jpa.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.AssociationOverride;
import javax.persistence.AttributeOverride;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.DiscriminatorType;
import javax.persistence.EmbeddedId;
import javax.persistence.EntityResult;
import javax.persistence.EnumType;
import javax.persistence.FetchType;
import javax.persistence.FieldResult;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.NamedNativeQuery;
import javax.persistence.NamedQuery;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;

import org.jpox.ClassLoaderResolver;
import org.jpox.exceptions.JPOXException;
import org.jpox.jpa.annotations.Extension;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.ArrayMetaData;
import org.jpox.metadata.ClassMetaData;
import org.jpox.metadata.ClassPersistenceModifier;
import org.jpox.metadata.CollectionMetaData;
import org.jpox.metadata.ColumnMetaData;
import org.jpox.metadata.ContainerMetaData;
import org.jpox.metadata.DiscriminatorMetaData;
import org.jpox.metadata.ElementMetaData;
import org.jpox.metadata.EmbeddedMetaData;
import org.jpox.metadata.EventListenerMetaData;
import org.jpox.metadata.ExtensionMetaData;
import org.jpox.metadata.FieldPersistenceModifier;
import org.jpox.metadata.FileMetaData;
import org.jpox.metadata.IdentityMetaData;
import org.jpox.metadata.IdentityStrategy;
import org.jpox.metadata.InheritanceMetaData;
import org.jpox.metadata.InheritanceStrategy;
import org.jpox.metadata.JoinMetaData;
import org.jpox.metadata.KeyMetaData;
import org.jpox.metadata.MapMetaData;
import org.jpox.metadata.MetaData;
import org.jpox.metadata.MetaDataManager;
import org.jpox.metadata.OrderMetaData;
import org.jpox.metadata.PackageMetaData;
import org.jpox.metadata.PrimaryKeyMetaData;
import org.jpox.metadata.QueryLanguage;
import org.jpox.metadata.QueryMetaData;
import org.jpox.metadata.QueryResultMetaData;
import org.jpox.metadata.SequenceMetaData;
import org.jpox.metadata.TableGeneratorMetaData;
import org.jpox.metadata.UniqueMetaData;
import org.jpox.metadata.ValueMetaData;
import org.jpox.metadata.VersionMetaData;
import org.jpox.metadata.VersionStrategy;
import org.jpox.metadata.annotations.AbstractAnnotationReader;
import org.jpox.metadata.annotations.AnnotationObject;
import org.jpox.metadata.annotations.Member;
import org.jpox.util.ClassUtils;
import org.jpox.util.JPOXLogger;
import org.jpox.util.StringUtils;

/**
* Implementation for Annotation Reader for JDK 1.5 annotations using JPA's definition.
* This reader also accepts certain JPOX extensions where the JPA annotations dont provide
* full definition of the data required.
*
* @version $Revision: 1.1 $
*/
public class JPAAnnotationReader extends AbstractAnnotationReader
{
    /**
     * Constructor.
     * @param mgr MetaData manager
     */
    public JPAAnnotationReader(MetaDataManager mgr)
    {
        super(mgr);

        // We support JPA and JPOX annotations in this reader
        setSupportedAnnotationPackages(new String[] {"javax.persistence", "org.jpox"});
    }

    /**
     * Method to process the "class" level annotations and create the outline ClassMetaData object
     * @param pmd Parent PackageMetaData
     * @param cls The class
     * @param annotations Annotations for this class
     * @param clr ClassLoader resolver
     * @return The ClassMetaData (or null if no annotations)
     */
    protected AbstractClassMetaData processClassAnnotations(PackageMetaData pmd, Class cls, AnnotationObject[] annotations,
            ClassLoaderResolver clr)
    {
        ClassMetaData cmd = null;

        if (annotations != null && annotations.length > 0)
        {
            String identityType = org.jpox.metadata.IdentityType.APPLICATION.toString();
            String identityColumn = null;
            String identityStrategy = null;
            String identityGenerator = null;

            String requiresExtent = "true";
            String detachable = "true"; // In JPA default is true.
            String embeddedOnly = "false";
            String persistenceModifier = ClassPersistenceModifier.NON_PERSISTENT.toString();
            String idClassName = null;
            String catalog = null;
            String schema = null;
            String table = null;
            String inheritanceStrategyForTree = null;
            String inheritanceStrategy = null;
            String discriminatorColumnName = null;
            String discriminatorColumnType = null;
            Integer discriminatorColumnLength = null;
            String discriminatorValue = null;
            String entityName = null;
            Class[] entityListeners = null;
            boolean excludeSuperClassListeners = false;
            boolean excludeDefaultListeners = false;
            ColumnMetaData[] pkColumnMetaData = null;
            HashSet<UniqueMetaData> uniques = null;
            HashSet<AbstractMemberMetaData> overriddenFields = null;
            HashSet<QueryMetaData> namedQueries = null;
            List<QueryResultMetaData> resultMappings = null;
            HashSet<ExtensionMetaData> extensions = null;

            for (int i=0;i<annotations.length;i++)
            {
                if (isSupportedAnnotation(annotations[i].getName()))
                {
                    HashMap<String, Object> annotationValues = annotations[i].getNameValueMap();
                    String annName = annotations[i].getName();
                    if (annName.equals(JPAAnnotationUtils.ENTITY))
                    {
                        // Class is an "Entity" so needs persisting
                        persistenceModifier = ClassPersistenceModifier.PERSISTENCE_CAPABLE.toString();
                        entityName = (String) annotationValues.get("name");
                        if (entityName == null || entityName.length() == 0)
                        {
                            entityName = ClassUtils.getClassNameForClass(cls);
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.MAPPED_SUPERCLASS))
                    {
                        if (isClassPersistenceCapable(cls))
                        {
                            // Class is an "MappedSuperclass" (no table) and has ID fields so needs persisting
                            persistenceModifier = ClassPersistenceModifier.PERSISTENCE_CAPABLE.toString();
                            inheritanceStrategy = InheritanceStrategy.SUBCLASS_TABLE.toString();
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.PERSISTENCE_AWARE))
                    {
                        // JPOX extension to define that the class accesses persistable fields so needs enhancing
                        persistenceModifier = ClassPersistenceModifier.PERSISTENCE_AWARE.toString();
                    }
                    else if (annName.equals(JPAAnnotationUtils.DATASTORE_IDENTITY))
                    {
                        // JPOX extension to allow datastore-identity
                        identityType = org.jpox.metadata.IdentityType.DATASTORE.toString();
                        identityColumn = (String)annotationValues.get("column");
                        GenerationType type = (GenerationType) annotationValues.get("generationType");
                        identityStrategy = JPAAnnotationUtils.getIdentityStrategyString(type);
                        identityGenerator = (String) annotationValues.get("generator");
                    }
                    else if (annName.equals(JPAAnnotationUtils.TABLE))
                    {
                        table = (String)annotationValues.get("name");
                        catalog = (String)annotationValues.get("catalog");
                        schema = (String)annotationValues.get("schema");
                        UniqueConstraint[] constrs = (UniqueConstraint[])annotationValues.get("uniqueConstraints");
                        if (constrs != null && constrs.length > 0)
                        {
                            for (int j=0;j<constrs.length;j++)
                            {
                                UniqueMetaData unimd = new UniqueMetaData(null, (String)annotationValues.get("name"), null);
                                for (int k=0;k<constrs[j].columnNames().length;k++)
                                {
                                    unimd.addColumn(new ColumnMetaData(unimd, constrs[j].columnNames()[k]));
                                }
                                uniques.add(unimd);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ID_CLASS))
                    {
                        idClassName = ((Class)annotationValues.get("value")).getName();
                    }
                    else if (annName.equals(JPAAnnotationUtils.INHERITANCE))
                    {
                        // Only valid in the root class
                        InheritanceType inhType = (InheritanceType)annotationValues.get("strategy");
                        inheritanceStrategyForTree = inhType.toString();
                        if (inhType == InheritanceType.JOINED)
                        {
                            inheritanceStrategy = InheritanceStrategy.NEW_TABLE.toString();
                        }
                        else if (inhType == InheritanceType.TABLE_PER_CLASS)
                        {
                            inheritanceStrategy = InheritanceStrategy.COMPLETE_TABLE.toString();
                        }
                        else if (inhType == InheritanceType.SINGLE_TABLE)
                        {
                            // Translated to root class as "new-table" and children as "superclass-table"
                            // and @Inheritance should only be specified on root class so defaults to JPOX internal default
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.DISCRIMINATOR_COLUMN))
                    {
                        discriminatorColumnName = (String)annotationValues.get("name");
                        DiscriminatorType type = (DiscriminatorType)annotationValues.get("discriminatorType");
                        if (type == DiscriminatorType.CHAR)
                        {
                            discriminatorColumnType = "CHAR";
                        }
                        else if (type == DiscriminatorType.INTEGER)
                        {
                            discriminatorColumnType = "INTEGER";
                        }
                        else if (type == DiscriminatorType.STRING)
                        {
                            discriminatorColumnType = "VARCHAR";
                        }
                        discriminatorColumnLength = (Integer)annotationValues.get("length");
                        // TODO Support JPA "columnDefinition"
                    }
                    else if (annName.equals(JPAAnnotationUtils.DISCRIMINATOR_VALUE))
                    {
                        discriminatorValue = (String)annotationValues.get("value");
                    }
                    else if (annName.equals(JPAAnnotationUtils.EMBEDDABLE))
                    {
                        embeddedOnly = "true";
                        persistenceModifier = ClassPersistenceModifier.PERSISTENCE_CAPABLE.toString();
                        identityType = org.jpox.metadata.IdentityType.NONDURABLE.toString();
                    }
                    else if (annName.equals(JPAAnnotationUtils.ENTITY_LISTENERS))
                    {
                        entityListeners = (Class[])annotationValues.get("value");
                    }
                    else if (annName.equals(JPAAnnotationUtils.EXCLUDE_SUPERCLASS_LISTENERS))
                    {
                        excludeSuperClassListeners = true;
                    }
                    else if (annName.equals(JPAAnnotationUtils.EXCLUDE_DEFAULT_LISTENERS))
                    {
                        excludeDefaultListeners = true;
                    }
                    else if (annName.equals(JPAAnnotationUtils.SEQUENCE_GENERATOR))
                    {
                        processSequenceGeneratorAnnotation(pmd, annotationValues);
                    }
                    else if (annName.equals(JPAAnnotationUtils.TABLE_GENERATOR))
                    {
                        processTableGeneratorAnnotation(pmd, annotationValues);
                    }
                    else if (annName.equals(JPAAnnotationUtils.PRIMARY_KEY_JOIN_COLUMN))
                    {
                        // Override the PK column name when we have a persistent superclass
                        pkColumnMetaData = new ColumnMetaData[1];
                        pkColumnMetaData[0] = new ColumnMetaData(cmd,
                            (String)annotationValues.get("name"), (String)annotationValues.get("referencedColumnName"),
                            null, null, null, null, null, null, null, null, null, null, null);
                    }
                    else if (annName.equals(JPAAnnotationUtils.PRIMARY_KEY_JOIN_COLUMNS))
                    {
                        // Override the PK column names when we have a persistent superclass
                        PrimaryKeyJoinColumn[] values = (PrimaryKeyJoinColumn[])annotationValues.get("value");
                        pkColumnMetaData = new ColumnMetaData[values.length];
                        for (int j=0;j<values.length;j++)
                        {
                            // TODO Support columnDefinition
                            pkColumnMetaData[j] = new ColumnMetaData(cmd,
                                values[j].name(), values[j].referencedColumnName(),
                                null, null, null, null, null, null, null, null, null, null, null);
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ATTRIBUTE_OVERRIDES))
                    {
                        AttributeOverride[] overrides = (AttributeOverride[])annotationValues.get("value");
                        if (overrides != null)
                        {
                            if (overriddenFields == null)
                            {
                                overriddenFields = new HashSet<AbstractMemberMetaData>();
                            }

                            for (int j=0;j<overrides.length;j++)
                            {
                                AbstractMemberMetaData fmd = mgr.getMetaDataFactory().newFieldObject(cmd,
                                    "#UNKNOWN." + overrides[j].name(),
                                    null, "persistent", null, null, null, null, null, null, null, null, null, null, null, null,
                                    null, null, null, null, null, null);
                                Column col = overrides[j].column();
                                // TODO Make inferrals about jdbctype, length etc if the field is 1 char etc
                                fmd.addColumn(new ColumnMetaData(fmd, col.name(), null, null, null, null,
                                    "" + col.length(), "" + col.scale(), "" + col.nullable(), null, null,
                                    "" + col.insertable(), "" + col.updatable(), "" + col.unique()));
                                overriddenFields.add(fmd);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ATTRIBUTE_OVERRIDE))
                    {
                        if (overriddenFields == null)
                        {
                            overriddenFields = new HashSet<AbstractMemberMetaData>();
                        }

                        AbstractMemberMetaData fmd = mgr.getMetaDataFactory().newFieldObject(cmd,
                            "#UNKNOWN." + (String)annotationValues.get("name"),
                            null, null, null, null, null, null, null, null, null, null, null, null, null, null,
                            null, null, null, null, null, null);
                        Column col = (Column)annotationValues.get("column");
                        // TODO Make inferrals about jdbctype, length etc if the field is 1 char etc
                        fmd.addColumn(new ColumnMetaData(fmd, col.name(), null, null, null, null,
                            "" + col.length(), "" + col.scale(), "" + col.nullable(), null, null,
                            "" + col.insertable(), "" + col.updatable(), "" + col.unique()));
                        overriddenFields.add(fmd);
                    }
                    else if (annName.equals(JPAAnnotationUtils.ASSOCIATION_OVERRIDES))
                    {
                        AssociationOverride[] overrides = (AssociationOverride[])annotationValues.get("value");
                        if (overrides != null)
                        {
                            if (overriddenFields == null)
                            {
                                overriddenFields = new HashSet<AbstractMemberMetaData>();
                            }

                            for (int j=0;j<overrides.length;j++)
                            {
                                AbstractMemberMetaData fmd = mgr.getMetaDataFactory().newFieldObject(cmd,
                                    "#UNKNOWN." + overrides[j].name(),
                                    null, null, null, null, null, null, null, null, null, null, null, null, null, null,
                                    null, null, null, null, null, null);
                                JoinColumn[] cols = overrides[j].joinColumns();
                                for (int k=0;k<cols.length;k++)
                                {
                                    fmd.addColumn(new ColumnMetaData(fmd, cols[k].name(), null,
                                        cols[k].referencedColumnName(), null, null,
                                        null, null, "" + cols[k].nullable(), null, null,
                                        "" + cols[k].insertable(), "" + cols[k].updatable(), "" + cols[k].unique()));
                                }
                                overriddenFields.add(fmd);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ASSOCIATION_OVERRIDE))
                    {
                        if (overriddenFields == null)
                        {
                            overriddenFields = new HashSet<AbstractMemberMetaData>();
                        }

                        AbstractMemberMetaData fmd = mgr.getMetaDataFactory().newFieldObject(cmd,
                            "#UNKNOWN." + (String)annotationValues.get("name"),
                            null, null, null, null, null, null, null, null, null, null, null, null, null, null,
                            null, null, null, null, null, null);
                        JoinColumn[] cols = (JoinColumn[])annotationValues.get("joinColumns");
                        for (int k=0;k<cols.length;k++)
                        {
                            fmd.addColumn(new ColumnMetaData(fmd, cols[k].name(), null,
                                cols[k].referencedColumnName(), null, null,
                                null, null, "" + cols[k].nullable(), null, null,
                                "" + cols[k].insertable(), "" + cols[k].updatable(), "" + cols[k].unique()));
                        }
                        overriddenFields.add(fmd);
                    }
                    else if (annName.equals(JPAAnnotationUtils.NAMED_QUERIES))
                    {
                        NamedQuery[] queries = (NamedQuery[])annotationValues.get("value");
                        if (namedQueries == null)
                        {
                            namedQueries = new HashSet<QueryMetaData>();
                        }
                        for (int j=0;j<queries.length;j++)
                        {
                            QueryMetaData qmd = new QueryMetaData(cmd, null, queries[j].name(), QueryLanguage.JPQL.toString(),
                                "true", null, null, null, null);
                            qmd.setQuery(queries[j].query());
                            namedQueries.add(qmd);
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.NAMED_QUERY))
                    {
                        if (namedQueries == null)
                        {
                            namedQueries = new HashSet<QueryMetaData>();
                        }
                        QueryMetaData qmd = new QueryMetaData(cmd, null, (String)annotationValues.get("name"),
                            QueryLanguage.JPQL.toString(), "true", null, null, null, null);
                        qmd.setQuery((String)annotationValues.get("query"));
                        namedQueries.add(qmd);
                    }
                    else if (annName.equals(JPAAnnotationUtils.NAMED_NATIVE_QUERIES))
                    {
                        NamedNativeQuery[] queries = (NamedNativeQuery[])annotationValues.get("value");
                        if (namedQueries == null)
                        {
                            namedQueries = new HashSet<QueryMetaData>();
                        }
                        for (int j=0;j<queries.length;j++)
                        {
                            String resultClassName = null;
                            if (queries[j].resultClass() != null && queries[j].resultClass() != void.class)
                            {
                                resultClassName = queries[j].resultClass().getName();
                            }
                            String resultMappingName = null;
                            if (queries[j].resultSetMapping() != null)
                            {
                                resultMappingName = queries[j].resultSetMapping();
                            }
                            QueryMetaData qmd = new QueryMetaData(cmd, null, queries[j].name(), QueryLanguage.SQL.toString(),
                                "true", resultClassName, resultMappingName, null, null);
                            qmd.setQuery(queries[j].query());
                            namedQueries.add(qmd);
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.NAMED_NATIVE_QUERY))
                    {
                        if (namedQueries == null)
                        {
                            namedQueries = new HashSet<QueryMetaData>();
                        }

                        Class resultClass = (Class)annotationValues.get("resultClass");
                        String resultClassName = null;
                        if (resultClass != null && resultClass != void.class)
                        {
                            resultClassName = resultClass.getName();
                        }
                        String resultMappingName = (String)annotationValues.get("resultSetMapping");
                        if (StringUtils.isWhitespace(resultMappingName))
                        {
                            resultMappingName = null;
                        }
                        QueryMetaData qmd = new QueryMetaData(cmd, null, (String)annotationValues.get("name"),
                            QueryLanguage.SQL.toString(), "true", resultClassName, resultMappingName, null, null);
                        qmd.setQuery((String)annotationValues.get("query"));
                        namedQueries.add(qmd);
                    }
                    else if (annName.equals(JPAAnnotationUtils.SQL_RESULTSET_MAPPINGS))
                    {
                        SqlResultSetMapping[] mappings = (SqlResultSetMapping[])annotationValues.get("value");
                        if (resultMappings == null)
                        {
                            resultMappings = new ArrayList<QueryResultMetaData>();
                        }

                        for (int j=0;j<mappings.length;j++)
                        {
                            QueryResultMetaData qrmd = new QueryResultMetaData(cmd, mappings[j].name());
                            EntityResult[] entityResults = (EntityResult[])mappings[j].entities();
                            if (entityResults != null)
                            {
                                for (int k=0;k<entityResults.length;k++)
                                {
                                    String entityClassName = entityResults[k].entityClass().getName();
                                    qrmd.addPersistentTypeMapping(entityClassName, null,
                                        entityResults[k].discriminatorColumn());
                                    FieldResult[] fields = entityResults[k].fields();
                                    if (fields != null)
                                    {
                                        for (int l=0;l<fields.length;l++)
                                        {
                                            qrmd.addMappingForPersistentTypeMapping(entityClassName, fields[l].name(), fields[l].column());
                                        }
                                    }
                                }
                            }
                            ColumnResult[] colResults = (ColumnResult[])mappings[j].columns();
                            if (colResults != null)
                            {
                                for (int k=0;k<colResults.length;k++)
                                {
                                    qrmd.addScalarColumn(colResults[k].name());
                                }
                            }
                           
                            resultMappings.add(qrmd);
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.SQL_RESULTSET_MAPPING))
                    {
                        if (resultMappings == null)
                        {
                            resultMappings = new ArrayList<QueryResultMetaData>();
                        }

                        QueryResultMetaData qrmd = new QueryResultMetaData(cmd, (String)annotationValues.get("name"));
                        EntityResult[] entityResults = (EntityResult[])annotationValues.get("entities");
                        if (entityResults != null)
                        {
                            for (int j=0;j<entityResults.length;j++)
                            {
                                String entityClassName = entityResults[j].entityClass().getName();
                                qrmd.addPersistentTypeMapping(entityClassName, null,
                                    entityResults[j].discriminatorColumn());
                                FieldResult[] fields = entityResults[j].fields();
                                if (fields != null)
                                {
                                    for (int k=0;k<fields.length;k++)
                                    {
                                        qrmd.addMappingForPersistentTypeMapping(entityClassName, fields[k].name(), fields[k].column());
                                    }
                                }
                            }
                        }
                        ColumnResult[] colResults = (ColumnResult[])annotationValues.get("columns");
                        if (colResults != null)
                        {
                            for (int j=0;j<colResults.length;j++)
                            {
                                qrmd.addScalarColumn(colResults[j].name());
                            }
                        }
                        resultMappings.add(qrmd);
                    }
                    else if (annName.equals(JPAAnnotationUtils.SECONDARY_TABLES))
                    {
                        // processed below in newJoinMetaData
                    }
                    else if (annName.equals(JPAAnnotationUtils.SECONDARY_TABLE))
                    {
                        // processed below in newJoinMetaData
                    }
                    else if (annName.equals(JPAAnnotationUtils.EXTENSION))
                    {
                        // JPOX extension
                        ExtensionMetaData extmd = new ExtensionMetaData((String)annotationValues.get("vendorName"),
                            (String)annotationValues.get("key"), (String)annotationValues.get("value"));
                        extensions = new HashSet<ExtensionMetaData>(1);
                        extensions.add(extmd);
                    }
                    else if (annName.equals(JPAAnnotationUtils.EXTENSIONS))
                    {
                        // JPOX extension
                        Extension[] values = (Extension[])annotationValues.get("value");
                        if (values != null && values.length > 0)
                        {
                            extensions = new HashSet<ExtensionMetaData>(values.length);
                            for (int j=0;j<values.length;j++)
                            {
                                ExtensionMetaData extmd = new ExtensionMetaData(values[j].vendorName(),
                                    values[j].key().toString(), values[j].value().toString());
                                extensions.add(extmd);
                            }
                        }
                    }
                    else
                    {
                        JPOXLogger.METADATA.error(LOCALISER.msg("MetaData.Annotations.AnnotationNotProcessed",
                            cls.getName(), annotations[i].getName()));
                    }
                }
            }

            if (entityName == null || entityName.length() == 0)
            {
                entityName = ClassUtils.getClassNameForClass(cls);
            }

            if (persistenceModifier.equals(ClassPersistenceModifier.PERSISTENCE_CAPABLE.toString()) ||
                persistenceModifier.equals(ClassPersistenceModifier.PERSISTENCE_AWARE.toString()))
            {
                JPOXLogger.METADATA.info(LOCALISER.msg("MetaData.Annotations.ClassUsingAnnotations", cls.getName(), "JPA"));
                if (pmd == null)
                {
                    // No owning package defined so add one
                    FileMetaData filemd = new FileMetaData("annotations", null, null, null);
                    filemd.setType(FileMetaData.ANNOTATIONS);
                    pmd = new PackageMetaData(filemd, cls.getPackage().getName(), null, null);
                    filemd.addPackage(pmd);
                }
                boolean superClassPC = isClassPersistenceCapable(cls.getSuperclass());
                cmd = mgr.getMetaDataFactory().newClassObject(pmd, ClassUtils.getClassNameForClass(cls), identityType, idClassName,
                    requiresExtent, detachable, embeddedOnly, persistenceModifier,
                    superClassPC ? cls.getSuperclass().getName() : null, catalog, schema, table, entityName);

                if (excludeSuperClassListeners)
                {
                    cmd.excludeSuperClassListeners();
                }
                if (excludeDefaultListeners)
                {
                    cmd.excludeDefaultListeners();
                }
                if (entityListeners != null)
                {
                    for (int i=0; i<entityListeners.length; i++)
                    {
                        // Any EventListener will not have their callback methods registered at this point
                        EventListenerMetaData elmd = new EventListenerMetaData(entityListeners[i].getName());
                        cmd.addListener(elmd);
                    }
                }

                pmd.addClass(cmd);

                // Inheritance
                InheritanceMetaData inhmd = null;
                if (inheritanceStrategy != null)
                {
                    // Strategy specified so create inheritance data
                    inhmd = new InheritanceMetaData(cmd, inheritanceStrategy);
                    inhmd.setStrategyForTree(inheritanceStrategyForTree);
                }
                else if (discriminatorValue != null || discriminatorColumnName != null ||
                        discriminatorColumnLength != null || discriminatorColumnType != null)
                {
                    // Discriminator specified so we need inheritance data
                    inhmd = new InheritanceMetaData(cmd, null);
                    inhmd.setStrategyForTree(inheritanceStrategyForTree);
                }

                if (discriminatorValue != null || discriminatorColumnName != null ||
                    discriminatorColumnLength != null || discriminatorColumnType != null)
                {
                    // Add discriminator information to the inheritance of this class
                    DiscriminatorMetaData dismd = null;
                    if (discriminatorColumnType != null && discriminatorColumnType != "VARCHAR")
                    {
                        dismd = new DiscriminatorMetaData(inhmd, discriminatorColumnName, discriminatorValue,
                            "value-map", "false");
                    }
                    else
                    {
                        dismd = new DiscriminatorMetaData(inhmd, discriminatorColumnName, discriminatorValue,
                            "class-name", "false");
                    }
                    ColumnMetaData discolmd = null;
                    if (discriminatorColumnLength != null || discriminatorColumnName != null || discriminatorColumnType != null)
                    {
                        discolmd = new ColumnMetaData(dismd, discriminatorColumnName);
                        if (discriminatorColumnType != null)
                        {
                            discolmd.setJdbcType(discriminatorColumnType);
                        }
                        if (discriminatorColumnLength != null)
                        {
                            discolmd.setLength(discriminatorColumnLength);
                        }
                        dismd.setColumnMetaData(discolmd);
                    }
                    inhmd.setDiscriminatorMetaData(dismd);
                }
                if (inhmd != null)
                {
                    cmd.setInheritanceMetaData(inhmd);
                }

                // JPOX extension - datastore-identity
                if (identityType == org.jpox.metadata.IdentityType.DATASTORE.toString())
                {
                    IdentityMetaData idmd = new IdentityMetaData(cmd, identityColumn, identityStrategy, identityGenerator);
                    if (identityGenerator != null)
                    {
                        idmd.setValueGeneratorName(identityGenerator);
                    }
                    cmd.setIdentityMetaData(idmd);
                }

                if (pkColumnMetaData != null)
                {
                    // PK columns overriding those in the root class
                    PrimaryKeyMetaData pkmd = cmd.getPrimaryKeyMetaData();
                    if (pkmd == null)
                    {
                        pkmd = new PrimaryKeyMetaData(cmd, null, null);
                        cmd.setPrimaryKeyMetaData(pkmd);
                    }
                    for (int i=0;i<pkColumnMetaData.length;i++)
                    {
                        pkmd.addColumn(pkColumnMetaData[i]);
                    }
                }
                if (uniques != null && uniques.size() > 0)
                {
                    // Unique constraints for the primary/secondary tables
                    Iterator<UniqueMetaData> uniquesIter = uniques.iterator();
                    while (uniquesIter.hasNext())
                    {
                        cmd.addUniqueConstraint(uniquesIter.next());
                    }
                }

                if (overriddenFields != null)
                {
                    // Fields overridden from superclasses
                    Iterator<AbstractMemberMetaData> iter = overriddenFields.iterator();
                    while (iter.hasNext())
                    {
                        cmd.addMember(iter.next());
                    }
                }
                if (namedQueries != null)
                {
                    Iterator<QueryMetaData> iter = namedQueries.iterator();
                    while (iter.hasNext())
                    {
                        cmd.addQuery(iter.next());
                    }
                }
                if (resultMappings != null)
                {
                    Iterator<QueryResultMetaData> iter = resultMappings.iterator();
                    while (iter.hasNext())
                    {
                        cmd.addQueryResultMetaData(iter.next());
                    }
                }
                if (extensions != null)
                {
                    Iterator<ExtensionMetaData> iter = extensions.iterator();
                    while (iter.hasNext())
                    {
                        ExtensionMetaData extmd = iter.next();
                        cmd.addExtension(extmd.getVendorName(), extmd.getKey(), extmd.getValue());
                    }
                }
            }

            // Process any secondary tables
            newJoinMetaDataForClass(cmd, annotations);
        }

        return cmd;
    }

    /**
     * Convenience method to process the annotations for a field/property.
     * The passed annotations may have been specified on the field or on a getter method.
     * @param cmd The ClassMetaData to update
     * @param member The field/property
     * @param annotations The annotations for the field/property
     * @param propertyAccessor if has persistent properties
     * @return The FieldMetaData/PropertyMetaData that was added (if any)
     */
    protected AbstractMemberMetaData processMemberAnnotations(AbstractClassMetaData cmd, Member member,
            AnnotationObject[] annotations, boolean propertyAccessor)
    {
        if (Modifier.isTransient(member.getModifiers()))
        {
            // Field is transient so nothing to persist
            return null;
        }

        // TODO Change this when JPA is enhanced using methods starting "jpox"
        if (member.getName().startsWith("jdo"))
        {
            // ignore JDO fields/methods added during enhancement
            return null;
        }

        if ((annotations != null && annotations.length > 0) || JPAAnnotationUtils.isBasicByDefault(member.getType()))
        {
            if (!member.isProperty() && (annotations == null || annotations.length == 0) && propertyAccessor)
            {
                return null;
            }
            if (member.isProperty() && (annotations == null || annotations.length == 0) && !propertyAccessor)
            {
                // field accessor will ignore all methods not annotated
                return null;
            }

            // Create the Field/Property MetaData so we have something to add to
            AbstractMemberMetaData mmd = newMetaDataForMember(cmd, member, annotations);

            // Process other annotations
            ColumnMetaData[] columnMetaData = null;
            JoinMetaData joinmd = null;
            boolean oneToMany = false;
            boolean manyToMany = false;
            for (int i=0;annotations != null && i<annotations.length;i++)
            {
                if (isSupportedAnnotation(annotations[i].getName()))
                {
                    String annName = annotations[i].getName();
                    HashMap<String, Object> annotationValues = annotations[i].getNameValueMap();
                    if (annName.equals(JPAAnnotationUtils.JOIN_COLUMNS))
                    {
                        // 1-1 FK columns, or 1-N FK columns, or N-1 FK columns
                        JoinColumn[] cols = (JoinColumn[])annotationValues.get("value");
                        if (cols != null)
                        {
                            columnMetaData = new ColumnMetaData[cols.length];
                            for (int j=0;j<cols.length;j++)
                            {
                                columnMetaData[j] = new ColumnMetaData(mmd, cols[j].name(), null,
                                    cols[j].referencedColumnName(), null, null, null, null, "" + cols[j].nullable(),
                                    null, null, "" + cols[j].insertable(), "" + cols[j].updatable(), "" + cols[j].unique());
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.JOIN_COLUMN))
                    {
                        // 1-1 FK column, or 1-N FK column, or N-1 FK column
                        columnMetaData = new ColumnMetaData[1];
                        String colNullable = null;
                        String colInsertable = null;
                        String colUpdateable = null;
                        String colUnique = null;
                        if (annotationValues.get("nullable") != null)
                        {
                            colNullable = annotationValues.get("nullable").toString();
                        }
                        if (annotationValues.get("insertable") != null)
                        {
                            colInsertable = annotationValues.get("insertable").toString();
                        }
                        if (annotationValues.get("updatable") != null)
                        {
                            // Note : "updatable" is spelt incorrectly in the JPA spec.
                            colUpdateable = annotationValues.get("updatable").toString();
                        }
                        if (annotationValues.get("unique") != null)
                        {
                            colUnique = annotationValues.get("unique").toString();
                        }
                        columnMetaData[0] = new ColumnMetaData(mmd, (String)annotationValues.get("name"), null,
                            (String)annotationValues.get("referencedColumnName"), null, null,
                            null, null, colNullable, null, null, colInsertable, colUpdateable, colUnique);
                    }
                    else if (annName.equals(JPAAnnotationUtils.ATTRIBUTE_OVERRIDES) && mmd.isEmbedded())
                    {
                        // Embedded field overrides
                        EmbeddedMetaData emd = new EmbeddedMetaData(mmd,mmd.getName(),"","");
                        mmd.setEmbeddedMetaData(emd);                           
                        AttributeOverride[] attributeOverride = (AttributeOverride[])annotationValues.get("value");
                        for (int j=0; j<attributeOverride.length; j++)
                        {
                            AbstractMemberMetaData apmd = mgr.getMetaDataFactory().newFieldObject(emd,
                                                                    attributeOverride[j].name(),
                                                                    null,null,null,
                                                                    null,null,null,
                                                                    null,null,null,
                                                                    null,null,null,
                                                                    null,null,null,
                                                                    null,null,null,
                                                                    null,null);
                            emd.addMember(apmd);
                            try
                            {
                                //needs to do the same for methods
                                Field overrideField = member.getType().getDeclaredField(attributeOverride[j].name());
                                apmd.addColumn(JPAAnnotationUtils.getColumnMetaDataForColumnAnnotation(apmd,
                                    new Member(overrideField), attributeOverride[j].column()));
                            }
                            catch (SecurityException e)
                            {
                                throw new JPOXException("Cannot obtain override field "+
                                    attributeOverride[j].name()+" of class "+member.getType()+
                                    " for persistent class "+cmd.getName(),e);
                            }
                            catch (NoSuchFieldException e)
                            {
                                throw new JPOXException("Override field "+attributeOverride[j].name()+
                                    " does not exist in class "+member.getType()+" for persistent class "+
                                    cmd.getName(),e);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.JOIN_TABLE))
                    {
                        // Process @JoinTable to generate JoinMetaData
                        mmd.setTable((String)annotationValues.get("name"));
                        mmd.setCatalog((String)annotationValues.get("catalog"));
                        mmd.setSchema((String)annotationValues.get("schema"));

                        joinmd = new JoinMetaData(mmd, null, null, null, null, null, null, null, null);
                        mmd.setJoinMetaData(joinmd);

                        if (annotationValues.get("joinColumns") != null)
                        {
                            ArrayList<JoinColumn> joinColumns = new ArrayList<JoinColumn>();
                            joinColumns.addAll(Arrays.asList((JoinColumn[])annotationValues.get("joinColumns")));
                            for (int j = 0; j < joinColumns.size(); j++)
                            {
                                ColumnMetaData colmd = new ColumnMetaData(joinmd,
                                    joinColumns.get(j).name(), joinColumns.get(j).referencedColumnName(),
                                    null, null, null, null, null,
                                    Boolean.valueOf(joinColumns.get(j).nullable()).toString(),
                                    null, null, null, null, null);
                                joinmd.addColumn(colmd);
                            }
                        }
                        if (annotationValues.get("inverseJoinColumns") != null)
                        {
                            ArrayList<JoinColumn> elementColumns = new ArrayList<JoinColumn>();
                            elementColumns.addAll(Arrays.asList((JoinColumn[])annotationValues.get("inverseJoinColumns")));
                            ElementMetaData elmd = new ElementMetaData(mmd, null, null, null, null, null, null);
                            mmd.setElementMetaData(elmd);
                            for (int j = 0; j < elementColumns.size(); j++)
                            {
                                ColumnMetaData colmd = new ColumnMetaData(elmd, elementColumns.get(j).name(),
                                    elementColumns.get(j).referencedColumnName(),
                                    null, null, null, null, null,
                                    Boolean.valueOf(elementColumns.get(j).nullable()).toString(),
                                    null, null, null, null, null);
                                elmd.addColumn(colmd);
                            }
                        }
                        UniqueConstraint[] joinUniqueConstraints = (UniqueConstraint[])annotationValues.get("uniqueConstraints");
                        if (joinUniqueConstraints != null && joinUniqueConstraints.length > 0)
                        {
                            // Unique constraints on the join table
                            for (int j=0;j<joinUniqueConstraints.length;j++)
                            {
                                UniqueMetaData unimd = new UniqueMetaData(null, null, null);
                                for (int k=0;k<joinUniqueConstraints[j].columnNames().length;k++)
                                {
                                    unimd.addColumn(new ColumnMetaData(unimd, joinUniqueConstraints[j].columnNames()[k]));
                                }
                                joinmd.setUniqueMetaData(unimd); // JDO only supports a single unique constraint on a join table
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.MAP_KEY))
                    {
                        String keyMappedBy = (String)annotationValues.get("name");
                        if (keyMappedBy != null)
                        {
                            mmd.setKeyMetaData(new KeyMetaData(mmd, null, null, null, null, null, keyMappedBy));
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ORDER_BY))
                    {
                        String orderBy = (String)annotationValues.get("value");
                        if (orderBy != null)
                        {
                            // "Ordered List"
                            mmd.setOrderMetaData(new OrderMetaData(orderBy));
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ONE_TO_MANY))
                    {
                        // 1-N relation
                        oneToMany = true;
                    }
                    else if (annName.equals(JPAAnnotationUtils.MANY_TO_MANY))
                    {
                        // M-N relation
                        manyToMany = true;
                    }
                }
            }

            // Post-processing to apply JPA rules for field relationships etc
            if (oneToMany && mmd.getJoinMetaData() == null && mmd.getMappedBy() == null &&
                !mgr.getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.jpa.oneToManyUniFkRelations"))
            {
                // 1-N with no join specified and unidirectional so JPA says it has to be via join (no 1-N uni FKs)
                mmd.setJoinMetaData(new JoinMetaData(mmd, null, null, null, null, null, null, null, null));
            }
            if (manyToMany && mmd.getJoinMetaData() == null && mmd.getMappedBy() == null)
            {
                // M-N with no join specified and unidir so add the join for them
                mmd.setJoinMetaData(new JoinMetaData(mmd, null, null, null, null, null, null, null, null));
            }

            if (mmd.getOrderMetaData() == null && Collection.class.isAssignableFrom(member.getType()))
            {
                // @OrderBy not specified but is a Collection so use ordering of element using PK field(s)
                mmd.setOrderMetaData(new OrderMetaData("#PK"));
            }

            if (columnMetaData == null)
            {
                // Column specified (at least in part) via @Column/@Lob/@Enumerated/@Temporal
                ColumnMetaData colmd = newColumnMetaData(mmd, member, annotations);
                if (colmd != null)
                {
                    columnMetaData = new ColumnMetaData[1];
                    columnMetaData[0] = colmd;
                }
            }
            if (columnMetaData != null)
            {
                // Column definition provided so apply to the respective place
                if ((mmd.hasCollection() || mmd.hasArray()) && joinmd==null)
                {
                    // Column is for the FK of the element of the collection/array
                    ElementMetaData elemmd = mmd.getElementMetaData();
                    if (elemmd == null)
                    {
                        elemmd = new ElementMetaData(mmd, null, null, null, null, null, null);
                        mmd.setElementMetaData(elemmd);
                    }
                    for (int i=0;i<columnMetaData.length;i++)
                    {
                        elemmd.addColumn(columnMetaData[i]);
                    }
                }
                else if (mmd.hasMap() && joinmd == null)
                {
                    // Column is for the FK value of the map
                    ValueMetaData valmd = mmd.getValueMetaData();
                    if (valmd == null)
                    {
                        valmd = new ValueMetaData(mmd, null, null, null, null, null,null);
                        mmd.setValueMetaData(valmd);
                    }
                    for (int i=0;i<columnMetaData.length;i++)
                    {
                        valmd.addColumn(columnMetaData[i]);
                    }
                }
                else
                {
                    // Column is for the field
                    for (int i=0;i<columnMetaData.length;i++)
                    {
                        mmd.addColumn(columnMetaData[i]);
                    }
                }
            }
            return mmd;
        }

        return null;
    }
   
    /**
     * Method to take the passed in outline ClassMetaData and process the annotations for
     * method adding any necessary MetaData to the ClassMetaData.
     * @param cmd The ClassMetaData (to be updated)
     * @param method The method
     */
    protected void processMethodAnnotations(AbstractClassMetaData cmd, Method method)
    {
        Annotation[] annotations = method.getAnnotations();

        EventListenerMetaData elmd = cmd.getListenerForClass(cmd.getFullClassName());
        if (elmd == null)
        {
            elmd = new EventListenerMetaData(cmd.getFullClassName());
            cmd.addListener(elmd);
        }

        if (annotations != null)
        {
            for (int i=0; i<annotations.length; i++)
            {
                String annotationTypeName = annotations[i].annotationType().getName();
                if (annotationTypeName.equals(PrePersist.class.getName()) ||
                    annotationTypeName.equals(PostPersist.class.getName()) ||
                    annotationTypeName.equals(PreRemove.class.getName()) ||
                    annotationTypeName.equals(PostRemove.class.getName()) ||
                    annotationTypeName.equals(PreUpdate.class.getName()) ||
                    annotationTypeName.equals(PostUpdate.class.getName()) ||
                    annotationTypeName.equals(PostLoad.class.getName()))
                {
                    elmd.addCallback(annotationTypeName, method.getDeclaringClass().getName(), method.getName());
                }
            }
        }
    }

    /**
     * Method to create a new field/property MetaData for the supplied annotations.
     * @param cmd MetaData for the class
     * @param field The field/method
     * @param annotations Annotations for the field/property
     * @return The MetaData for the field/property
     */
    private AbstractMemberMetaData newMetaDataForMember(AbstractClassMetaData cmd, Member field,
            AnnotationObject[] annotations)
    {
        String modifier = null;
        String dfg = null;
        String embedded = null;
        String pk = null;
        String version = null;
        String nullValue = null;
        String mappedBy = null;
        CascadeType[] cascades = null;
        HashSet<ExtensionMetaData> extensions = null;
        String valueStrategy = null;
        String valueGenerator = null;
        boolean storeInLob = false;
        Class targetEntity = null;

        for (int i=0;annotations != null && i<annotations.length;i++)
        {
            if (isSupportedAnnotation(annotations[i].getName()))
            {
                String annName = annotations[i].getName();
                HashMap<String, Object> annotationValues = annotations[i].getNameValueMap();
                if (annName.equals(JPAAnnotationUtils.EMBEDDED))
                {
                    embedded = "true";
                }
                else if (annName.equals(JPAAnnotationUtils.ID))
                {
                    pk = "true";
                    if (modifier == null)
                    {
                        modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.TRANSIENT))
                {
                    modifier = FieldPersistenceModifier.NONE.toString();
                }
                else if (annName.equals(JPAAnnotationUtils.ENUMERATED))
                {
                    if (modifier == null)
                    {
                        modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.VERSION))
                {
                    version = "true";
                    if (modifier == null)
                    {
                        modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.EMBEDDED_ID))
                {
                    pk = "true";
                    embedded = "true";
                    if (modifier == null)
                    {
                        modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.BASIC))
                {
                    FetchType fetch = (FetchType)annotationValues.get("fetch");
                    if (fetch == FetchType.LAZY)
                    {
                        dfg = "false";
                    }
                    else
                    {
                        dfg = "true";
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.ONE_TO_ONE))
                {
                    // 1-1 relation
                    modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    mappedBy = (String)annotationValues.get("mappedBy");
                    cascades = (CascadeType[])annotationValues.get("cascade");
                    targetEntity = (Class)annotationValues.get("targetEntity");
                }
                else if (annName.equals(JPAAnnotationUtils.ONE_TO_MANY))
                {
                    // 1-N relation
                    modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    mappedBy = (String)annotationValues.get("mappedBy");
                    cascades = (CascadeType[])annotationValues.get("cascade");
                    targetEntity = (Class)annotationValues.get("targetEntity");
                }
                else if (annName.equals(JPAAnnotationUtils.MANY_TO_MANY))
                {
                    // M-N relation
                    modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    mappedBy = (String)annotationValues.get("mappedBy");
                    cascades = (CascadeType[])annotationValues.get("cascade");
                    targetEntity = (Class)annotationValues.get("targetEntity");
                }
                else if (annName.equals(JPAAnnotationUtils.MANY_TO_ONE))
                {
                    // N-1 relation
                    modifier = FieldPersistenceModifier.PERSISTENT.toString();
                    mappedBy = (String)annotationValues.get("mappedBy");
                    cascades = (CascadeType[])annotationValues.get("cascade");
                    targetEntity = (Class)annotationValues.get("targetEntity");
                }
                else if (annName.equals(JPAAnnotationUtils.GENERATED_VALUE))
                {
                    GenerationType type = (GenerationType) annotationValues.get("strategy");
                    valueStrategy = JPAAnnotationUtils.getIdentityStrategyString(type);
                    valueGenerator = (String) annotationValues.get("generator");
                }
                else if (annName.equals(JPAAnnotationUtils.LOB))
                {
                    storeInLob = true;
                }
                else if (annName.equals(JPAAnnotationUtils.EXTENSION))
                {
                    ExtensionMetaData extmd = new ExtensionMetaData((String)annotationValues.get("vendorName"),
                        (String)annotationValues.get("key"), (String)annotationValues.get("value"));
                    extensions = new HashSet<ExtensionMetaData>(1);
                    extensions.add(extmd);
                }
                else if (annName.equals(JPAAnnotationUtils.EXTENSIONS))
                {
                    Extension[] values = (Extension[])annotationValues.get("value");
                    if (values != null && values.length > 0)
                    {
                        extensions = new HashSet<ExtensionMetaData>(values.length);
                        for (int j=0;j<values.length;j++)
                        {
                            ExtensionMetaData extmd = new ExtensionMetaData(values[j].vendorName(),
                                values[j].key().toString(), values[j].value().toString());
                            extensions.add(extmd);
                        }
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.SEQUENCE_GENERATOR))
                {
                    // Sequence generator, so store it against the package that we are under
                    processSequenceGeneratorAnnotation(cmd.getPackageMetaData(), annotationValues);
                }
                else if (annName.equals(JPAAnnotationUtils.TABLE_GENERATOR))
                {
                    // Table generator, so store it against the package that we are under
                    processTableGeneratorAnnotation(cmd.getPackageMetaData(), annotationValues);
                }
            }
        }

        if (JPAAnnotationUtils.isBasicByDefault(field.getType()) && modifier == null)
        {
            modifier = FieldPersistenceModifier.PERSISTENT.toString();
        }

        // Create the field
        AbstractMemberMetaData fmd;
        if (field.isProperty())
        {
            fmd = mgr.getMetaDataFactory().newPropertyObject(cmd, field.getName(), pk, modifier, dfg, nullValue,
                embedded, null, null, mappedBy, null, null, null, null, null, null, null, null, null,
                null, null, null, null);
        }
        else
        {
            fmd = mgr.getMetaDataFactory().newFieldObject(cmd, field.getName(), pk, modifier, dfg, nullValue,
                embedded, null, null, mappedBy, null, null, null, null, null, null, null, null, null,
                null, null, null);
        }

        if (version != null)
        {
            // Tag this field as the version field
            VersionMetaData vermd = new VersionMetaData(VersionStrategy.VERSION_NUMBER.toString(), fmd.getName());
            cmd.setVersionMetaData(vermd);
        }

        cmd.addMember(fmd);

        if (cascades != null)
        {
            for (int i = 0; i < cascades.length; i++)
            {
                if (cascades[i] == CascadeType.ALL)
                {
                    fmd.setCascadePersist(true);
                    fmd.setCascadeUpdate(true);
                    fmd.setCascadeDelete(true);
                    fmd.setCascadeRefresh(true);
                }
                else if (cascades[i] == CascadeType.PERSIST)
                {
                    fmd.setCascadePersist(true);
                }
                else if (cascades[i] == CascadeType.MERGE)
                {
                    fmd.setCascadeUpdate(true);
                }
                else if (cascades[i] == CascadeType.REMOVE)
                {
                    fmd.setCascadeDelete(true);
                }
                else if (cascades[i] == CascadeType.REFRESH)
                {
                    fmd.setCascadeRefresh(true);
                }
            }
        }

        // Value generation
        if (valueStrategy != null && valueGenerator != null)
        {
            fmd.setValueGeneratorName(valueGenerator);
        }
        if (valueStrategy != null)
        {
            fmd.setValueStrategy(IdentityStrategy.getIdentityStrategy(valueStrategy));
        }

        // Type storage
        if (storeInLob)
        {
            fmd.setStoreInLob();
        }

        // Container fields : If the field is a container then add its container element
        ContainerMetaData contmd = null;
        if (Collection.class.isAssignableFrom(field.getType()))
        {
            String elementType = null;
            if (targetEntity != null && targetEntity != void.class)
            {
                elementType = targetEntity.getName();
            }
            if (elementType == null)
            {
                elementType = ClassUtils.getCollectionElementType(field.getType(),field.getGenericType());
            }
            // No annotation for collections so cant specify the element type, dependent, embedded, serialized

            contmd = new CollectionMetaData(fmd, elementType, null, null, null);
        }
        else if (field.getType().isArray())
        {
            contmd = new ArrayMetaData(fmd, null, null, null, null);
        }
        else if (Map.class.isAssignableFrom(field.getType()))
        {
            String keyType = ClassUtils.getMapKeyType(field.getType(),field.getGenericType());
            String valueType = null;
            if (targetEntity != null && targetEntity != void.class)
            {
                valueType = targetEntity.getName();
            }
            if (valueType == null)
            {
                valueType = ClassUtils.getMapValueType(field.getType(),field.getGenericType());
            }
            // No annotation for maps so cant specify the key/value type, dependent, embedded, serialized
            contmd = new MapMetaData(fmd, keyType, null, null, null, valueType, null, null, null);
        }
        if (contmd != null)
        {
            fmd.setContainer(contmd);           
        }

        // JPOX extensions
        if (extensions != null)
        {
            Iterator<ExtensionMetaData> iter = extensions.iterator();
            while (iter.hasNext())
            {
                ExtensionMetaData extmd = iter.next();
                fmd.addExtension(extmd.getVendorName(), extmd.getKey(), extmd.getValue());
            }
        }

        return fmd;
    }

    /**
     * Method to create a new ColumnMetaData.
     * @param parent The parent MetaData object
     * @param field The field/property
     * @param annotations Annotations on this field/property
     * @return MetaData for the column
     */
    private ColumnMetaData newColumnMetaData(MetaData parent, Member field, AnnotationObject[] annotations)
    {
        String columnName= null;
        String target= null;
        String targetField= null;
        String jdbcType= null;
        String sqlType= null;
        String length= null;
        String scale= null;
        String allowsNull= null;
        String defaultValue= null;
        String insertValue= null;
        String insertable= null;
        String updateable= null;
        String unique= null;
        String table = null;

        for (int i=0;annotations != null && i<annotations.length;i++)
        {
            if (isSupportedAnnotation(annotations[i].getName()))
            {
                String annName = annotations[i].getName();
                HashMap<String, Object> annotationValues = annotations[i].getNameValueMap();
                if (annName.equals(JPAAnnotationUtils.COLUMN))
                {
                    columnName = (String)annotationValues.get("name");
                    if (field.getType().isPrimitive())
                    {
                        if (annotationValues.get("precision") != null)
                        {
                            int precisionValue = ((Integer)annotationValues.get("precision")).intValue();
                            if (precisionValue != 0)
                            {
                                length = "" + precisionValue;
                            }
                        }
                        if (annotationValues.get("scale") != null)
                        {
                            int scaleValue = ((Integer)annotationValues.get("scale")).intValue();
                            if (scaleValue != 0)
                            {
                                scale = "" + scaleValue;
                            }
                        }
                        if ((length == null || length.equals("0") ) && char.class.isAssignableFrom(field.getType()))
                        {
                            //in the TCK, char is stored by default in a CHAR column with 1 length
                            //if nothing defined, then default to this
                            length = "1";
                        }
                        if (field.getType() == boolean.class)
                        {
                            jdbcType = "SMALLINT";
                        }
                    }
                    else if (String.class.isAssignableFrom(field.getType()))
                    {
                        if (annotationValues.get("length") != null)
                        {
                            length = annotationValues.get("length").toString();
                        }
                    }
                    else if (Number.class.isAssignableFrom(field.getType()))
                    {
                        if (annotationValues.get("precision") != null)
                        {
                            length = annotationValues.get("precision").toString();
                        }
                        if (annotationValues.get("scale") != null)
                        {
                            scale = annotationValues.get("scale").toString();
                        }
                    }
                    if (annotationValues.get("nullable") != null)
                    {
                        allowsNull = annotationValues.get("nullable").toString();
                    }
                    if (annotationValues.get("insertable") != null)
                    {
                        insertable = annotationValues.get("insertable").toString();
                    }
                    if (annotationValues.get("updatable") != null)
                    {
                        // Note : "updatable" is spelt incorrectly in the JPA spec.
                        updateable = annotationValues.get("updatable").toString();
                    }
                    if (annotationValues.get("unique") != null)
                    {
                        unique = annotationValues.get("unique").toString();
                    }
                    if (annotationValues.get("table") != null)
                    {
                        // Column in secondary-table
                        String columnTable = (String)annotationValues.get("table");
                        if (!StringUtils.isWhitespace(columnTable))
                        {
                            table = columnTable;
                        }
                    }
                }
                else if (Enum.class.isAssignableFrom(field.getType()) && annName.equals(JPAAnnotationUtils.ENUMERATED))
                {
                    EnumType type = (EnumType)annotationValues.get("value");
                    jdbcType = (type == EnumType.STRING ? "VARCHAR" : "INTEGER");
                }
                else if (JPAAnnotationUtils.isTemporalType(field.getType()) && annName.equals(JPAAnnotationUtils.TEMPORAL_TYPE))
                {
                    TemporalType type = (TemporalType)annotationValues.get("value");
                    if (type == TemporalType.DATE)
                    {
                        jdbcType = "DATE";
                    }
                    else if (type == TemporalType.TIME)
                    {
                        jdbcType = "TIME";
                    }
                    else if (type == TemporalType.TIMESTAMP)
                    {
                        jdbcType = "TIMESTAMP";
                    }
                }
            }
        }
        if (columnName == null && length == null && scale == null && insertable == null && updateable == null &&
            allowsNull == null && unique == null && jdbcType == null && sqlType == null)
        {
            return null;
        }

        ColumnMetaData colmd = new ColumnMetaData(parent, columnName, target, targetField, jdbcType, sqlType, length,
            scale, allowsNull, defaultValue, insertValue, insertable, updateable, unique);
        if (parent instanceof AbstractMemberMetaData)
        {
            AbstractMemberMetaData apmd = (AbstractMemberMetaData) parent;
            apmd.setTable(table);
            // apmd.addColumn(colmd);
            // update column settings if primary key, cannot be null
            colmd.setAllowsNull(new Boolean(apmd.isPrimaryKey() ? false : colmd.isAllowsNull()));
        }
        return colmd;
        // TODO Support columnDefinition
    }

    /**
     * Method to create a new JoinMetaData for a secondary table.
     * @param cmd MetaData for the class
     * @param annotations Annotations on the class
     * @return The join metadata
     */
    private JoinMetaData[] newJoinMetaDataForClass(AbstractClassMetaData cmd, AnnotationObject[] annotations)
    {
        HashSet<JoinMetaData> joins = new HashSet<JoinMetaData>();
        for (int i=0;annotations != null && i<annotations.length;i++)
        {
            String annName = annotations[i].getName();
            HashMap<String, Object> annotationValues = annotations[i].getNameValueMap();
            if (annName.equals(JPAAnnotationUtils.SECONDARY_TABLES))
            {
                SecondaryTable[] secTableAnns = (SecondaryTable[])annotationValues.get("value");
                if (secTableAnns != null)
                {
                    for (int j=0;j<secTableAnns.length;j++)
                    {
                        JoinMetaData joinmd = new JoinMetaData(cmd,
                            secTableAnns[j].name(), secTableAnns[j].catalog(), secTableAnns[j].schema(),
                            null, null, null, null, null);
                        PrimaryKeyJoinColumn[] pkJoinCols = secTableAnns[j].pkJoinColumns();
                        if (pkJoinCols != null)
                        {
                            for (int k = 0; k < pkJoinCols.length; k++)
                            {
                                ColumnMetaData colmd = new ColumnMetaData(joinmd, pkJoinCols[k].name(),
                                    pkJoinCols[k].referencedColumnName(),
                                    null, null, null, null, null, null, null, null, null, null, null);
                                joinmd.addColumn(colmd);
                            }
                        }
                        joins.add(joinmd);
                        cmd.addJoin(joinmd);

                        UniqueConstraint[] constrs = secTableAnns[j].uniqueConstraints();
                        if (constrs != null && constrs.length > 0)
                        {
                            for (int k=0;k<constrs.length;k++)
                            {
                                UniqueMetaData unimd = new UniqueMetaData(null, (String)annotationValues.get("table"), null);
                                for (int l=0;l<constrs[k].columnNames().length;l++)
                                {
                                    unimd.addColumn(new ColumnMetaData(unimd, constrs[k].columnNames()[l]));
                                }
                                joinmd.setUniqueMetaData(unimd); // JDO only allows one unique
                            }
                        }
                    }
                }
            }
            else if (annName.equals(JPAAnnotationUtils.SECONDARY_TABLE))
            {
                JoinMetaData joinmd = new JoinMetaData(cmd,
                    (String)annotationValues.get("name"), (String)annotationValues.get("catalog"),
                    (String)annotationValues.get("schema"), null, null, null, null, null);
                if (annotationValues.get("pkJoinColumns") != null)
                {
                    PrimaryKeyJoinColumn[] joinCols = (PrimaryKeyJoinColumn[])annotationValues.get("pkJoinColumns");
                    for (int j = 0; j < joinCols.length; j++)
                    {
                        ColumnMetaData colmd = new ColumnMetaData(joinmd, joinCols[j].name(),
                            joinCols[j].referencedColumnName(),
                            null, null, null, null, null, null, null, null, null, null, null);
                        joinmd.addColumn(colmd);
                    }
                }
                joins.add(joinmd);
                cmd.addJoin(joinmd);

                UniqueConstraint[] constrs = (UniqueConstraint[])annotationValues.get("uniqueConstraints");
                if (constrs != null && constrs.length > 0)
                {
                    for (int j=0;j<constrs.length;j++)
                    {
                        UniqueMetaData unimd = new UniqueMetaData(null, (String)annotationValues.get("table"), null);
                        for (int k=0;k<constrs[j].columnNames().length;k++)
                        {
                            unimd.addColumn(new ColumnMetaData(unimd, constrs[j].columnNames()[k]));
                        }
                        joinmd.setUniqueMetaData(unimd); // JDO only allows one unique
                    }
                }
            }
        }
        return (JoinMetaData[])joins.toArray(new JoinMetaData[joins.size()]);
    }

    /**
     * Process a @SequenceGenerator annotation.
     * @param pmd Package MetaData to add the sequence to
     * @param annotationValues The annotation info
     */
    private void processSequenceGeneratorAnnotation(PackageMetaData pmd, HashMap<String, Object> annotationValues)
    {
        // Sequence generator, so store it against the package that we are under
        String name = (String)annotationValues.get("name");
        String seqName = (String)annotationValues.get("sequenceName");
        Integer initialValue = (Integer)annotationValues.get("initialValue");
        if (initialValue == null)
        {
            initialValue = new Integer(1); // JPA default
        }
        Integer allocationSize = (Integer)annotationValues.get("allocationSize");
        if (allocationSize == null)
        {
            allocationSize = new Integer(50); // JPA default
        }
        SequenceMetaData seqmd = new SequenceMetaData(pmd, name, seqName, null, null,
            "" + initialValue.intValue(), "" + allocationSize.intValue());
        pmd.addSequence(seqmd);
    }

    /**
     * Process a @TableGenerator annotation and add it to the specified package MetaData.
     * @param pmd Package MetaData to add the table generator to
     * @param annotationValues The annotation info
     */
    private void processTableGeneratorAnnotation(PackageMetaData pmd, HashMap<String, Object> annotationValues)
    {
        String name = (String)annotationValues.get("name");
        String tgTable = (String)annotationValues.get("table");
        String tgCatalog = (String)annotationValues.get("catalog");
        String tgSchema = (String)annotationValues.get("schema");
        String tgPKColumnName = (String)annotationValues.get("pkColumnName");
        String tgValueColumnName = (String)annotationValues.get("valueColumnName");
        String tgPKColumnValue = (String)annotationValues.get("pkColumnValue");
        Integer initialValue = (Integer)annotationValues.get("initialValue");
        Integer allocationSize = (Integer)annotationValues.get("allocationSize");
        TableGeneratorMetaData tgmd = new TableGeneratorMetaData(pmd, name, tgTable,
            tgCatalog, tgSchema, tgPKColumnName, tgValueColumnName, tgPKColumnValue,
            "" + initialValue.intValue(), "" + allocationSize.intValue());
        pmd.addTableGenerator(tgmd);
        // TODO Support uniqueConstraints
    }

    /**
     * Check if class is persistence capable, by looking at annotations
     * @param cls the Class
     * @return true if the class has Entity annotation
     */
    protected boolean isClassPersistenceCapable(Class cls)
    {
        AnnotationObject[] annotations = getClassAnnotationsForClass(cls);
        for (int i = 0; i < annotations.length; i++)
        {
            String annClassName = annotations[i].getName();
            if (annClassName.equals(JPAAnnotationUtils.ENTITY))
            {
                return true;
            }
            else if (annClassName.equals(JPAAnnotationUtils.EMBEDDABLE))
            {
                return true;
            }
            else if (annClassName.equals(JPAAnnotationUtils.MAPPED_SUPERCLASS))
            {
                Field[] fields = cls.getDeclaredFields();
                for (int j = 0; j < fields.length; j++)
                {
                    if (fields[j].getAnnotation(Id.class) != null)
                    {
                        return true;
                    }
                    if (fields[j].getAnnotation(EmbeddedId.class) != null)
                    {
                        return true;
                    }
                }
                Method[] methods = cls.getDeclaredMethods();
                for (int j = 0; j < methods.length; j++)
                {
                    if (methods[j].getAnnotation(Id.class) != null)
                    {
                        return true;
                    }
                    if (methods[j].getAnnotation(EmbeddedId.class) != null)
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}
TOP

Related Classes of org.jpox.jpa.metadata.JPAAnnotationReader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.