Package org.datanucleus.api.jpa.metadata

Source Code of org.datanucleus.api.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.datanucleus.api.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.EntityResult;
import javax.persistence.EnumType;
import javax.persistence.FetchType;
import javax.persistence.FieldResult;
import javax.persistence.GenerationType;
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.datanucleus.ClassLoaderResolver;
import org.datanucleus.api.jpa.annotations.Extension;
import org.datanucleus.api.jpa.annotations.FetchGroup;
import org.datanucleus.api.jpa.annotations.FetchMember;
import org.datanucleus.api.jpa.annotations.FetchPlan;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractElementMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ArrayMetaData;
import org.datanucleus.metadata.ClassMetaData;
import org.datanucleus.metadata.ClassPersistenceModifier;
import org.datanucleus.metadata.CollectionMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.ContainerMetaData;
import org.datanucleus.metadata.DiscriminatorMetaData;
import org.datanucleus.metadata.ElementMetaData;
import org.datanucleus.metadata.EmbeddedMetaData;
import org.datanucleus.metadata.EventListenerMetaData;
import org.datanucleus.metadata.ExtensionMetaData;
import org.datanucleus.metadata.FetchGroupMetaData;
import org.datanucleus.metadata.FetchPlanMetaData;
import org.datanucleus.metadata.FieldMetaData;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.IdentityMetaData;
import org.datanucleus.metadata.IdentityStrategy;
import org.datanucleus.metadata.InheritanceMetaData;
import org.datanucleus.metadata.InheritanceStrategy;
import org.datanucleus.metadata.JoinMetaData;
import org.datanucleus.metadata.KeyMetaData;
import org.datanucleus.metadata.MapMetaData;
import org.datanucleus.metadata.MetaData;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.metadata.NullValue;
import org.datanucleus.metadata.OrderMetaData;
import org.datanucleus.metadata.PackageMetaData;
import org.datanucleus.metadata.PrimaryKeyMetaData;
import org.datanucleus.metadata.PropertyMetaData;
import org.datanucleus.metadata.QueryLanguage;
import org.datanucleus.metadata.QueryMetaData;
import org.datanucleus.metadata.QueryResultMetaData;
import org.datanucleus.metadata.SequenceMetaData;
import org.datanucleus.metadata.TableGeneratorMetaData;
import org.datanucleus.metadata.UniqueMetaData;
import org.datanucleus.metadata.ValueMetaData;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.metadata.VersionStrategy;
import org.datanucleus.metadata.annotations.AbstractAnnotationReader;
import org.datanucleus.metadata.annotations.AnnotationObject;
import org.datanucleus.metadata.annotations.Member;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
* Implementation for Annotation Reader for JDK 1.5 annotations using JPA's definition.
* This reader also accepts certain DataNucleus extensions where the JPA annotations don't provide
* full definition of the data required.
*/
public class JPAAnnotationReader extends AbstractAnnotationReader
{
    ClassLoaderResolver clr = null;

    /**
     * Constructor.
     * @param mgr MetaData manager
     */
    public JPAAnnotationReader(MetaDataManager mgr)
    {
        super(mgr);

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

    /**
     * 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)
    {
        this.clr = clr;
        ClassMetaData cmd = null;

        if (annotations != null && annotations.length > 0)
        {
            if (isClassPersistenceCapable(cls))
            {
                cmd = pmd.newClassMetadata(ClassUtils.getClassNameForClass(cls));
                cmd.setPersistenceModifier(ClassPersistenceModifier.PERSISTENCE_CAPABLE);
            }
            else if (isClassPersistenceAware(cls))
            {
                cmd = pmd.newClassMetadata(ClassUtils.getClassNameForClass(cls));
                cmd.setPersistenceModifier(ClassPersistenceModifier.PERSISTENCE_AWARE);
            }

            if (cmd != null)
            {
                IdentityType identityType = IdentityType.APPLICATION;
                String identityColumn = null;
                String identityStrategy = null;
                String identityGenerator = null;

                String cacheable = "true";
                String requiresExtent = "true";
                String detachable = "true"; // In JPA default is true.
                String embeddedOnly = "false";
                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 discriminatorColumnDdl = 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;
                FetchPlanMetaData[] fetchPlans = null;
                FetchGroupMetaData[] fetchGroups = null;
                HashSet<ExtensionMetaData> extensions = null;

                String jpaLevel = mgr.getNucleusContext().getPersistenceConfiguration().getStringProperty("datanucleus.jpa.level");
                for (int i=0;i<annotations.length;i++)
                {
                    HashMap<String, Object> annotationValues = annotations[i].getNameValueMap();
                    String annName = annotations[i].getName();
                    if (annName.equals(JPAAnnotationUtils.ENTITY))
                    {
                        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))
                        {
                            inheritanceStrategy = InheritanceStrategy.SUBCLASS_TABLE.toString();
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.DATASTORE_IDENTITY) && jpaLevel.equalsIgnoreCase("DataNucleus"))
                    {
                        // extension to allow datastore-identity
                        identityType = IdentityType.DATASTORE;
                        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();
                                unimd.setTable((String)annotationValues.get("name"));
                                for (int k=0;k<constrs[j].columnNames().length;k++)
                                {
                                    ColumnMetaData colmd = new ColumnMetaData();
                                    colmd.setName(constrs[j].columnNames()[k]);
                                    unimd.addColumn(colmd);
                                }
                                if (uniques == null)
                                {
                                    uniques = new HashSet<UniqueMetaData>();
                                }
                                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 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");
                        String tmp = (String)annotationValues.get("columnDefinition");
                        if (!StringUtils.isWhitespace(tmp))
                        {
                            discriminatorColumnDdl = tmp;
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.DISCRIMINATOR_VALUE))
                    {
                        discriminatorValue = (String)annotationValues.get("value");
                    }
                    else if (annName.equals(JPAAnnotationUtils.EMBEDDABLE))
                    {
                        embeddedOnly = "true";
                        identityType = IdentityType.NONDURABLE;
                    }
                    else if (annName.equals(JPAAnnotationUtils.CACHEABLE))
                    {
                        Boolean cacheableVal = (Boolean)annotationValues.get("value");
                        if (cacheableVal == Boolean.FALSE)
                        {
                            cacheable = "false";
                        }
                    }
                    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();
                        pkColumnMetaData[0].setName((String)annotationValues.get("name"));
                        pkColumnMetaData[0].setTarget((String)annotationValues.get("referencedColumnName"));
                    }
                    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++)
                        {
                            pkColumnMetaData[j] = new ColumnMetaData();
                            pkColumnMetaData[j].setName(values[j].name());
                            pkColumnMetaData[j].setTarget(values[j].referencedColumnName());
                            if (!StringUtils.isWhitespace(values[j].columnDefinition()))
                            {
                                pkColumnMetaData[j].setColumnDdl(values[j].columnDefinition());
                            }
                        }
                    }
                    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 = new FieldMetaData(cmd,
                                    "#UNKNOWN." + overrides[j].name());
                                fmd.setPersistenceModifier(FieldPersistenceModifier.PERSISTENT.toString());
                                Column col = overrides[j].column();
                                // TODO Make inferrals about jdbctype, length etc if the field is 1 char etc
                                ColumnMetaData colmd = new ColumnMetaData();
                                colmd.setName(col.name());
                                colmd.setLength(col.length());
                                colmd.setScale(col.scale());
                                colmd.setAllowsNull(col.nullable());
                                colmd.setInsertable(col.insertable());
                                colmd.setUpdateable(col.updatable());
                                colmd.setUnique(col.unique());
                                fmd.addColumn(colmd);
                                overriddenFields.add(fmd);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ATTRIBUTE_OVERRIDE))
                    {
                        if (overriddenFields == null)
                        {
                            overriddenFields = new HashSet<AbstractMemberMetaData>();
                        }

                        AbstractMemberMetaData fmd = new FieldMetaData(cmd,
                            "#UNKNOWN." + (String)annotationValues.get("name"));
                        Column col = (Column)annotationValues.get("column");
                        // TODO Make inferrals about jdbctype, length etc if the field is 1 char etc
                        ColumnMetaData colmd = new ColumnMetaData();
                        colmd.setName(col.name());
                        colmd.setLength(col.length());
                        colmd.setScale(col.scale());
                        colmd.setAllowsNull(col.nullable());
                        colmd.setInsertable(col.insertable());
                        colmd.setUpdateable(col.updatable());
                        colmd.setUnique(col.unique());
                        fmd.addColumn(colmd);
                        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 = new FieldMetaData(cmd,
                                    "#UNKNOWN." + overrides[j].name());
                                JoinColumn[] cols = overrides[j].joinColumns();
                                for (int k=0;k<cols.length;k++)
                                {
                                    ColumnMetaData colmd = new ColumnMetaData();
                                    colmd.setName(cols[k].name());
                                    colmd.setTarget(cols[k].referencedColumnName());
                                    colmd.setAllowsNull(cols[k].nullable());
                                    colmd.setInsertable(cols[k].insertable());
                                    colmd.setUpdateable(cols[k].updatable());
                                    colmd.setUnique(cols[k].unique());
                                    fmd.addColumn(colmd);
                                }
                                overriddenFields.add(fmd);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.ASSOCIATION_OVERRIDE))
                    {
                        if (overriddenFields == null)
                        {
                            overriddenFields = new HashSet<AbstractMemberMetaData>();
                        }

                        AbstractMemberMetaData fmd = new FieldMetaData(cmd,
                            "#UNKNOWN." + (String)annotationValues.get("name"));
                        JoinColumn[] cols = (JoinColumn[])annotationValues.get("joinColumns");
                        for (int k=0;k<cols.length;k++)
                        {
                            ColumnMetaData colmd = new ColumnMetaData();
                            colmd.setName(cols[k].name());
                            colmd.setTarget(cols[k].referencedColumnName());
                            colmd.setAllowsNull(cols[k].nullable());
                            colmd.setInsertable(cols[k].insertable());
                            colmd.setUpdateable(cols[k].updatable());
                            colmd.setUnique(cols[k].unique());
                            fmd.addColumn(colmd);
                        }
                        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(queries[j].name());
                            qmd.setLanguage(QueryLanguage.JPQL.toString());
                            qmd.setUnmodifiable(true);
                            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((String)annotationValues.get("name"));
                        qmd.setLanguage(QueryLanguage.JPQL.toString());
                        qmd.setUnmodifiable(true);
                        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(queries[j].name());
                            qmd.setLanguage(QueryLanguage.SQL.toString());
                            qmd.setUnmodifiable(true);
                            qmd.setResultClass(resultClassName);
                            qmd.setResultMetaDataName(resultMappingName);
                            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((String)annotationValues.get("name"));
                        qmd.setLanguage(QueryLanguage.SQL.toString());
                        qmd.setUnmodifiable(true);
                        qmd.setResultClass(resultClassName);
                        qmd.setResultMetaDataName(resultMappingName);
                        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(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((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.FETCHPLANS))
                    {
                        if (fetchPlans != null)
                        {
                            NucleusLogger.METADATA.warn(LOCALISER.msg("044207", cmd.getFullClassName()));
                        }
                        FetchPlan[] plans = (FetchPlan[])annotationValues.get("value");
                        fetchPlans = new FetchPlanMetaData[plans.length];
                        for (int j=0;j<plans.length;j++)
                        {
                            fetchPlans[j] = new FetchPlanMetaData(plans[j].name());
                            fetchPlans[j].setMaxFetchDepth(plans[j].maxFetchDepth());
                            fetchPlans[j].setFetchSize(plans[j].fetchSize());
                            int numGroups = plans[j].fetchGroups().length;
                            for (int k=0;k<numGroups;k++)
                            {
                                FetchGroupMetaData fgmd = new FetchGroupMetaData(plans[j].fetchGroups()[k]);
                                fetchPlans[j].addFetchGroup(fgmd);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.FETCHPLAN))
                    {
                        if (fetchPlans != null)
                        {
                            NucleusLogger.METADATA.warn(LOCALISER.msg("044207", cmd.getFullClassName()));
                        }
                        fetchPlans = new FetchPlanMetaData[1];
                        int maxFetchDepth = ((Integer)annotationValues.get("maxFetchDepth")).intValue();
                        int fetchSize = ((Integer)annotationValues.get("fetchSize")).intValue();
                        fetchPlans[0] = new FetchPlanMetaData((String)annotationValues.get("name"));
                        fetchPlans[0].setMaxFetchDepth(maxFetchDepth);
                        fetchPlans[0].setFetchSize(fetchSize);
                    }
                    else if (annName.equals(JPAAnnotationUtils.FETCHGROUPS))
                    {
                        if (fetchGroups != null)
                        {
                            NucleusLogger.METADATA.warn(LOCALISER.msg("044208", cmd.getFullClassName()));
                        }
                        FetchGroup[] groups = (FetchGroup[])annotationValues.get("value");
                        fetchGroups = new FetchGroupMetaData[groups.length];
                        for (int j=0;j<groups.length;j++)
                        {
                            fetchGroups[j] = new FetchGroupMetaData(groups[j].name());
                            fetchGroups[j].setPostLoad(groups[j].postLoad());
                            int numFields = groups[j].members().length;
                            for (int k=0;k<numFields;k++)
                            {
                                FieldMetaData fmd = new FieldMetaData(fetchGroups[j],
                                    groups[j].members()[k].value());
                                fmd.setRecursionDepth(groups[j].members()[k].recursionDepth());
                                fetchGroups[j].addMember(fmd);
                            }
                            int numGroups = groups[j].fetchGroups().length;
                            for (int k=0;k<numGroups;k++)
                            {
                                FetchGroupMetaData subgrp = new FetchGroupMetaData(groups[j].fetchGroups()[k]);
                                fetchGroups[j].addFetchGroup(subgrp);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.FETCHGROUP))
                    {
                        if (fetchGroups != null)
                        {
                            NucleusLogger.METADATA.warn(LOCALISER.msg("044208", cmd.getFullClassName()));
                        }
                        fetchGroups = new FetchGroupMetaData[1];
                        fetchGroups[0] = new FetchGroupMetaData((String)annotationValues.get("name"));
                        fetchGroups[0].setPostLoad((String)annotationValues.get("postLoad"));
                        FetchMember[] fields = (FetchMember[])annotationValues.get("members");
                        if (fields != null)
                        {
                            for (int j=0;j<fields.length;j++)
                            {
                                FieldMetaData fmd = new FieldMetaData(fetchGroups[0],
                                    fields[j].value());
                                fmd.setRecursionDepth(fields[j].recursionDepth());
                                fetchGroups[0].addMember(fmd);
                            }
                        }
                    }
                    else if (annName.equals(JPAAnnotationUtils.EXTENSION))
                    {
                        // extension
                        ExtensionMetaData extmd = new ExtensionMetaData((String)annotationValues.get("vendorName"),
                            (String)annotationValues.get("key"), (String)annotationValues.get("value"));
                        if (extensions == null)
                        {
                            extensions = new HashSet<ExtensionMetaData>(1);
                        }
                        extensions.add(extmd);
                    }
                    else if (annName.equals(JPAAnnotationUtils.EXTENSIONS))
                    {
                        // extension
                        Extension[] values = (Extension[])annotationValues.get("value");
                        if (values != null && values.length > 0)
                        {
                            if (extensions == null)
                            {
                                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
                    {
                        NucleusLogger.METADATA.error(LOCALISER.msg("044203",
                            cls.getName(), annotations[i].getName()));
                    }
                }

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

                NucleusLogger.METADATA.info(LOCALISER.msg("044200", cls.getName(), "JPA"));

                cmd.setTable(table);
                cmd.setCatalog(catalog);
                cmd.setSchema(schema);
                cmd.setEntityName(entityName);
                cmd.setDetachable(detachable);
                cmd.setRequiresExtent(requiresExtent);
                cmd.setObjectIdClass(idClassName);
                cmd.setEmbeddedOnly(embeddedOnly);
                cmd.setCacheable(cacheable);
                cmd.setIdentityType(identityType);
                if (isClassPersistenceCapable(cls.getSuperclass()))
                {
                    cmd.setPersistenceCapableSuperclass(cls.getSuperclass().getName());
                }
                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);
                    }
                }

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

                if (discriminatorValue != null || discriminatorColumnName != null ||
                    discriminatorColumnLength != null || discriminatorColumnType != null)
                {
                    // Add discriminator information to the inheritance of this class
                    DiscriminatorMetaData dismd = inhmd.newDiscriminatorMetadata();
                    if (discriminatorValue != null)
                    {
                        // Value specified so assumed to be value-map
                        dismd.setColumnName(discriminatorColumnName);
                        dismd.setValue(discriminatorValue).setStrategy("value-map").setIndexed("false");
                    }
                    else
                    {
                        // No value so use class-name
                        discriminatorValue = cls.getName();
                        dismd.setColumnName(discriminatorColumnName);
                        dismd.setValue(discriminatorValue).setStrategy("value-map").setIndexed("false");
                    }

                    ColumnMetaData discolmd = null;
                    if (discriminatorColumnLength != null || discriminatorColumnName != null || discriminatorColumnType != null)
                    {
                        discolmd = new ColumnMetaData();
                        discolmd.setName(discriminatorColumnName);
                        if (discriminatorColumnType != null)
                        {
                            discolmd.setJdbcType(discriminatorColumnType);
                        }
                        if (discriminatorColumnLength != null)
                        {
                            discolmd.setLength(discriminatorColumnLength);
                        }
                        dismd.setColumnMetaData(discolmd);
                        if (discriminatorColumnDdl != null)
                        {
                            discolmd.setColumnDdl(discriminatorColumnDdl);
                        }
                    }
                }

                // extension - datastore-identity
                if (identityType == IdentityType.DATASTORE)
                {
                    IdentityMetaData idmd = cmd.newIdentityMetadata();
                    idmd.setColumnName(identityColumn);
                    idmd.setValueStrategy(IdentityStrategy.getIdentityStrategy(identityStrategy));
                    if (identityGenerator != null)
                    {
                        idmd.setSequence(identityGenerator);
                        idmd.setValueGeneratorName(identityGenerator);
                    }
                }

                if (pkColumnMetaData != null)
                {
                    // PK columns overriding those in the root class
                    PrimaryKeyMetaData pkmd = cmd.newPrimaryKeyMetadata();
                    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 "datanucleus"
        if (member.getName().startsWith("jdo"))
        {
            // ignore JDO fields/methods added during enhancement
            return null;
        }

        String jpaLevel = mgr.getNucleusContext().getPersistenceConfiguration().getStringProperty("datanucleus.jpa.level");
        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);

            // Check if this is marked as an element collection
            boolean elementCollection = false;
            for (int i=0;i<annotations.length;i++)
            {
                String annName = annotations[i].getName();
                if (annName.equals(JPAAnnotationUtils.ELEMENT_COLLECTION))
                {
                    elementCollection = true;
                    break;
                }
            }

            // Process other annotations
            ColumnMetaData[] columnMetaData = null;
            JoinMetaData joinmd = null;
            KeyMetaData keymd = null;
            boolean oneToMany = false;
            boolean manyToMany = false;
            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.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();
                            columnMetaData[j].setName(cols[j].name());
                            columnMetaData[j].setTarget(cols[j].referencedColumnName());
                            columnMetaData[j].setAllowsNull(cols[j].nullable());
                            columnMetaData[j].setInsertable(cols[j].insertable());
                            columnMetaData[j].setUpdateable(cols[j].updatable());
                            columnMetaData[j].setUnique(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();
                    columnMetaData[0].setName((String)annotationValues.get("name"));
                    columnMetaData[0].setTarget((String)annotationValues.get("referencedColumnName"));
                    columnMetaData[0].setAllowsNull(colNullable);
                    columnMetaData[0].setInsertable(colInsertable);
                    columnMetaData[0].setUpdateable(colUpdateable);
                    columnMetaData[0].setUnique(colUnique);
                }
                else if (annName.equals(JPAAnnotationUtils.PRIMARY_KEY_JOIN_COLUMNS))
                {
                    // 1-1 FK columns
                    PrimaryKeyJoinColumn[] cols = (PrimaryKeyJoinColumn[])annotationValues.get("value");
                    if (cols != null)
                    {
                        columnMetaData = new ColumnMetaData[cols.length];
                        for (int j=0;j<cols.length;j++)
                        {
                            columnMetaData[j] = new ColumnMetaData();
                            columnMetaData[j].setName(cols[j].name());
                            columnMetaData[j].setTarget(cols[j].referencedColumnName());
                        }
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.PRIMARY_KEY_JOIN_COLUMN))
                {
                    // 1-1 FK column
                    columnMetaData = new ColumnMetaData[1];
                    columnMetaData[0] = new ColumnMetaData();
                    columnMetaData[0].setName((String)annotationValues.get("name"));
                    columnMetaData[0].setTarget((String)annotationValues.get("referencedColumnName"));
                }
                else if (annName.equals(JPAAnnotationUtils.ATTRIBUTE_OVERRIDES) && mmd.isEmbedded())
                {
                    // Embedded field overrides
                    AttributeOverride[] attributeOverride = (AttributeOverride[])annotationValues.get("value");
                    for (int j=0; j<attributeOverride.length; j++)
                    {
                        processEmbeddedAttributeOverride(mmd, attributeOverride[j].name(),
                            member.getType(), attributeOverride[j].column());
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.ATTRIBUTE_OVERRIDE) && mmd.isEmbedded())
                {
                    // Embedded field override
                    processEmbeddedAttributeOverride(mmd, (String)annotationValues.get("name"),
                        member.getType(), (Column)annotationValues.get("column"));
                }
                else if (annName.equals(JPAAnnotationUtils.JOIN_TABLE))
                {
                    // Process @JoinTable to generate JoinMetaData
                    String tableName = (String)annotationValues.get("name");
                    if (!StringUtils.isWhitespace(tableName))
                    {
                        mmd.setTable(tableName);
                    }
                    String catalogName = (String)annotationValues.get("catalog");
                    if (!StringUtils.isWhitespace(catalogName))
                    {
                        mmd.setCatalog(catalogName);
                    }
                    String schemaName = (String)annotationValues.get("schema");
                    if (!StringUtils.isWhitespace(schemaName))
                    {
                        mmd.setSchema(schemaName);
                    }

                    joinmd = new JoinMetaData();
                    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();
                            colmd.setName(joinColumns.get(j).name());
                            colmd.setTarget(joinColumns.get(j).referencedColumnName());
                            colmd.setAllowsNull(joinColumns.get(j).nullable());
                            joinmd.addColumn(colmd);
                        }
                    }
                    if (annotationValues.get("inverseJoinColumns") != null)
                    {
                        ArrayList<JoinColumn> elementColumns = new ArrayList<JoinColumn>();
                        elementColumns.addAll(Arrays.asList((JoinColumn[])annotationValues.get("inverseJoinColumns")));
                        AbstractElementMetaData aemd = null;
                        if (Map.class.isAssignableFrom(member.getType()))
                        {
                            aemd = new ValueMetaData();
                            mmd.setValueMetaData((ValueMetaData)aemd);
                        }
                        else
                        {
                            aemd = new ElementMetaData();
                            mmd.setElementMetaData((ElementMetaData)aemd);
                        }
                        for (int j = 0; j < elementColumns.size(); j++)
                        {
                            ColumnMetaData colmd = new ColumnMetaData();
                            colmd.setName(elementColumns.get(j).name());
                            colmd.setTarget(elementColumns.get(j).referencedColumnName());
                            colmd.setAllowsNull(elementColumns.get(j).nullable());
                            aemd.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();
                            for (int k=0;k<joinUniqueConstraints[j].columnNames().length;k++)
                            {
                                ColumnMetaData colmd = new ColumnMetaData();
                                colmd.setName(joinUniqueConstraints[j].columnNames()[k]);
                                unimd.addColumn(colmd);
                            }
                            joinmd.setUniqueMetaData(unimd); // JDO only supports a single unique constraint on a join table
                        }
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.COLLECTION_TABLE))
                {
                    // Process @CollectionTable to generate JoinMetaData
                    String tableName = (String)annotationValues.get("name");
                    if (!StringUtils.isWhitespace(tableName))
                    {
                        mmd.setTable(tableName);
                    }
                    String catalogName = (String)annotationValues.get("catalog");
                    if (!StringUtils.isWhitespace(catalogName))
                    {
                        mmd.setCatalog(catalogName);
                    }
                    String schemaName = (String)annotationValues.get("schema");
                    if (!StringUtils.isWhitespace(schemaName))
                    {
                        mmd.setSchema(schemaName);
                    }

                    joinmd = mmd.getJoinMetaData();
                    if (joinmd == null)
                    {
                        // Should always be set by @ElementCollection but add just in case
                        joinmd = new JoinMetaData();
                        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();
                            colmd.setName(joinColumns.get(j).name());
                            colmd.setTarget(joinColumns.get(j).referencedColumnName());
                            colmd.setAllowsNull(joinColumns.get(j).nullable());
                            joinmd.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();
                            for (int k=0;k<joinUniqueConstraints[j].columnNames().length;k++)
                            {
                                ColumnMetaData colmd = new ColumnMetaData();
                                colmd.setName(joinUniqueConstraints[j].columnNames()[k]);
                                unimd.addColumn(colmd);
                            }
                            joinmd.setUniqueMetaData(unimd); // JDO only supports a single unique constraint on a join table
                        }
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.MAP_KEY_COLUMN))
                {
                    if (keymd == null)
                    {
                        keymd = new KeyMetaData();
                        mmd.setKeyMetaData(keymd);
                    }
                    String name = (String)annotationValues.get("name");
                    if (name != null)
                    {
                        keymd.setMappedBy(name);
                    }
                    Class keyType = mmd.getMap() != null && mmd.getMap().getKeyType() != null ? clr.classForName(mmd.getMap().getKeyType()) : Object.class;
                    keymd.addColumn(newColumnMetaData(keymd, keyType, annotations));
                }
                else if (annName.equals(JPAAnnotationUtils.MAP_KEY))
                {
                    String keyMappedBy = (String)annotationValues.get("name");
                    if (keyMappedBy != null)
                    {
                        if (keymd == null)
                        {
                            keymd = new KeyMetaData();
                            mmd.setKeyMetaData(keymd);
                        }
                        keymd.setMappedBy(keyMappedBy);
                        if (mmd.getMap() != null &&
                                (mmd.getMap().getKeyType() == null || mmd.getMap().getKeyType().equals(Object.class.getName())))
                        {
                            // Set keyType based on mapped-by field of value class
                            String valueType = mmd.getMap().getValueType();
                            try
                            {
                                Class cls = clr.classForName(valueType);
                                try
                                {
                                    Field fld = cls.getDeclaredField(keyMappedBy);
                                    mmd.getMap().setKeyType(fld.getType().getName());
                                }
                                catch (NoSuchFieldException nsfe)
                                {
                                    try
                                    {
                                        String getterName = ClassUtils.getJavaBeanGetterName(keyMappedBy, false);
                                        Method mthd = cls.getDeclaredMethod(getterName, (Class[])null);
                                        mmd.getMap().setKeyType(mthd.getReturnType().getName());
                                    }
                                    catch (NoSuchMethodException nsme)
                                    {
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                            }
                        }
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.MAP_KEY_ENUMERATED))
                {
                    EnumType type = (EnumType)annotationValues.get("value");
                    if (keymd == null)
                    {
                        keymd = new KeyMetaData();
                        mmd.setKeyMetaData(keymd);
                    }
                    // TODO Merge with any @MapKey specification of the column
                    ColumnMetaData colmd = keymd.newColumnMetaData();
                    colmd.setJdbcType(type == EnumType.STRING ? "VARCHAR" : "INTEGER");
                }
                else if (annName.equals(JPAAnnotationUtils.ORDER_BY))
                {
                    if (mmd.getOrderMetaData() != null)
                    {
                        throw new NucleusException("@OrderBy found yet field=" +
                            cmd.getFullClassName() + "." + member.getName() +
                            " already has ordering information!");
                    }

                    String orderBy = (String)annotationValues.get("value");
                    if (orderBy != null)
                    {
                        // "Ordered List"
                        OrderMetaData ordmd = new OrderMetaData();
                        ordmd.setOrdering(orderBy);
                        mmd.setOrderMetaData(ordmd);
                    }
                }
                else if (annName.equals(JPAAnnotationUtils.ORDER_COLUMN))
                {
                    if (mmd.getOrderMetaData() != null)
                    {
                        throw new NucleusException("@OrderColumn found yet field=" +
                            cmd.getFullClassName() + "." + member.getName() +
                        " already has ordering information!");
                    }

                    String columnName = (String)annotationValues.get("name");
                    OrderMetaData ordermd = new OrderMetaData();
                    ordermd.setColumnName(columnName);

                    String colNullable = null;
                    String colInsertable = null;
                    String colUpdateable = 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();
                    }
                    ColumnMetaData colmd = new ColumnMetaData();
                    colmd.setName(columnName);
                    colmd.setAllowsNull(colNullable);
                    colmd.setInsertable(colInsertable);
                    colmd.setUpdateable(colUpdateable);
                    String tmp = (String)annotationValues.get("columnDefinition");
                    if (!StringUtils.isWhitespace(tmp))
                    {
                        colmd.setColumnDdl(tmp);
                    }
                    ordermd.addColumn(colmd);
                    mmd.setOrderMetaData(ordermd);
                }
                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 &&
                jpaLevel.equalsIgnoreCase("JPA1"))
            {
                // 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());
            }
            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());
            }

            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)
                OrderMetaData ordmd = new OrderMetaData();
                ordmd.setOrdering("#PK"); // Special value recognised by OrderMetaData
                mmd.setOrderMetaData(ordmd);
            }

            if (columnMetaData == null)
            {
                // Column specified (at least in part) via @Column/@Lob/@Enumerated/@Temporal
                ColumnMetaData colmd = newColumnMetaData(mmd, member.getType(), 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.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.setValueMetaData(valmd);
                    }
                    for (int i=0;i<columnMetaData.length;i++)
                    {
                        valmd.addColumn(columnMetaData[i]);
                    }
                }
                else if (elementCollection)
                {
                    // Column is for element/value column(s) of join table of 1-N of non-PCs
                    if (mmd.hasCollection() || mmd.hasArray())
                    {
                        ElementMetaData elemmd = mmd.getElementMetaData();
                        if (elemmd == null)
                        {
                            elemmd = new ElementMetaData();
                            mmd.setElementMetaData(elemmd);
                        }
                        for (int i=0;i<columnMetaData.length;i++)
                        {
                            elemmd.addColumn(columnMetaData[i]);
                        }
                    }
                    else if (mmd.hasMap())
                    {
                        ValueMetaData valmd = mmd.getValueMetaData();
                        if (valmd == null)
                        {
                            valmd = new ValueMetaData();
                            mmd.setValueMetaData(valmd);
                        }
                        for (int i=0;i<columnMetaData.length;i++)
                        {
                            valmd.addColumn(columnMetaData[i]);
                        }
                    }
                }
                else
                {
                    // Column is for the member
                    for (int i=0;i<columnMetaData.length;i++)
                    {
                        mmd.addColumn(columnMetaData[i]);
                    }
                }
            }
            return mmd;
        }

        return null;
    }

    /**
     * Method to process the override of embedded members.
     * Can recurse if the overriddenName uses "dot" syntax.
     * @param mmd Metadata for this member
     * @param overriddenName The overridden member within this embedded object
     * @param type Type of this member
     * @param column The column details to override it with
     */
    protected void processEmbeddedAttributeOverride(AbstractMemberMetaData mmd, String overriddenName,
            Class type, Column column)
    {
        // Make sure it has embedded metadata
        EmbeddedMetaData embmd = mmd.getEmbeddedMetaData();
        if (embmd == null)
        {
            embmd = new EmbeddedMetaData();
            embmd.setParent(mmd);
            mmd.setEmbeddedMetaData(embmd);
        }
        mmd.setEmbeddedMetaData(embmd);

        if (overriddenName.indexOf('.') > 0)
        {
            int position = overriddenName.indexOf('.');
            String baseMemberName = overriddenName.substring(0, position);
            String nestedMemberName = overriddenName.substring(position+1);
            AbstractMemberMetaData ammd = null;

            // Try as field
            try
            {
                Field overrideMember = type.getDeclaredField(baseMemberName);
                ammd = new FieldMetaData(embmd, baseMemberName);
                type = overrideMember.getType();
            }
            catch (Exception e)
            {
            }
            if (ammd == null)
            {
                // Try as property
                try
                {
                    Method overrideMember = type.getDeclaredMethod(ClassUtils.getJavaBeanGetterName(baseMemberName, false));
                    ammd = new FieldMetaData(embmd, baseMemberName);
                    type = overrideMember.getReturnType();
                }
                catch (Exception e)
                {
                }
            }
            if (ammd == null)
            {
                throw new NucleusException("Cannot obtain override field/property "+
                    overriddenName + " of class " + type + " for persistent class " + mmd.getClassName(true));
            }

            embmd.addMember(ammd);
            ammd.setParent(embmd);

            // Recurse to nested field type
            processEmbeddedAttributeOverride(ammd, nestedMemberName, type, column);
        }
        else
        {
            Member overriddenMember = null;
            java.lang.reflect.Member overrideMember = null;
            AbstractMemberMetaData ammd = null;

            // Try as field
            try
            {
                overrideMember = type.getDeclaredField(overriddenName);
                overriddenMember = new Member((Field)overrideMember);
                ammd = new FieldMetaData(embmd, overriddenName);
            }
            catch (Exception e)
            {
            }

            if (ammd == null)
            {
                // Try as property
                try
                {
                    overrideMember = type.getDeclaredMethod(ClassUtils.getJavaBeanGetterName(overriddenName, false));
                    overriddenMember = new Member((Method)overrideMember);
                    ammd = new PropertyMetaData(embmd, overriddenName);
                }
                catch (Exception e)
                {
                }
            }

            if (ammd == null)
            {
                throw new NucleusException("Cannot obtain override field/property "+
                    overriddenName + " of class " + type + " for persistent class " + mmd.getClassName(true));
            }

            embmd.addMember(ammd);
            ammd.addColumn(JPAAnnotationUtils.getColumnMetaDataForColumnAnnotation(ammd,
                overriddenMember, column));
        }
    }

    /**
     * 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;
        boolean orphanRemoval = false;
        CascadeType[] cascades = null;
        HashSet<ExtensionMetaData> extensions = null;
        String valueStrategy = null;
        String valueGenerator = null;
        boolean storeInLob = false;
        Class targetEntity = null;
        boolean addJoin = false;

        String jpaLevel = mgr.getNucleusContext().getPersistenceConfiguration().getStringProperty("datanucleus.jpa.level");
        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.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";
                }
                modifier = FieldPersistenceModifier.PERSISTENT.toString();
            }
            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");
                FetchType fetch = (FetchType)annotationValues.get("fetch");
                if (fetch == FetchType.LAZY)
                {
                    dfg = "false";
                }
                else
                {
                    dfg = "true";
                }
                boolean optional = (Boolean)annotationValues.get("optional");
                if (!optional)
                {
                    nullValue = "exception";
                }
                else
                {
                    nullValue = "none";
                }
                orphanRemoval = (Boolean)annotationValues.get("orphanRemoval");
            }
            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");
                FetchType fetch = (FetchType)annotationValues.get("fetch");
                if (fetch == FetchType.LAZY)
                {
                    dfg = "false";
                }
                else
                {
                    dfg = "true";
                }
                orphanRemoval = (Boolean)annotationValues.get("orphanRemoval");
            }
            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");
                FetchType fetch = (FetchType)annotationValues.get("fetch");
                if (fetch == FetchType.LAZY)
                {
                    dfg = "false";
                }
                else
                {
                    dfg = "true";
                }
            }
            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");
                FetchType fetch = (FetchType)annotationValues.get("fetch");
                if (fetch == FetchType.LAZY)
                {
                    dfg = "false";
                }
                else
                {
                    dfg = "true";
                }
                boolean optional = (Boolean)annotationValues.get("optional");
                if (!optional)
                {
                    nullValue = "exception";
                }
                else
                {
                    nullValue = "none";
                }
            }
            else if (annName.equals(JPAAnnotationUtils.ELEMENT_COLLECTION))
            {
                // 1-N NonPC relation
                modifier = FieldPersistenceModifier.PERSISTENT.toString();
                targetEntity = (Class)annotationValues.get("targetClass");
                addJoin = true;
                FetchType fetch = (FetchType)annotationValues.get("fetch");
                if (fetch == FetchType.LAZY)
                {
                    dfg = "false";
                }
                else
                {
                    dfg = "true";
                }
                cascades = new CascadeType[1];
                cascades[0] = CascadeType.ALL;
            }
            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;
                modifier = FieldPersistenceModifier.PERSISTENT.toString();
            }
            else if (annName.equals(JPAAnnotationUtils.EXTENSION) &&
                    jpaLevel.equalsIgnoreCase("DataNucleus"))
            {
                ExtensionMetaData extmd = new ExtensionMetaData((String)annotationValues.get("vendorName"),
                    (String)annotationValues.get("key"), (String)annotationValues.get("value"));
                if (extensions == null)
                {
                    extensions = new HashSet<ExtensionMetaData>(1);
                }
                extensions.add(extmd);
            }
            else if (annName.equals(JPAAnnotationUtils.EXTENSIONS) &&
                    jpaLevel.equalsIgnoreCase("DataNucleus"))
            {
                Extension[] values = (Extension[])annotationValues.get("value");
                if (values != null && values.length > 0)
                {
                    if (extensions == null)
                    {
                        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 = new PropertyMetaData(cmd, field.getName());
        }
        else
        {
            fmd = new FieldMetaData(cmd, field.getName());
        }
        fmd.setPersistenceModifier(modifier);
        fmd.setPrimaryKey(pk);
        fmd.setDefaultFetchGroup(dfg);
        fmd.setEmbedded(embedded);
        fmd.setNullValue(NullValue.getNullValue(nullValue));
        fmd.setMappedBy(mappedBy);

        if (version != null)
        {
            // Tag this field as the version field
            VersionMetaData vermd = cmd.newVersionMetadata();
            vermd.setStrategy(VersionStrategy.VERSION_NUMBER).setFieldName(fmd.getName());
        }

        cmd.addMember(fmd);

        if (orphanRemoval)
        {
            fmd.setCascadeRemoveOrphans(true);
        }
        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.setSequence(valueGenerator);
            fmd.setValueGeneratorName(valueGenerator);
        }
        if (valueStrategy != null)
        {
            fmd.setValueStrategy(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)
            {
                Class elType = ClassUtils.getCollectionElementType(field.getType(), field.getGenericType());
                elementType = (elType != null ? elType.getName() : null);
            }
            // No annotation for collections so cant specify the element type, dependent, embedded, serialized

            contmd = new CollectionMetaData();
            ((CollectionMetaData)contmd).setElementType(elementType);
        }
        else if (field.getType().isArray())
        {
            contmd = new ArrayMetaData();
        }
        else if (Map.class.isAssignableFrom(field.getType()))
        {
            Class keyCls = ClassUtils.getMapKeyType(field.getType(), field.getGenericType());
            String keyType = (keyCls != null ? keyCls.getName() : null);
            String valueType = null;
            if (targetEntity != null && targetEntity != void.class)
            {
                valueType = targetEntity.getName();
            }
            if (valueType == null)
            {
                Class valueCls = ClassUtils.getMapValueType(field.getType(), field.getGenericType());
                valueType = (valueCls != null ? valueCls.getName() : null);
            }

            // No annotation for maps so cant specify the key/value type, dependent, embedded, serialized
            contmd = new MapMetaData();
            MapMetaData mapmd = (MapMetaData)contmd;
            mapmd.setKeyType(keyType);
            mapmd.setValueType(valueType);
        }
        if (contmd != null)
        {
            fmd.setContainer(contmd);
        }

        if (addJoin)
        {
            if (fmd.getJoinMetaData() == null)
            {
                JoinMetaData joinmd = new JoinMetaData();
                fmd.setJoinMetaData(joinmd);
            }
        }

        // 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.
     * TODO !!!! the fieldType logic, like setting a length based on the type,  should be done only after loading all metadata,
     * otherwise it can cause a different behavior based on the loading order of the annotations !!!!
     * @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, Class fieldType, AnnotationObject[] annotations)
    {
        String columnName = null;
        String target = null;
        String targetField = null;
        String jdbcType = null;
        String sqlType = null;
        String typePrecision = null;
        String typeLength = null;
        String typeScale = null;
        String allowsNull = null;
        String defaultValue = null;
        String insertValue = null;
        String insertable = null;
        String updateable = null;
        String unique = null;
        String table = null;
        String columnDdl = null;

        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.COLUMN) || (parent instanceof KeyMetaData && annName.equals(JPAAnnotationUtils.MAP_KEY_COLUMN)))
            {
                columnName = (String)annotationValues.get("name");
                typeLength = "" + (Integer)annotationValues.get("length");
                if (annotationValues.get("precision") != null)
                {
                    int precisionValue = ((Integer)annotationValues.get("precision")).intValue();
                    if (precisionValue != 0)
                    {
                        typePrecision = "" + precisionValue;
                    }
                }
                if (annotationValues.get("scale") != null)
                {
                    int scaleValue = ((Integer)annotationValues.get("scale")).intValue();
                    if (scaleValue != 0)
                    {
                        typeScale = "" + scaleValue;
                    }
                }

                if (fieldType == char.class || fieldType == Character.class)
                {
                    // Char field needs to have length of 1 (JPA TCK)
                    jdbcType = "CHAR";
                    typeLength = "1";
                }
                else if (fieldType == boolean.class || fieldType == Boolean.class)
                {
                    jdbcType = "SMALLINT";
                }

                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;
                    }
                }

                String tmp = (String)annotationValues.get("columnDefinition");
                if (!StringUtils.isWhitespace(tmp))
                {
                    columnDdl = tmp;
                }
            }
            else if (Enum.class.isAssignableFrom(fieldType) && annName.equals(JPAAnnotationUtils.ENUMERATED))
            {
                EnumType type = (EnumType)annotationValues.get("value");
                jdbcType = (type == EnumType.STRING ? "VARCHAR" : "INTEGER");
            }
            else if (JPAAnnotationUtils.isTemporalType(fieldType) && annName.equals(JPAAnnotationUtils.TEMPORAL))
            {
                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";
                }
            }
        }

        // Set length/scale based on the field type
        String length = null;
        String scale = null;
        if (Enum.class.isAssignableFrom(fieldType))
        {
            // Ignore scale on Enum
            if (jdbcType != null && jdbcType.equals("VARCHAR"))
            {
                length = typeLength;
            }
            else if (typePrecision != null)
            {
                length = typePrecision;
            }
        }
        else
        {
            if (String.class.isAssignableFrom(fieldType) || fieldType == Character.class || fieldType == char.class)
            {
                length = typeLength;
            }
            else
            {
                length = (typePrecision != null ? typePrecision : null);
            }
            scale = typeScale;
        }

        if (columnName == null && length == null && scale == null && insertable == null && updateable == null &&
            allowsNull == null && unique == null && jdbcType == null && sqlType == null)
        {
            // Nothing specified so don't provide ColumnMetaData and default to what we get
            return null;
        }

        ColumnMetaData colmd = new ColumnMetaData();
        colmd.setName(columnName);
        colmd.setTarget(target);
        colmd.setTargetMember(targetField);
        colmd.setJdbcType(jdbcType);
        colmd.setSqlType(sqlType);
        colmd.setLength(length);
        colmd.setScale(scale);
        colmd.setAllowsNull(allowsNull);
        colmd.setDefaultValue(defaultValue);
        colmd.setInsertValue(insertValue);
        colmd.setInsertable(insertable);
        colmd.setUpdateable(updateable);
        colmd.setUnique(unique);
        if (columnDdl != null)
        {
            colmd.setColumnDdl(columnDdl);
        }
        if (parent instanceof AbstractMemberMetaData)
        {
            AbstractMemberMetaData apmd = (AbstractMemberMetaData) parent;
            if (!StringUtils.isWhitespace(table))
            {
                apmd.setTable(table);
            }
            // apmd.addColumn(colmd);
            // update column settings if primary key, cannot be null
            colmd.setAllowsNull(Boolean.valueOf(apmd.isPrimaryKey() ? false : colmd.isAllowsNull()));
        }
        else if (parent instanceof KeyMetaData)
        {
            KeyMetaData keymd = (KeyMetaData) parent;
            AbstractMemberMetaData apmd = (AbstractMemberMetaData) keymd.getParent();
            if (!StringUtils.isWhitespace(table))
            {
                apmd.setTable(table);
            }
            colmd.setAllowsNull(colmd.isAllowsNull());
        }
        return colmd;
    }

    /**
     * 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();
                        joinmd.setTable(secTableAnns[j].name());
                        joinmd.setCatalog(secTableAnns[j].catalog());
                        joinmd.setSchema(secTableAnns[j].schema());
                        PrimaryKeyJoinColumn[] pkJoinCols = secTableAnns[j].pkJoinColumns();
                        if (pkJoinCols != null)
                        {
                            for (int k = 0; k < pkJoinCols.length; k++)
                            {
                                ColumnMetaData colmd = new ColumnMetaData();
                                colmd.setName(pkJoinCols[k].name());
                                colmd.setTarget(pkJoinCols[k].referencedColumnName());
                                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();
                                unimd.setTable((String)annotationValues.get("table"));
                                for (int l=0;l<constrs[k].columnNames().length;l++)
                                {
                                    ColumnMetaData colmd = new ColumnMetaData();
                                    colmd.setName(constrs[k].columnNames()[l]);
                                    unimd.addColumn(colmd);
                                }
                                joinmd.setUniqueMetaData(unimd); // JDO only allows one unique
                            }
                        }
                    }
                }
            }
            else if (annName.equals(JPAAnnotationUtils.SECONDARY_TABLE))
            {
                JoinMetaData joinmd = new JoinMetaData();
                joinmd.setTable((String)annotationValues.get("name"));
                joinmd.setCatalog((String)annotationValues.get("catalog"));
                joinmd.setSchema((String)annotationValues.get("schema"));
                if (annotationValues.get("pkJoinColumns") != null)
                {
                    PrimaryKeyJoinColumn[] joinCols = (PrimaryKeyJoinColumn[])annotationValues.get("pkJoinColumns");
                    for (int j = 0; j < joinCols.length; j++)
                    {
                        ColumnMetaData colmd = new ColumnMetaData();
                        colmd.setName(joinCols[j].name());
                        colmd.setTarget(joinCols[j].referencedColumnName());
                        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();
                        unimd.setTable((String)annotationValues.get("table"));
                        for (int k=0;k<constrs[j].columnNames().length;k++)
                        {
                            ColumnMetaData colmd = new ColumnMetaData();
                            colmd.setName(constrs[j].columnNames()[k]);
                            unimd.addColumn(colmd);
                        }
                        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");
        // Don't apply default for sequenceName if not provided. This is done by the individual sequence generators
        Integer initialValue = (Integer)annotationValues.get("initialValue");
        if (initialValue == null)
        {
            initialValue = Integer.valueOf(1); // JPA default
        }
        Integer allocationSize = (Integer)annotationValues.get("allocationSize");
        if (allocationSize == null)
        {
            allocationSize = Integer.valueOf(50); // JPA default
        }
        SequenceMetaData seqmd = pmd.newSequenceMetadata(name, null);
        seqmd.setDatastoreSequence(seqName);
        seqmd.setInitialValue(initialValue.intValue());
        seqmd.setAllocationSize(allocationSize.intValue());
    }

    /**
     * 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)
    {
        TableGeneratorMetaData tgmd = pmd.newTableGeneratorMetadata((String)annotationValues.get("name"));
        tgmd.setTableName((String)annotationValues.get("table"));
        tgmd.setCatalogName((String)annotationValues.get("catalog"));
        tgmd.setSchemaName((String)annotationValues.get("schema"));
        tgmd.setPKColumnName((String)annotationValues.get("pkColumnName"));
        tgmd.setPKColumnValue((String)annotationValues.get("pkColumnValue"));
        tgmd.setValueColumnName((String)annotationValues.get("valueColumnName"));
        tgmd.setInitialValue((Integer)annotationValues.get("initialValue"));
        tgmd.setAllocationSize((Integer)annotationValues.get("allocationSize"));
        // 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))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if class is persistence aware, by looking at annotations
     * @param cls the Class
     * @return true if the class has @PersistenceAware
     */
    protected boolean isClassPersistenceAware(Class cls)
    {
        String jpaLevel =
            mgr.getNucleusContext().getPersistenceConfiguration().getStringProperty("datanucleus.jpa.level");
        if (jpaLevel.equalsIgnoreCase("DataNucleus"))
        {
            AnnotationObject[] annotations = getClassAnnotationsForClass(cls);
            for (int i = 0; i < annotations.length; i++)
            {
                String annName = annotations[i].getName();
                if (annName.equals(JPAAnnotationUtils.PERSISTENCE_AWARE))
                {
                    return true;
                }
            }
        }
        return false;
    }
}
TOP

Related Classes of org.datanucleus.api.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.