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

org.datanucleus.api.jdo.metadata.JDOAnnotationReader Maven / Gradle / Ivy

There is a newer version: 6.0.4
Show newest version
/**********************************************************************
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.jdo.metadata;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.jdo.AttributeConverter;
import javax.jdo.AttributeConverter.UseDefault;
import javax.jdo.annotations.Column;
import javax.jdo.annotations.Discriminator;
import javax.jdo.annotations.DiscriminatorStrategy;
import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.FetchGroup;
import javax.jdo.annotations.FetchPlan;
import javax.jdo.annotations.ForeignKey;
import javax.jdo.annotations.ForeignKeyAction;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Index;
import javax.jdo.annotations.InheritanceStrategy;
import javax.jdo.annotations.Join;
import javax.jdo.annotations.NullValue;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.PersistenceModifier;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.Query;
import javax.jdo.annotations.SequenceStrategy;
import javax.jdo.annotations.Unique;
import javax.jdo.annotations.VersionStrategy;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.PropertyNames;
import org.datanucleus.api.jdo.JDOQuery;
import org.datanucleus.api.jdo.JDOTypeConverter;
import org.datanucleus.api.jdo.JDOTypeConverterUtils;
import org.datanucleus.api.jdo.NucleusJDOHelper;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ArrayMetaData;
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.FetchGroupMemberMetaData;
import org.datanucleus.metadata.FetchGroupMetaData;
import org.datanucleus.metadata.FetchPlanMetaData;
import org.datanucleus.metadata.FieldMetaData;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.metadata.FileMetaData;
import org.datanucleus.metadata.ForeignKeyMetaData;
import org.datanucleus.metadata.IdentityMetaData;
import org.datanucleus.metadata.ValueGenerationStrategy;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.IndexMetaData;
import org.datanucleus.metadata.IndexedValue;
import org.datanucleus.metadata.InheritanceMetaData;
import org.datanucleus.metadata.InvalidClassMetaDataException;
import org.datanucleus.metadata.InvalidMetaDataException;
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.MetaDataUtils;
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.SequenceMetaData;
import org.datanucleus.metadata.UniqueMetaData;
import org.datanucleus.metadata.ValueMetaData;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.metadata.annotations.AbstractAnnotationReader;
import org.datanucleus.metadata.annotations.AnnotationObject;
import org.datanucleus.metadata.annotations.Member;
import org.datanucleus.store.types.ContainerHandler;
import org.datanucleus.store.types.TypeManager;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
 * Implementation for Annotation Reader for java annotations using the JDO definition.
 */
public class JDOAnnotationReader extends AbstractAnnotationReader
{
    /**
     * Constructor.
     * @param mgr MetaData manager
     */
    public JDOAnnotationReader(MetaDataManager mgr)
    {
        super(mgr);

        // We support JDO standard and DataNucleus JDO extension annotations in this reader.
        setSupportedAnnotationPackages(new String[]{"javax.jdo", "org.datanucleus.api.jdo.annotations"});

        // Create default AnnotationObject for PersistenceCapable to allow merging annotations
        persistenceCapableDefaults = getAnnotationObjectsForAnnotations(PersistenceCapableModel.class.getSimpleName(), PersistenceCapableModel.class.getAnnotations())[0];
        addSupportedDuplicateAnnotations(PersistenceCapable.class.getName());
    }

    @javax.jdo.annotations.PersistenceCapable
    protected class PersistenceCapableModel {}

    AnnotationObject persistenceCapableDefaults = null;

    /**
     * Method to process the "class" level annotations and create the outline ClassMetaData object.
     * Supports classes annotated with @PersistenceCapable, classes annotated with @PersistenceAware, and classes which have neither of those but have @Queries or @Query.
     * @param pmd Parent PackageMetaData
     * @param cls The class
     * @param annotations Annotations for this class
     * @param clr ClassLoader resolver
     * @return The ClassMetaData/InterfaceMetaData (or null if no annotations)
     */
    protected AbstractClassMetaData processClassAnnotations(PackageMetaData pmd, Class cls, AnnotationObject[] annotations, ClassLoaderResolver clr)
    {
        if (annotations == null || annotations.length == 0)
        {
            return null;
        }

        AbstractClassMetaData cmd = null;

        AnnotationObject pcAnnotation = isClassPersistable(annotations);
        if (pcAnnotation != null)
        {
            // PersistenceCapable class
            cmd = (cls.isInterface()) ? pmd.newInterfaceMetadata(ClassUtils.getClassNameForClass(cls)) : pmd.newClassMetadata(ClassUtils.getClassNameForClass(cls));
            cmd.setPersistenceModifier(ClassPersistenceModifier.PERSISTENCE_CAPABLE);

            // Process all attributes here in case needed for other annotations
            processPersistenceCapableAnnotation(cls, cmd, pcAnnotation.getNameValueMap());
        }
        else if (isClassPersistenceAware(annotations))
        {
            // PersistenceAware class
            cmd = pmd.newClassMetadata(ClassUtils.getClassNameForClass(cls));
            cmd.setPersistenceModifier(ClassPersistenceModifier.PERSISTENCE_AWARE);
        }
        else if (doesClassHaveNamedQueries(annotations))
        {
            // Class with named query specified
            cmd = pmd.newClassMetadata(ClassUtils.getClassNameForClass(cls));
            cmd.setPersistenceModifier(ClassPersistenceModifier.NON_PERSISTENT);
        }
        else
        {
            // Not involved in the persistence process
            return null;
        }

        // Cater for named queries being specified on a persistence aware, or other class
        processNamedQueries(cmd, cls, annotations);

        if (cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_CAPABLE)
        {
            // Not persistable, so no further information needed
            return cmd;
        }

        // Class is persistable so process annotations
        for (AnnotationObject annotation : annotations)
        {
            String annName = annotation.getName();
            if (annName.equals(JDOAnnotationUtils.PERSISTENCE_CAPABLE))
            {
                // @PersistenceCapable is merged and processed above
                continue;
            }

            Map annotationValues = annotation.getNameValueMap();

            if (annName.equals(JDOAnnotationUtils.EMBEDDED_ONLY))
            {
                cmd.setEmbeddedOnly(true);
            }
            else if (annName.equals(JDOAnnotationUtils.VERSION))
            {
                VersionStrategy versionStrategy = (VersionStrategy) annotationValues.get("strategy");
                String strategy = JDOAnnotationUtils.getVersionStrategyString(versionStrategy);
                String indexed = (String) annotationValues.get("indexed");
                String column = (String) annotationValues.get("column");
                Column[] columns = (Column[]) annotationValues.get("columns");
                VersionMetaData vermd = new VersionMetaData();
                vermd.setStrategy(strategy);
                vermd.setColumnName(column);
                vermd.setIndexed(IndexedValue.getIndexedValue(indexed));
                if (columns != null && columns.length > 0)
                {
                    // Only use the first column
                    ColumnMetaData colmd = JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(columns[0]);
                    vermd.setColumnMetaData(colmd);
                }
                JDOAnnotationUtils.addExtensionsToMetaData(vermd, (Extension[]) annotationValues.get("extensions"));

                vermd.setParent(cmd);
                cmd.setVersionMetaData(vermd);
            }
            else if (annName.equals(JDOAnnotationUtils.DATASTORE_IDENTITY))
            {
                String strategy = JDOAnnotationUtils.getValueGenerationStrategyString((IdGeneratorStrategy) annotationValues.get("strategy"));
                String customStrategy = (String) annotationValues.get("customStrategy");
                if (!StringUtils.isWhitespace(customStrategy))
                {
                    // User has provided an extension strategy
                    strategy = customStrategy;
                }
                String sequence = (String) annotationValues.get("sequence");
                String column = (String) annotationValues.get("column");
                Column[] columns = (Column[]) annotationValues.get("columns");
                IdentityMetaData idmd = new IdentityMetaData();
                idmd.setColumnName(column);
                idmd.setValueStrategy(ValueGenerationStrategy.getIdentityStrategy(strategy));
                idmd.setSequence(sequence);
                if (columns != null && columns.length > 0)
                {
                    // Only use the first column
                    ColumnMetaData colmd = JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(columns[0]);
                    idmd.setColumnMetaData(colmd);
                }
                JDOAnnotationUtils.addExtensionsToMetaData(idmd, (Extension[]) annotationValues.get("extensions"));

                idmd.setParent(cmd);
                cmd.setIdentityMetaData(idmd);
            }
            else if (annName.equals(JDOAnnotationUtils.PRIMARY_KEY))
            {
                String pkName = (String) annotationValues.get("name");
                String pkColumn = (String) annotationValues.get("column");
                Column[] columns = (Column[]) annotationValues.get("columns");
                PrimaryKeyMetaData pkmd = new PrimaryKeyMetaData();
                pkmd.setName(pkName);
                pkmd.setColumnName(pkColumn);
                if (columns != null && columns.length > 0)
                {
                    for (Column column : columns)
                    {
                        pkmd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(column));
                    }
                }
                JDOAnnotationUtils.addExtensionsToMetaData(pkmd, (Extension[]) annotationValues.get("extensions"));

                pkmd.setParent(cmd);
                cmd.setPrimaryKeyMetaData(pkmd);
            }
            else if (annName.equals(JDOAnnotationUtils.JOINS))
            {
                Join[] js = (Join[]) annotationValues.get("value");
                if (js != null && js.length > 0)
                {
                    for (Join join : js)
                    {
                        JoinMetaData joinmd = cmd.newJoinMetaData();
                        joinmd.setTable(join.table());
                        joinmd.setColumnName(join.column());
                        joinmd.setIndexed(IndexedValue.getIndexedValue(join.indexed()));
                        joinmd.setOuter(MetaDataUtils.getBooleanForString(join.outer(), false));
                        joinmd.setUnique(join.unique());
                        joinmd.setDeleteAction(JDOAnnotationUtils.getForeignKeyActionString(join.deleteAction()));
                        JDOAnnotationUtils.addExtensionsToMetaData(joinmd, join.extensions());
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.JOIN))
            {
                JoinMetaData joinmd = cmd.newJoinMetaData();
                joinmd.setTable((String) annotationValues.get("table"));
                joinmd.setColumnName((String) annotationValues.get("column"));
                joinmd.setIndexed(IndexedValue.getIndexedValue((String) annotationValues.get("indexed")));
                joinmd.setOuter(MetaDataUtils.getBooleanForString((String) annotationValues.get("outer"), false));
                joinmd.setUnique((String) annotationValues.get("unique"));
                joinmd.setDeleteAction(((ForeignKeyAction) annotationValues.get("deleteAction")).toString());
                JDOAnnotationUtils.addExtensionsToMetaData(joinmd, (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.INHERITANCE))
            {
                String strategy = JDOAnnotationUtils.getInheritanceStrategyString((InheritanceStrategy) annotationValues.get("strategy"));
                String customStrategy = (String) annotationValues.get("customStrategy");
                if (!StringUtils.isWhitespace(customStrategy))
                {
                    // User has provided an extension strategy
                    strategy = customStrategy;
                }

                InheritanceMetaData inhmd = cmd.getInheritanceMetaData();
                if (inhmd == null)
                {
                    inhmd = cmd.newInheritanceMetadata();
                }
                inhmd.setStrategy(strategy);
            }
            else if (annName.equals(JDOAnnotationUtils.DISCRIMINATOR))
            {
                DiscriminatorStrategy discriminatorStrategy = (DiscriminatorStrategy) annotationValues.get("strategy");
                String strategy = JDOAnnotationUtils.getDiscriminatorStrategyString(discriminatorStrategy);
                String column = (String) annotationValues.get("column");
                String indexed = (String) annotationValues.get("indexed");
                String value = (String) annotationValues.get("value");
                Column[] columns = (Column[]) annotationValues.get("columns");

                InheritanceMetaData inhmd = cmd.getInheritanceMetaData();
                if (inhmd == null)
                {
                    inhmd = cmd.newInheritanceMetadata();
                }

                DiscriminatorMetaData dismd = inhmd.newDiscriminatorMetadata();
                dismd.setColumnName(column);
                dismd.setValue(value);
                dismd.setStrategy(strategy);
                dismd.setIndexed(indexed);
                if (columns != null && columns.length > 0)
                {
                    // Only use the first column
                    ColumnMetaData colmd = JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(columns[0]);
                    dismd.setColumnMetaData(colmd);
                }
            }
            else if (annName.equals(JDOAnnotationUtils.FETCHPLANS))
            {
                FileMetaData filemd = (FileMetaData) pmd.getParent();

                FetchPlan[] plans = (FetchPlan[]) annotationValues.get("value");
                for (FetchPlan plan : plans)
                {
                    FetchPlanMetaData fpmd = filemd.newFetchPlanMetadata(plan.name());
                    fpmd.setFetchSize(plan.fetchSize());
                    fpmd.setMaxFetchDepth(plan.maxFetchDepth());
                    int numGroups = plan.fetchGroups().length;
                    for (int k = 0; k < numGroups; k++)
                    {
                        fpmd.addFetchGroup(new FetchGroupMetaData(plan.fetchGroups()[k]));
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.FETCHPLAN))
            {
                FileMetaData filemd = (FileMetaData) pmd.getParent();
                FetchPlanMetaData fpmd = filemd.newFetchPlanMetadata((String)annotationValues.get("name"));
                fpmd.setFetchSize(((Integer) annotationValues.get("fetchSize")).intValue());
                fpmd.setMaxFetchDepth(((Integer) annotationValues.get("maxFetchDepth")).intValue());
                String[] fpFetchGroups = (String[]) annotationValues.get("fetchGroups");
                for (String fpFetchGroup : fpFetchGroups)
                {
                    fpmd.addFetchGroup(new FetchGroupMetaData(fpFetchGroup));
                }
            }
            else if (annName.equals(JDOAnnotationUtils.FETCHGROUPS))
            {
                FetchGroup[] groups = (FetchGroup[]) annotationValues.get("value");
                for (FetchGroup group : groups)
                {
                    FetchGroupMetaData fgmd = cmd.newFetchGroupMetaData(group.name());

                    if (!StringUtils.isWhitespace(group.postLoad()))
                    {
                        fgmd.setPostLoad(Boolean.valueOf(group.postLoad()));
                    }
                    int numFields = group.members().length;
                    for (int k = 0; k < numFields; k++)
                    {
                        FetchGroupMemberMetaData fgmmd = new FetchGroupMemberMetaData(fgmd, group.members()[k].name());
                        fgmmd.setRecursionDepth(group.members()[k].recursionDepth());
                        fgmd.addMember(fgmmd);
                    }
                    int numGroups = group.fetchGroups().length;
                    for (int k = 0; k < numGroups; k++)
                    {
                        fgmd.addFetchGroup(new FetchGroupMetaData(group.fetchGroups()[k]));
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.FETCHGROUP))
            {
                FetchGroupMetaData fgmd = cmd.newFetchGroupMetaData((String) annotationValues.get("name"));
                String postLoadStr = (String) annotationValues.get("postLoad");
                if (!StringUtils.isWhitespace(postLoadStr))
                {
                    fgmd.setPostLoad(Boolean.valueOf(postLoadStr));
                }
                Persistent[] fields = (Persistent[]) annotationValues.get("members");
                if (fields != null)
                {
                    for (Persistent field : fields)
                    {
                        FetchGroupMemberMetaData fgmmd = new FetchGroupMemberMetaData(fgmd, field.name());
                        fgmmd.setRecursionDepth(field.recursionDepth());
                        fgmd.addMember(fgmmd);
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.SEQUENCE))
            {
                String seqName = (String) annotationValues.get("name");
                String seqStrategy = JDOAnnotationUtils.getSequenceStrategyString((SequenceStrategy) annotationValues.get("strategy"));
                String seqSeq = (String) annotationValues.get("datastoreSequence");
                Class seqFactory = (Class) annotationValues.get("factoryClass");
                String seqFactoryClassName = null;
                if (seqFactory != null && seqFactory != void.class)
                {
                    seqFactoryClassName = seqFactory.getName();
                }
                Integer seqSize = (Integer) annotationValues.get("allocationSize");
                Integer seqStart = (Integer) annotationValues.get("initialValue");

                if (StringUtils.isWhitespace(seqName))
                {
                    throw new InvalidClassMetaDataException("044155", cmd.getFullClassName());
                }
                SequenceMetaData seqmd = new SequenceMetaData(seqName, seqStrategy);
                seqmd.setFactoryClass(seqFactoryClassName);
                seqmd.setDatastoreSequence(seqSeq);
                if (seqSize != null)
                {
                    seqmd.setAllocationSize(seqSize);
                }
                if (seqStart != null)
                {
                    seqmd.setInitialValue(seqStart);
                }
                JDOAnnotationUtils.addExtensionsToMetaData(seqmd, (Extension[]) annotationValues.get("extensions"));

                // Sequence - currently only allowing 1 per class (should really be on the package)
                cmd.getPackageMetaData().addSequence(seqmd);
            }
            else if (annName.equals(JDOAnnotationUtils.INDICES))
            {
                // Multiple Indices for the class
                Index[] values = (Index[]) annotationValues.get("value");
                if (values != null && values.length > 0)
                {
                    for (Index idx : values)
                    {
                        IndexMetaData idxmd = JDOAnnotationUtils.getIndexMetaData(idx.name(), idx.table(), "" + idx.unique(), idx.members(), idx.columns());
                        if (idxmd.getNumberOfColumns() == 0 && idxmd.getNumberOfMembers() == 0)
                        {
                            NucleusLogger.METADATA.warn(Localiser.msg("044204", cls.getName()));
                        }
                        else
                        {
                            cmd.addIndex(idxmd);
                            idxmd.setParent(cmd);
                        }
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.INDEX))
            {
                // Single Index for the class
                String name = (String) annotationValues.get("name");
                String table = (String) annotationValues.get("table");
                String unique = (String) annotationValues.get("unique");
                String[] members = (String[]) annotationValues.get("members");
                Column[] columns = (Column[]) annotationValues.get("columns");

                IndexMetaData idxmd = JDOAnnotationUtils.getIndexMetaData(name, table, unique, members, columns);
                JDOAnnotationUtils.addExtensionsToMetaData(idxmd, (Extension[]) annotationValues.get("extensions"));
                if (idxmd.getNumberOfColumns() == 0 && idxmd.getNumberOfMembers() == 0)
                {
                    NucleusLogger.METADATA.warn(Localiser.msg("044204", cls.getName()));
                }
                else
                {
                    cmd.addIndex(idxmd);
                    idxmd.setParent(cmd);
                }
            }
            else if (annName.equals(JDOAnnotationUtils.UNIQUES))
            {
                // Multiple Unique Constraints for the class
                Unique[] values = (Unique[]) annotationValues.get("value");
                if (values != null && values.length > 0)
                {
                    for (Unique uni : values)
                    {
                        UniqueMetaData unimd = JDOAnnotationUtils.getUniqueMetaData(uni.name(), uni.table(), "" + uni.deferred(), uni.members(), uni.columns());
                        if (unimd.getNumberOfColumns() == 0 && unimd.getNumberOfMembers() == 0)
                        {
                            NucleusLogger.METADATA.warn(Localiser.msg("044205", cls.getName()));
                        }
                        else
                        {
                            cmd.addUniqueConstraint(unimd);
                            unimd.setParent(cmd);
                        }
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.UNIQUE))
            {
                // Single Unique constraint for the class
                String name = (String) annotationValues.get("name");
                String table = (String) annotationValues.get("table");
                String deferred = (String) annotationValues.get("deferred");
                String[] members = (String[]) annotationValues.get("members");
                Column[] columns = (Column[]) annotationValues.get("columns");

                UniqueMetaData unimd = JDOAnnotationUtils.getUniqueMetaData(name, table, deferred, members, columns);
                JDOAnnotationUtils.addExtensionsToMetaData(unimd, (Extension[]) annotationValues.get("extensions"));
                if (unimd.getNumberOfColumns() == 0 && unimd.getNumberOfMembers() == 0)
                {
                    NucleusLogger.METADATA.warn(Localiser.msg("044205", cls.getName()));
                }
                else
                {
                    cmd.addUniqueConstraint(unimd);
                    unimd.setParent(cmd);
                }
            }
            else if (annName.equals(JDOAnnotationUtils.FOREIGNKEYS))
            {
                // Multiple FKs for the class
                ForeignKey[] values = (ForeignKey[]) annotationValues.get("value");
                if (values != null && values.length > 0)
                {
                    for (ForeignKey fk : values)
                    {
                        String deleteAction = JDOAnnotationUtils.getForeignKeyActionString(fk.deleteAction());
                        String updateAction = JDOAnnotationUtils.getForeignKeyActionString(fk.updateAction());
                        ForeignKeyMetaData fkmd = JDOAnnotationUtils.getFKMetaData(fk.name(), fk.table(), fk.unique(), "" + fk.deferred(), deleteAction, updateAction, fk.members(), fk.columns());
                        if (fkmd.getNumberOfColumns() == 0 && fkmd.getNumberOfMembers() == 0)
                        {
                            NucleusLogger.METADATA.warn(Localiser.msg("044206", cls.getName()));
                        }
                        else
                        {
                            cmd.addForeignKey(fkmd);
                            fkmd.setParent(cmd);
                        }
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.FOREIGNKEY))
            {
                // Single FK constraint for the class
                String name = (String) annotationValues.get("name");
                String table = (String) annotationValues.get("table");
                String unique = (String) annotationValues.get("unique");
                String deferred = (String) annotationValues.get("deferred");
                String deleteAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("deleteAction"));
                String updateAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("updateAction"));
                String[] members = (String[]) annotationValues.get("members");
                Column[] columns = (Column[]) annotationValues.get("columns");

                ForeignKeyMetaData fkmd = JDOAnnotationUtils.getFKMetaData(name, table, unique, deferred, deleteAction, updateAction, members, columns);
                JDOAnnotationUtils.addExtensionsToMetaData(fkmd, (Extension[]) annotationValues.get("extensions"));
                if (fkmd.getNumberOfColumns() == 0 && fkmd.getNumberOfMembers() == 0)
                {
                    NucleusLogger.METADATA.warn(Localiser.msg("044206", cls.getName()));
                }
                else
                {
                    cmd.addForeignKey(fkmd);
                    fkmd.setParent(cmd);
                }
            }
            else if (annName.equals(JDOAnnotationUtils.COLUMNS))
            {
                // Unmapped column specification
                Column[] cols = (Column[]) annotationValues.get("value");
                if (cols != null && cols.length > 0)
                {
                    for (Column col : cols)
                    {
                        ColumnMetaData colmd = JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(col);
                        JDOAnnotationUtils.addExtensionsToMetaData(colmd, col.extensions());

                        colmd.setParent(cmd);
                        cmd.addUnmappedColumn(colmd);
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.CACHEABLE))
            {
                String cache = (String) annotationValues.get("value");
                if (cache != null && cache.equalsIgnoreCase("false"))
                {
                    cmd.setCacheable(false);
                }
            }
            else if (annName.equals(JDOAnnotationUtils.EXTENSIONS))
            {
                Extension[] values = (Extension[]) annotationValues.get("value");
                if (values != null && values.length > 0)
                {
                    for (Extension ext : values)
                    {
                        String vendorName = ext.vendorName();
                        if (StringUtils.isWhitespace(vendorName))
                        {
                            throw new InvalidMetaDataException("044160", vendorName, ext.key().toString(), ext.value().toString());
                        }
                        else if (vendorName.equalsIgnoreCase(MetaData.VENDOR_NAME))
                        {
                            cmd.addExtension(ext.key().toString(), ext.value().toString());
                        }
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.EXTENSION))
            {
                String vendorName = (String)annotationValues.get("vendorName");
                if (StringUtils.isWhitespace(vendorName))
                {
                    throw new InvalidMetaDataException("044160", vendorName, annotationValues.get("key"), annotationValues.get("value"));
                }
                else if (vendorName.equalsIgnoreCase(MetaData.VENDOR_NAME))
                {
                    cmd.addExtension((String)annotationValues.get("key"), (String)annotationValues.get("value"));
                }
            }
            else
            {
                if (!annName.equals(JDOAnnotationUtils.PERSISTENCE_AWARE) && !annName.equals(JDOAnnotationUtils.QUERIES) && !annName.equals(JDOAnnotationUtils.QUERY))
                {
                    NucleusLogger.METADATA.debug(Localiser.msg("044203", cls.getName(), annotation.getName()));
                }
            }
        }

        NucleusLogger.METADATA.debug(Localiser.msg("044200", cls.getName(), "JDO"));

        return cmd;
    }

    private void processPersistenceCapableAnnotation(Class cls, AbstractClassMetaData cmd, Map annotationValues)
    {
        String identityType = JDOAnnotationUtils.getIdentityTypeString((javax.jdo.annotations.IdentityType) annotationValues.get("identityType"));
        cmd.setIdentityType(IdentityType.getIdentityType(identityType));

        Class idClass = (Class) annotationValues.get("objectIdClass");
        if (idClass != null && idClass != void.class)
        {
            cmd.setObjectIdClass(NucleusJDOHelper.getObjectIdClassForInputIdClass(idClass.getName()));
        }

        cmd.setEmbeddedOnly((String) annotationValues.get("embeddedOnly"));
        cmd.setCacheable((String) annotationValues.get("cacheable"));

        String serializeRead = (String) annotationValues.get("serializeRead");
        if (serializeRead != null)
        {
            cmd.setSerializeRead(serializeRead.equals("true") ? true : false);
        }
        cmd.setRequiresExtent((String) annotationValues.get("requiresExtent"));

        if (mmgr.getNucleusContext().getConfiguration().getBooleanProperty(PropertyNames.PROPERTY_METADATA_ALWAYS_DETACHABLE))
        {
            cmd.setDetachable(true);
        }
        else
        {
            cmd.setDetachable((String) annotationValues.get("detachable"));
        }

        JDOAnnotationUtils.addExtensionsToMetaData(cmd, (Extension[]) annotationValues.get("extensions"));

        String tblName = (String) annotationValues.get("table");
        if (!StringUtils.isWhitespace(tblName))
        {
            cmd.setTable(tblName);
        }
        String catName = (String) annotationValues.get("catalog");
        if (!StringUtils.isWhitespace(catName))
        {
            cmd.setCatalog(catName);
        }
        String schName = (String) annotationValues.get("schema");
        if (!StringUtils.isWhitespace(schName))
        {
            cmd.setSchema(schName);
        }

        // Members typically providing specification of overridden fields/properties
        Persistent[] members = (Persistent[]) annotationValues.get("members");
        if (members != null)
        {
            // Add on the fields/properties direct to the metadata for the class/interface
            for (Persistent member : members)
            {
                String memberName = member.name();
                if (memberName.indexOf('.') > 0)
                {
                    memberName = memberName.substring(memberName.lastIndexOf('.') + 1);
                }
                boolean isField = isMemberOfClassAField(cls, memberName);
                AbstractMemberMetaData fmd = getFieldMetaDataForPersistent(cmd, member, isField);
                cmd.addMember(fmd);
            }
        }
    }

    /**
     * Convenience method to process @Queries, @Query.
     * @param cmd Metadata for the class, to which any queries will be added
     * @param cls Class that the named queries are registered against
     * @param annotations Annotations specified on the class
     */
    protected void processNamedQueries(AbstractClassMetaData cmd, Class cls, AnnotationObject[] annotations)
    {
        for (AnnotationObject annotation : annotations)
        {
            Map annotationValues = annotation.getNameValueMap();
            String annName = annotation.getName();

            if (annName.equals(JDOAnnotationUtils.QUERIES))
            {
                Query[] qs = (Query[]) annotationValues.get("value");
                for (Query query : qs)
                {
                    String lang = JDOAnnotationUtils.getQueryLanguageName(query.language());
                    if (!StringUtils.isWhitespace(lang))
                    {
                        if (lang.equals(JDOQuery.JDOQL_QUERY_LANGUAGE)) // Convert to JDOQL
                        {
                            lang = QueryLanguage.JDOQL.toString();
                        }
                        else if (lang.equals(JDOQuery.SQL_QUERY_LANGUAGE)) // Convert to SQL
                        {
                            lang = QueryLanguage.SQL.toString();
                        }
                        else if (lang.equals(JDOQuery.JPQL_QUERY_LANGUAGE)) // Convert to JPQL
                        {
                            lang = QueryLanguage.JPQL.toString();
                        }
                    }
                    String resultClassName = (query.resultClass() != null && query.resultClass() != void.class ? query.resultClass().getName() : null);
                    if (StringUtils.isWhitespace(query.name()))
                    {
                        throw new InvalidClassMetaDataException("044154", cmd.getFullClassName());
                    }
                    QueryMetaData qmd = new QueryMetaData(query.name());
                    qmd.setScope(cls.getName());
                    qmd.setLanguage(lang);
                    qmd.setUnmodifiable(query.unmodifiable());
                    qmd.setResultClass(resultClassName);
                    qmd.setUnique(query.unique());
                    qmd.setFetchPlanName(query.fetchPlan());
                    qmd.setQuery(query.value());
                    JDOAnnotationUtils.addExtensionsToMetaData(qmd, query.extensions());
                    cmd.addQuery(qmd);
                    qmd.setParent(cmd);
                }
            }
            else if (annName.equals(JDOAnnotationUtils.QUERY))
            {
                String unmodifiable = "" + annotationValues.get("unmodifiable");
                Class resultClassValue = (Class) annotationValues.get("resultClass");
                String resultClassName = (resultClassValue != null && resultClassValue != void.class ? resultClassValue.getName() : null);
                String lang = JDOAnnotationUtils.getQueryLanguageName((String) annotationValues.get("language"));
                if (!StringUtils.isWhitespace(lang))
                {
                    if (lang.equals(JDOQuery.JDOQL_QUERY_LANGUAGE)) // Convert to JDOQL
                    {
                        lang = QueryLanguage.JDOQL.toString();
                    }
                    else if (lang.equals(JDOQuery.SQL_QUERY_LANGUAGE)) // Convert to SQL
                    {
                        lang = QueryLanguage.SQL.toString();
                    }
                    else if (lang.equals(JDOQuery.JPQL_QUERY_LANGUAGE)) // Convert to JPQL
                    {
                        lang = QueryLanguage.JPQL.toString();
                    }
                }
                if (StringUtils.isWhitespace((String) annotationValues.get("name")))
                {
                    throw new InvalidClassMetaDataException("044154", cmd.getFullClassName());
                }
                QueryMetaData qmd = new QueryMetaData((String) annotationValues.get("name"));
                qmd.setScope(cls.getName());
                qmd.setLanguage(lang);
                qmd.setUnmodifiable(unmodifiable);
                qmd.setResultClass(resultClassName);
                qmd.setUnique((String) annotationValues.get("unique"));
                qmd.setFetchPlanName((String) annotationValues.get("fetchPlan"));
                qmd.setQuery((String) annotationValues.get("value"));
                JDOAnnotationUtils.addExtensionsToMetaData(qmd, (Extension[]) annotationValues.get("extensions"));
                cmd.addQuery(qmd);
                qmd.setParent(cmd);
            }
        }
    }

    /**
     * Convenience method to process the annotations for a field/property. The passed annotations may have been specified on the field or on the getter methods.
     * @param cmd The ClassMetaData/InterfaceMetaData to update
     * @param member The field/property
     * @param annotations Annotations for the field/property
     * @return The FieldMetaData/PropertyMetaData that was added (if any)
     */
    protected AbstractMemberMetaData processMemberAnnotations(AbstractClassMetaData cmd, Member member, AnnotationObject[] annotations)
    {
        if (annotations == null || annotations.length == 0)
        {
            return null;
        }

        AbstractMemberMetaData mmd = null;

        boolean primaryKey = false;
        boolean serialised = false;
        boolean embeddedMember = false;
        boolean nonPersistentField = false;
        boolean transactionalField = false;
        String cacheable = null;

        Class[] elementTypes = null;
        String embeddedElement = null;
        String serializedElement = null;
        String dependentElement = null;

        Class[] keyTypes = null;
        String embeddedKey = null;
        String serializedKey = null;
        String dependentKey = null;

        Class[] valueTypes = null;
        String embeddedValue = null;
        String serializedValue = null;
        String dependentValue = null;

        String embeddedOwnerField = null;
        String embeddedNullIndicatorColumn = null;
        String embeddedNullIndicatorValue = null;
        Persistent[] embeddedMembers = null;
        Persistent[] embeddedElementMembers = null;
        Persistent[] embeddedKeyMembers = null;
        Persistent[] embeddedValueMembers = null;

        ColumnMetaData[] colmds = null;
        JoinMetaData joinmd = null;
        ElementMetaData elemmd = null;
        KeyMetaData keymd = null;
        ValueMetaData valuemd = null;
        OrderMetaData ordermd = null;
        IndexMetaData idxmd = null;
        UniqueMetaData unimd = null;
        ForeignKeyMetaData fkmd = null;
        Map extensions = null;
        Class convertConverterCls = null;

        for (AnnotationObject annotation : annotations)
        {
            String annName = annotation.getName();
            Map annotationValues = annotation.getNameValueMap();

            if (annName.equals(JDOAnnotationUtils.PERSISTENT))
            {
                String pkStr = "" + annotationValues.get("primaryKey");
                Boolean pk = null;
                if (!StringUtils.isWhitespace(pkStr))
                {
                    pk = Boolean.valueOf(pkStr);
                }
                String dfgStr = (String) annotationValues.get("defaultFetchGroup");
                Boolean dfg = null;
                if (!StringUtils.isWhitespace(dfgStr))
                {
                    dfg = Boolean.valueOf(dfgStr);
                }
                String nullValue = JDOAnnotationUtils.getNullValueString((NullValue) annotationValues.get("nullValue"));
                String embStr = (String) annotationValues.get("embedded");
                Boolean embedded = null;
                if (!StringUtils.isWhitespace(embStr))
                {
                    embedded = Boolean.valueOf(embStr);
                }
                String serStr = (String) annotationValues.get("serialized");
                Boolean serialized = null;
                if (!StringUtils.isWhitespace(serStr))
                {
                    serialized = Boolean.valueOf(serStr);
                }
                String depStr = (String) annotationValues.get("dependent");
                Boolean dependent = null;
                if (!StringUtils.isWhitespace(depStr))
                {
                    dependent = Boolean.valueOf(depStr);
                }

                String valueStrategy = JDOAnnotationUtils.getValueGenerationStrategyString((IdGeneratorStrategy) annotationValues.get("valueStrategy"));
                String customValueStrategy = (String) annotationValues.get("customValueStrategy");
                if (!StringUtils.isWhitespace(customValueStrategy))
                {
                    // User has provided an extension strategy
                    valueStrategy = customValueStrategy;
                }

                FieldPersistenceModifier modifier = JDOAnnotationUtils.getFieldPersistenceModifier((PersistenceModifier) annotationValues.get("persistenceModifier"));
                if (modifier == null)
                {
                    modifier = FieldPersistenceModifier.PERSISTENT;
                }
                String sequence = (String) annotationValues.get("sequence");
                String mappedBy = (String) annotationValues.get("mappedBy");
                String table = (String) annotationValues.get("table");
                String column = (String) annotationValues.get("column");
                String loadFetchGroup = (String) annotationValues.get("loadFetchGroup");
                String fieldTypeName = null;
                int recursionDepth = ((Integer) annotationValues.get("recursionDepth")).intValue();
                cacheable = (String) annotationValues.get("cacheable");
                Class[] fieldTypes = (Class[]) annotationValues.get("types");
                if (fieldTypes != null && fieldTypes.length > 0)
                {
                    StringBuilder typeStr = new StringBuilder();
                    for (Class fieldType : fieldTypes)
                    {
                        if (typeStr.length() > 0)
                        {
                            typeStr.append(',');
                        }
                        if (fieldType != null && fieldType != void.class)
                        {
                            typeStr.append(fieldType.getName());
                        }
                    }
                    fieldTypeName = typeStr.toString();
                }
                dependentElement = (String) annotationValues.get("dependentElement");
                serializedElement = (String) annotationValues.get("serializedElement");
                embeddedElement = (String) annotationValues.get("embeddedElement");
                dependentKey = (String) annotationValues.get("dependentKey");
                serializedKey = (String) annotationValues.get("serializedKey");
                embeddedKey = (String) annotationValues.get("embeddedKey");
                dependentValue = (String) annotationValues.get("dependentValue");
                serializedValue = (String) annotationValues.get("serializedValue");
                embeddedValue = (String) annotationValues.get("embeddedValue");
                Class converterCls = (Class) annotationValues.get("converter");
                if (converterCls == UseDefault.class)
                {
                    converterCls = null;
                }
                Boolean disableConversion = (Boolean)annotationValues.get("useDefaultConversion");

                mmd = member.isProperty() ? new PropertyMetaData(cmd, member.getName()) : new FieldMetaData(cmd, member.getName());

                if (isPersistenceContext())
                {
                    if (disableConversion != null && disableConversion)
                    {
                        mmd.setTypeConverterDisabled();
                    }
                    else if (converterCls != null)
                    {
                        TypeManager typeMgr = mmgr.getNucleusContext().getTypeManager();
                        if (typeMgr.getTypeConverterForName(converterCls.getName()) == null)
                        {
                            // Not yet cached an instance of this converter so create one
                            AttributeConverter conv = JDOTypeConverterUtils.createAttributeConverter(mmgr.getNucleusContext(), converterCls);
                            Class attrType = JDOTypeConverterUtils.getAttributeTypeForAttributeConverter(converterCls, member.getType());
                            Class dbType = JDOTypeConverterUtils.getDatastoreTypeForAttributeConverter(converterCls, attrType, null);

                            // Register the TypeConverter under the name of the AttributeConverter class
                            JDOTypeConverter typeConv = new JDOTypeConverter(conv);
                            typeMgr.registerConverter(converterCls.getName(), typeConv, attrType, dbType, false, null);
                        }

                        mmd.setTypeConverterName(converterCls.getName());
                    }
                }

                if (modifier != null)
                {
                    mmd.setPersistenceModifier(modifier);
                }
                if (dfg != null)
                {
                    mmd.setDefaultFetchGroup(dfg);
                }
                if (pk != null)
                {
                    mmd.setPrimaryKey(pk);
                }
                if (embedded != null)
                {
                    mmd.setEmbedded(embedded);
                }
                if (serialized != null)
                {
                    mmd.setSerialised(serialized);
                }
                if (dependent != null)
                {
                    mmd.setDependent(dependent);
                }
                mmd.setNullValue(org.datanucleus.metadata.NullValue.getNullValue(nullValue));
                mmd.setMappedBy(mappedBy);
                mmd.setColumn(column);
                mmd.setTable(table);
                mmd.setRecursionDepth(recursionDepth);
                mmd.setLoadFetchGroup(loadFetchGroup);
                mmd.setValueStrategy(valueStrategy);
                mmd.setSequence(sequence);
                mmd.setFieldTypes(fieldTypeName);

                // Add any columns defined on the @Persistent
                Column[] columns = (Column[]) annotationValues.get("columns");
                if (columns != null && columns.length > 0)
                {
                    for (Column col : columns)
                    {
                        mmd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(col));
                    }
                }
                JDOAnnotationUtils.addExtensionsToMetaData(mmd, (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.PRIMARY_KEY))
            {
                primaryKey = true;
                if (cmd.getIdentityType() == IdentityType.DATASTORE)
                {
                    // ClassMetaData was created as DATASTORE so change it to APPLICATION
                    cmd.setIdentityType(IdentityType.APPLICATION);
                }
            }
            else if (annName.equals(JDOAnnotationUtils.SERIALIZED))
            {
                serialised = true;
            }
            else if (annName.equals(JDOAnnotationUtils.NOTPERSISTENT))
            {
                nonPersistentField = true;
            }
            else if (annName.equals(JDOAnnotationUtils.TRANSACTIONAL))
            {
                transactionalField = true;
            }
            else if (annName.equals(JDOAnnotationUtils.COLUMNS))
            {
                // Multiple column specification
                Column[] cols = (Column[]) annotationValues.get("value");
                if (cols != null && cols.length > 0)
                {
                    colmds = new ColumnMetaData[cols.length];
                    for (int j = 0; j < cols.length; j++)
                    {
                        colmds[j] = JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(cols[j]);
                        JDOAnnotationUtils.addExtensionsToMetaData(colmds[j], cols[j].extensions());
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.COLUMN))
            {
                // Single column specification
                colmds = new ColumnMetaData[1];
                colmds[0] = JDOAnnotationUtils.getColumnMetaDataForAnnotations(annotationValues);
                JDOAnnotationUtils.addExtensionsToMetaData(colmds[0], (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.JOIN))
            {
                String joinColumn = (String) annotationValues.get("column");
                String joinOuter = (String) annotationValues.get("outer");
                String deleteAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("deleteAction"));
                String pkName = (String) annotationValues.get("primaryKey");
                String fkName = (String) annotationValues.get("foreignKey");
                String generateFK = (String) annotationValues.get("generateForeignKey");
                String indexed = (String) annotationValues.get("indexed");
                String indexName = (String) annotationValues.get("index");
                String unique = (String) annotationValues.get("unique");
                String uniqueName = (String) annotationValues.get("uniqueKey");
                String generatePK = (String) annotationValues.get("generatePrimaryKey");
                if (!StringUtils.isWhitespace(uniqueName))
                {
                    unique = "true";
                }
                if (!StringUtils.isWhitespace(indexName))
                {
                    indexed = "true";
                }
                Column[] joinColumns = (Column[]) annotationValues.get("columns");
                joinmd = new JoinMetaData();
                joinmd.setColumnName(joinColumn);
                joinmd.setOuter(MetaDataUtils.getBooleanForString(joinOuter, false));
                joinmd.setIndexed(IndexedValue.getIndexedValue(indexed));
                joinmd.setUnique(unique);
                joinmd.setDeleteAction(deleteAction);

                if (!StringUtils.isWhitespace(pkName))
                {
                    PrimaryKeyMetaData pkmd = new PrimaryKeyMetaData();
                    pkmd.setName(pkName);
                    joinmd.setPrimaryKeyMetaData(pkmd);
                }
                else if (generatePK != null && generatePK.equalsIgnoreCase("true"))
                {
                    joinmd.setPrimaryKeyMetaData(new PrimaryKeyMetaData());
                }

                if (!StringUtils.isWhitespace(fkName))
                {
                    ForeignKeyMetaData joinFkmd = joinmd.getForeignKeyMetaData();
                    if (joinFkmd == null)
                    {
                        joinFkmd = new ForeignKeyMetaData();
                        joinFkmd.setName(fkName);
                        joinmd.setForeignKeyMetaData(joinFkmd);
                    }
                    else
                    {
                        joinFkmd.setName(fkName);
                    }
                }
                else if (generateFK != null && generateFK.equalsIgnoreCase("true"))
                {
                    joinmd.setForeignKeyMetaData(new ForeignKeyMetaData());
                }

                if (!StringUtils.isWhitespace(indexName))
                {
                    IndexMetaData joinIdxmd = joinmd.getIndexMetaData();
                    if (joinIdxmd == null)
                    {
                        joinIdxmd = new IndexMetaData();
                        joinmd.setIndexMetaData(joinIdxmd);
                    }
                    joinIdxmd.setName(indexName);
                }

                if (!StringUtils.isWhitespace(uniqueName))
                {
                    UniqueMetaData joinUnimd = joinmd.getUniqueMetaData();
                    if (joinUnimd == null)
                    {
                        joinUnimd = new UniqueMetaData();
                        joinmd.setUniqueMetaData(joinUnimd);
                    }
                    joinUnimd.setName(uniqueName);
                }
                if (joinColumns != null && joinColumns.length > 0)
                {
                    for (Column joinCol : joinColumns)
                    {
                        joinmd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(joinCol));
                    }
                }
                JDOAnnotationUtils.addExtensionsToMetaData(joinmd, (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.ELEMENT))
            {
                // Element of a Collection/Array
                elementTypes = (Class[]) annotationValues.get("types");
                embeddedElement = (String) annotationValues.get("embedded");
                serializedElement = (String) annotationValues.get("serialized");
                dependentElement = (String) annotationValues.get("dependent");

                String elementDeleteAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("deleteAction"));
                String elementUpdateAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("updateAction"));
                String fkName = (String) annotationValues.get("foreignKey");
                String generateFK = (String) annotationValues.get("generateForeignKey");
                String indexed = (String) annotationValues.get("indexed");
                String indexName = (String) annotationValues.get("index");
                String unique = (String) annotationValues.get("unique");
                String uniqueName = (String) annotationValues.get("uniqueKey");
                Class converterCls = (Class) annotationValues.get("converter");
                if (converterCls == UseDefault.class)
                {
                    converterCls = null;
                }
                Boolean disableConversion = (Boolean)annotationValues.get("useDefaultConversion");

                if (!StringUtils.isWhitespace(uniqueName))
                {
                    unique = "true";
                }
                if (!StringUtils.isWhitespace(indexName))
                {
                    indexed = "true";
                }
                elemmd = new ElementMetaData();
                elemmd.setTable((String) annotationValues.get("table"));
                elemmd.setColumnName((String) annotationValues.get("column"));
                elemmd.setDeleteAction(elementDeleteAction);
                elemmd.setUpdateAction(elementUpdateAction);
                elemmd.setIndexed(IndexedValue.getIndexedValue(indexed));
                elemmd.setUnique(MetaDataUtils.getBooleanForString(unique, false));
                elemmd.setMappedBy((String) annotationValues.get("mappedBy"));
                if (!StringUtils.isWhitespace(fkName))
                {
                    ForeignKeyMetaData elemFkmd = elemmd.getForeignKeyMetaData();
                    if (elemFkmd == null)
                    {
                        elemFkmd = new ForeignKeyMetaData();
                        elemFkmd.setName(fkName);
                        elemmd.setForeignKeyMetaData(elemFkmd);
                    }
                    else
                    {
                        elemFkmd.setName(fkName);
                    }
                }
                else if (generateFK != null && generateFK.equalsIgnoreCase("true"))
                {
                    elemmd.setForeignKeyMetaData(new ForeignKeyMetaData());
                }

                if (!StringUtils.isWhitespace(indexName))
                {
                    IndexMetaData elemIdxmd = elemmd.getIndexMetaData();
                    if (elemIdxmd == null)
                    {
                        elemIdxmd = new IndexMetaData();
                        elemmd.setIndexMetaData(elemIdxmd);
                    }
                    elemIdxmd.setName(indexName);
                }

                if (!StringUtils.isWhitespace(uniqueName))
                {
                    UniqueMetaData elemUnimd = elemmd.getUniqueMetaData();
                    if (elemUnimd == null)
                    {
                        elemUnimd = new UniqueMetaData();
                        elemmd.setUniqueMetaData(elemUnimd);
                    }
                    elemUnimd.setName(uniqueName);
                }

                Column[] elementColumns = (Column[]) annotationValues.get("columns");
                if (elementColumns != null && elementColumns.length > 0)
                {
                    for (Column elementCol : elementColumns)
                    {
                        elemmd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(elementCol));
                    }
                }

                if (isPersistenceContext())
                {
                    if (disableConversion != null && disableConversion)
                    {
                        elemmd.addExtension(MetaData.EXTENSION_MEMBER_TYPE_CONVERTER_DISABLED, "true");
                    }
                    else if (converterCls != null)
                    {
                        TypeManager typeMgr = mmgr.getNucleusContext().getTypeManager();
                        if (typeMgr.getTypeConverterForName(converterCls.getName()) == null)
                        {
                            // Not yet cached an instance of this converter so create one
                            AttributeConverter conv = JDOTypeConverterUtils.createAttributeConverter(mmgr.getNucleusContext(), converterCls);
                            Class attrType = JDOTypeConverterUtils.getAttributeTypeForAttributeConverter(converterCls, 
                                ClassUtils.getCollectionElementType(member.getType(), member.getGenericType()));
                            Class dbType = JDOTypeConverterUtils.getDatastoreTypeForAttributeConverter(converterCls, attrType, null);

                            // Register the TypeConverter under the name of the AttributeConverter class
                            JDOTypeConverter typeConv = new JDOTypeConverter(conv);
                            typeMgr.registerConverter(converterCls.getName(), typeConv, attrType, dbType, false, null);
                        }

                        elemmd.addExtension(MetaData.EXTENSION_MEMBER_TYPE_CONVERTER_NAME, converterCls.getName());
                    }
                }

                JDOAnnotationUtils.addExtensionsToMetaData(elemmd, (Extension[]) annotationValues.get("extensions"));

                Embedded[] embeddedMappings = (Embedded[]) annotationValues.get("embeddedMapping");
                if (embeddedMappings != null && embeddedMappings.length > 0)
                {
                    // Embedded element
                    EmbeddedMetaData embmd = new EmbeddedMetaData();
                    embmd.setOwnerMember(embeddedMappings[0].ownerMember());
                    embmd.setNullIndicatorColumn(embeddedMappings[0].nullIndicatorColumn());
                    embmd.setNullIndicatorValue(embeddedMappings[0].nullIndicatorValue());
                    try
                    {
                        Discriminator disc = embeddedMappings[0].discriminatorColumnName();
                        if (disc != null)
                        {
                            DiscriminatorMetaData dismd = embmd.newDiscriminatorMetadata();
                            dismd.setColumnName(disc.column());
                            dismd.setStrategy(JDOAnnotationUtils.getDiscriminatorStrategyString(disc.strategy()));
                            // TODO Support other attributes of discriminator?
                        }
                    }
                    catch (Throwable thr)
                    {
                        // Ignore this. Maybe not using JDO3.1 jar
                    }
                    elemmd.setEmbeddedMetaData(embmd);
                    embeddedElementMembers = embeddedMappings[0].members();
                    // Delay addition of embeddedElementMembers til completion of this loop so we have the
                    // element type
                }
            }
            else if (annName.equals(JDOAnnotationUtils.KEY))
            {
                // Key of a Map
                keyTypes = (Class[]) annotationValues.get("types");
                embeddedKey = (String) annotationValues.get("embedded");
                serializedKey = (String) annotationValues.get("serialized");
                dependentKey = (String) annotationValues.get("dependent");

                String keyDeleteAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("deleteAction"));
                String keyUpdateAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("updateAction"));
                String fkName = (String) annotationValues.get("foreignKey");
                String generateFK = (String) annotationValues.get("generateForeignKey");
                String indexed = (String) annotationValues.get("indexed");
                String indexName = (String) annotationValues.get("index");
                String unique = (String) annotationValues.get("unique");
                String uniqueName = (String) annotationValues.get("uniqueKey");
                Class converterCls = (Class) annotationValues.get("converter");
                if (converterCls == UseDefault.class)
                {
                    converterCls = null;
                }
                Boolean disableConversion = (Boolean)annotationValues.get("useDefaultConversion");

                if (!StringUtils.isWhitespace(uniqueName))
                {
                    unique = "true";
                }
                if (!StringUtils.isWhitespace(indexName))
                {
                    indexed = "true";
                }
                keymd = new KeyMetaData();
                keymd.setTable((String) annotationValues.get("table"));
                keymd.setColumnName((String) annotationValues.get("column"));
                keymd.setDeleteAction(keyDeleteAction);
                keymd.setUpdateAction(keyUpdateAction);
                keymd.setIndexed(IndexedValue.getIndexedValue(indexed));
                keymd.setUnique(MetaDataUtils.getBooleanForString(unique, false));
                keymd.setMappedBy((String) annotationValues.get("mappedBy"));
                if (!StringUtils.isWhitespace(fkName))
                {
                    ForeignKeyMetaData keyFkmd = keymd.getForeignKeyMetaData();
                    if (keyFkmd == null)
                    {
                        keyFkmd = new ForeignKeyMetaData();
                        keyFkmd.setName(fkName);
                        keymd.setForeignKeyMetaData(keyFkmd);
                    }
                    else
                    {
                        keyFkmd.setName(fkName);
                    }
                }
                else if (generateFK != null && generateFK.equalsIgnoreCase("true"))
                {
                    keymd.setForeignKeyMetaData(new ForeignKeyMetaData());
                }

                if (!StringUtils.isWhitespace(indexName))
                {
                    IndexMetaData keyIdxmd = keymd.getIndexMetaData();
                    if (keyIdxmd == null)
                    {
                        keyIdxmd = new IndexMetaData();
                        keymd.setIndexMetaData(keyIdxmd);
                    }
                    keyIdxmd.setName(indexName);
                }

                if (!StringUtils.isWhitespace(uniqueName))
                {
                    UniqueMetaData keyUnimd = keymd.getUniqueMetaData();
                    if (keyUnimd == null)
                    {
                        keyUnimd = new UniqueMetaData();
                        keymd.setUniqueMetaData(keyUnimd);
                    }
                    keyUnimd.setName(uniqueName);
                }

                Column[] keyColumns = (Column[]) annotationValues.get("columns");
                if (keyColumns != null && keyColumns.length > 0)
                {
                    for (Column keyCol : keyColumns)
                    {
                        keymd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(keyCol));
                    }
                }

                if (isPersistenceContext())
                {
                    if (disableConversion != null && disableConversion)
                    {
                        keymd.addExtension(MetaData.EXTENSION_MEMBER_TYPE_CONVERTER_DISABLED, "true");
                    }
                    else if (converterCls != null)
                    {
                        TypeManager typeMgr = mmgr.getNucleusContext().getTypeManager();
                        if (typeMgr.getTypeConverterForName(converterCls.getName()) == null)
                        {
                            // Not yet cached an instance of this converter so create one
                            AttributeConverter conv = JDOTypeConverterUtils.createAttributeConverter(mmgr.getNucleusContext(), converterCls);
                            Class attrType = JDOTypeConverterUtils.getAttributeTypeForAttributeConverter(converterCls, 
                                ClassUtils.getMapKeyType(member.getType(), member.getGenericType()));
                            Class dbType = JDOTypeConverterUtils.getDatastoreTypeForAttributeConverter(converterCls, attrType, null);

                            // Register the TypeConverter under the name of the AttributeConverter class
                            JDOTypeConverter typeConv = new JDOTypeConverter(conv);
                            typeMgr.registerConverter(converterCls.getName(), typeConv, attrType, dbType, false, null);
                        }

                        keymd.addExtension(MetaData.EXTENSION_MEMBER_TYPE_CONVERTER_NAME, converterCls.getName());
                    }
                }

                JDOAnnotationUtils.addExtensionsToMetaData(keymd, (Extension[]) annotationValues.get("extensions"));

                Embedded[] embeddedMappings = (Embedded[]) annotationValues.get("embeddedMapping");
                if (embeddedMappings != null && embeddedMappings.length > 0)
                {
                    // Embedded key
                    EmbeddedMetaData embmd = new EmbeddedMetaData();
                    embmd.setOwnerMember(embeddedMappings[0].ownerMember());
                    embmd.setNullIndicatorColumn(embeddedMappings[0].nullIndicatorColumn());
                    embmd.setNullIndicatorValue(embeddedMappings[0].nullIndicatorValue());
                    keymd.setEmbeddedMetaData(embmd);
                    embeddedKeyMembers = embeddedMappings[0].members();
                    // Delay addition of embeddedKeyMembers til completion of this loop so we have the key
                    // type
                }
            }
            else if (annName.equals(JDOAnnotationUtils.VALUE))
            {
                // Value of a Map
                valueTypes = (Class[]) annotationValues.get("types");
                embeddedValue = (String) annotationValues.get("embedded");
                serializedValue = (String) annotationValues.get("serialized");
                dependentValue = (String) annotationValues.get("dependent");

                String valueDeleteAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("deleteAction"));
                String valueUpdateAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("updateAction"));
                String fkName = (String) annotationValues.get("foreignKey");
                String generateFK = (String) annotationValues.get("generateForeignKey");
                String indexed = (String) annotationValues.get("indexed");
                String indexName = (String) annotationValues.get("index");
                String unique = (String) annotationValues.get("unique");
                String uniqueName = (String) annotationValues.get("uniqueKey");
                Class converterCls = (Class) annotationValues.get("converter");
                if (converterCls == UseDefault.class)
                {
                    converterCls = null;
                }
                Boolean disableConversion = (Boolean)annotationValues.get("useDefaultConversion");

                if (!StringUtils.isWhitespace(uniqueName))
                {
                    unique = "true";
                }
                if (!StringUtils.isWhitespace(indexName))
                {
                    indexed = "true";
                }
                valuemd = new ValueMetaData();
                valuemd.setTable((String) annotationValues.get("table"));
                valuemd.setColumnName((String) annotationValues.get("column"));
                valuemd.setDeleteAction(valueDeleteAction);
                valuemd.setUpdateAction(valueUpdateAction);
                valuemd.setIndexed(IndexedValue.getIndexedValue(indexed));
                valuemd.setUnique(MetaDataUtils.getBooleanForString(unique, false));
                valuemd.setMappedBy((String) annotationValues.get("mappedBy"));
                if (!StringUtils.isWhitespace(fkName))
                {
                    ForeignKeyMetaData valueFkmd = valuemd.getForeignKeyMetaData();
                    if (valueFkmd == null)
                    {
                        valueFkmd = new ForeignKeyMetaData();
                        valueFkmd.setName(fkName);
                        valuemd.setForeignKeyMetaData(valueFkmd);
                    }
                    else
                    {
                        valueFkmd.setName(fkName);
                    }
                }
                else if (generateFK != null && generateFK.equalsIgnoreCase("true"))
                {
                    valuemd.setForeignKeyMetaData(new ForeignKeyMetaData());
                }

                if (!StringUtils.isWhitespace(indexName))
                {
                    IndexMetaData valueIdxmd = valuemd.getIndexMetaData();
                    if (valueIdxmd == null)
                    {
                        valueIdxmd = new IndexMetaData();
                        valuemd.setIndexMetaData(valueIdxmd);
                    }
                    valueIdxmd.setName(indexName);
                }

                if (!StringUtils.isWhitespace(uniqueName))
                {
                    UniqueMetaData valueUnimd = valuemd.getUniqueMetaData();
                    if (valueUnimd == null)
                    {
                        valueUnimd = new UniqueMetaData();
                        valuemd.setUniqueMetaData(valueUnimd);
                    }
                    valueUnimd.setName(uniqueName);
                }

                Column[] valueColumns = (Column[]) annotationValues.get("columns");
                if (valueColumns != null && valueColumns.length > 0)
                {
                    for (Column valueCol : valueColumns)
                    {
                        valuemd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(valueCol));
                    }
                }

                if (isPersistenceContext())
                {
                    if (disableConversion != null && disableConversion)
                    {
                        valuemd.addExtension(MetaData.EXTENSION_MEMBER_TYPE_CONVERTER_DISABLED, "true");
                    }
                    else if (converterCls != null)
                    {
                        TypeManager typeMgr = mmgr.getNucleusContext().getTypeManager();
                        if (typeMgr.getTypeConverterForName(converterCls.getName()) == null)
                        {
                            // Not yet cached an instance of this converter so create one
                            AttributeConverter conv = JDOTypeConverterUtils.createAttributeConverter(mmgr.getNucleusContext(), converterCls);
                            Class attrType = JDOTypeConverterUtils.getAttributeTypeForAttributeConverter(converterCls, 
                                ClassUtils.getMapValueType(member.getType(), member.getGenericType()));
                            Class dbType = JDOTypeConverterUtils.getDatastoreTypeForAttributeConverter(converterCls, attrType, null);

                            // Register the TypeConverter under the name of the AttributeConverter class
                            JDOTypeConverter typeConv = new JDOTypeConverter(conv);
                            typeMgr.registerConverter(converterCls.getName(), typeConv, attrType, dbType, false, null);
                        }

                        valuemd.addExtension(MetaData.EXTENSION_MEMBER_TYPE_CONVERTER_NAME, converterCls.getName());
                    }
                }

                JDOAnnotationUtils.addExtensionsToMetaData(valuemd, (Extension[]) annotationValues.get("extensions"));

                Embedded[] embeddedMappings = (Embedded[]) annotationValues.get("embeddedMapping");
                if (embeddedMappings != null && embeddedMappings.length > 0)
                {
                    // Embedded value
                    EmbeddedMetaData embmd = new EmbeddedMetaData();
                    embmd.setOwnerMember(embeddedMappings[0].ownerMember());
                    embmd.setNullIndicatorColumn(embeddedMappings[0].nullIndicatorColumn());
                    embmd.setNullIndicatorValue(embeddedMappings[0].nullIndicatorValue());
                    valuemd.setEmbeddedMetaData(embmd);
                    embeddedValueMembers = embeddedMappings[0].members();
                    // Delay addition of embeddedValueMembers til completion of this loop so we have the value type
                }
            }
            else if (annName.equals(JDOAnnotationUtils.ORDER))
            {
                ordermd = new OrderMetaData();
                ordermd.setColumnName((String) annotationValues.get("column"));
                ordermd.setMappedBy((String) annotationValues.get("mappedBy"));

                Column[] orderColumns = (Column[]) annotationValues.get("columns");
                if (orderColumns != null && orderColumns.length > 0)
                {
                    for (Column orderCol : orderColumns)
                    {
                        ordermd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(orderCol));
                    }
                }
                JDOAnnotationUtils.addExtensionsToMetaData(ordermd, (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.EMBEDDED))
            {
                embeddedMember = true;
                embeddedOwnerField = (String) annotationValues.get("ownerMember");
                if (StringUtils.isWhitespace(embeddedOwnerField))
                {
                    embeddedOwnerField = null;
                }
                embeddedNullIndicatorColumn = (String) annotationValues.get("nullIndicatorColumn");
                if (StringUtils.isWhitespace(embeddedNullIndicatorColumn))
                {
                    embeddedNullIndicatorColumn = null;
                }
                embeddedNullIndicatorValue = (String) annotationValues.get("nullIndicatorValue");
                if (StringUtils.isWhitespace(embeddedNullIndicatorValue))
                {
                    embeddedNullIndicatorValue = null;
                }
                embeddedMembers = (Persistent[]) annotationValues.get("members");
                if (embeddedMembers != null && embeddedMembers.length == 0)
                {
                    embeddedMembers = null;
                }
                // TODO Support discriminator
            }
            else if (annName.equals(JDOAnnotationUtils.INDEX))
            {
                // Index for the field
                String name = (String) annotationValues.get("name");
                String table = (String) annotationValues.get("table");
                String unique = (String) annotationValues.get("unique");
                String[] members = (String[]) annotationValues.get("members");
                Column[] columns = (Column[]) annotationValues.get("columns");

                idxmd = JDOAnnotationUtils.getIndexMetaData(name, table, unique, members, columns);
                JDOAnnotationUtils.addExtensionsToMetaData(idxmd, (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.UNIQUE))
            {
                // Unique for the field
                String name = (String) annotationValues.get("name");
                String table = (String) annotationValues.get("table");
                String deferred = (String) annotationValues.get("deferred");
                String[] members = (String[]) annotationValues.get("members");
                Column[] columns = (Column[]) annotationValues.get("columns");

                unimd = JDOAnnotationUtils.getUniqueMetaData(name, table, deferred, members, columns);
                JDOAnnotationUtils.addExtensionsToMetaData(unimd, (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.FOREIGNKEY))
            {
                // ForeignKey for field
                String name = (String) annotationValues.get("name");
                String table = (String) annotationValues.get("table");
                String unique = (String) annotationValues.get("unique");
                String deferred = (String) annotationValues.get("deferred");
                String deleteAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("deleteAction"));
                String updateAction = JDOAnnotationUtils.getForeignKeyActionString((ForeignKeyAction) annotationValues.get("updateAction"));
                String[] members = (String[]) annotationValues.get("members");
                Column[] columns = (Column[]) annotationValues.get("columns");

                fkmd = JDOAnnotationUtils.getFKMetaData(name, table, unique, deferred, deleteAction, updateAction, members, columns);
                JDOAnnotationUtils.addExtensionsToMetaData(fkmd, (Extension[]) annotationValues.get("extensions"));
            }
            else if (annName.equals(JDOAnnotationUtils.CACHEABLE))
            {
                String cache = (String) annotationValues.get("value");
                if (cache != null)
                {
                    cacheable = cache;
                }
            }
            else if (annName.equals(JDOAnnotationUtils.CONVERT))
            {
                convertConverterCls = (Class) annotationValues.get("value");
                if (convertConverterCls == UseDefault.class)
                {
                    convertConverterCls = null;
                }
                Boolean enabled = (Boolean) annotationValues.get("enabled");
                if (!enabled)
                {
                    convertConverterCls = null;
                }
            }
            else if (annName.equals(JDOAnnotationUtils.EXTENSIONS))
            {
                Extension[] values = (Extension[]) annotationValues.get("value");
                if (values != null && values.length > 0)
                {
                    extensions = new HashMap(values.length);
                    for (Extension ext : values)
                    {
                        String vendorName = ext.vendorName();
                        if (StringUtils.isWhitespace(vendorName))
                        {
                            throw new InvalidMetaDataException("044160", vendorName, ext.key().toString(), ext.value().toString());
                        }
                        else if (vendorName.equalsIgnoreCase(MetaData.VENDOR_NAME))
                        {
                            extensions.put(ext.key().toString(), ext.value().toString());
                        }
                    }
                }
            }
            else if (annName.equals(JDOAnnotationUtils.EXTENSION))
            {
                String vendorName = (String)annotationValues.get("vendorName");
                if (StringUtils.isWhitespace(vendorName))
                {
                    throw new InvalidMetaDataException("044160", vendorName, annotationValues.get("key"), annotationValues.get("value"));
                }
                else if (vendorName.equalsIgnoreCase(MetaData.VENDOR_NAME))
                {
                    extensions = new HashMap(1);
                    extensions.put((String)annotationValues.get("key"), (String)annotationValues.get("value"));
                }
            }
            else
            {
                NucleusLogger.METADATA.debug(Localiser.msg("044211", cmd.getFullClassName(), member.getName(), annotation.getName()));
            }
        }

        if (mmd == null && (transactionalField || nonPersistentField || primaryKey || colmds != null || serialised || embeddedOwnerField != null || embeddedMember ||
            embeddedNullIndicatorColumn != null || embeddedNullIndicatorValue != null || embeddedMembers != null || elemmd != null || keymd != null || valuemd != null || 
            ordermd != null || idxmd != null || unimd != null || fkmd != null || joinmd != null || extensions != null || convertConverterCls != null))
        {
            // @Persistent not supplied but other relevant annotations defined, so add default metadata element
            mmd = member.isProperty() ? new PropertyMetaData(cmd, member.getName()) : new FieldMetaData(cmd, member.getName());

            if (primaryKey)
            {
                mmd.setPersistenceModifier(FieldPersistenceModifier.PERSISTENT);
                mmd.setPrimaryKey(primaryKey);
            }
            if (serialised)
            {
                mmd.setPersistenceModifier(FieldPersistenceModifier.PERSISTENT);
            }
        }

        if (mmd != null)
        {
            cmd.addMember(mmd);

            if (primaryKey)
            {
                mmd.setPrimaryKey(true);
            }
            if (serialised)
            {
                mmd.setSerialised(true);
            }
            if (embeddedMember)
            {
                mmd.setEmbedded(true);
            }
            if (nonPersistentField)
            {
                mmd.setNotPersistent();
            }
            if (transactionalField)
            {
                mmd.setTransactional();
            }

            if (isPersistenceContext())
            {
                if (convertConverterCls != null)
                {
                    TypeManager typeMgr = mmgr.getNucleusContext().getTypeManager();
                    if (typeMgr.getTypeConverterForName(convertConverterCls.getName()) == null)
                    {
                        // Not yet cached an instance of this converter so create one
                        AttributeConverter conv = JDOTypeConverterUtils.createAttributeConverter(mmgr.getNucleusContext(), convertConverterCls);
                        Class attrType = JDOTypeConverterUtils.getAttributeTypeForAttributeConverter(convertConverterCls, member.getType());
                        Class dbType = JDOTypeConverterUtils.getDatastoreTypeForAttributeConverter(convertConverterCls, attrType, null);

                        // Register the TypeConverter under the name of the AttributeConverter class
                        JDOTypeConverter typeConv = new JDOTypeConverter(conv);
                        typeMgr.registerConverter(convertConverterCls.getName(), typeConv, attrType, dbType, false, null);
                    }

                    mmd.setTypeConverterName(convertConverterCls.getName());
                }
            }

            // Add any embedded info
            if (embeddedMember)
            {
                if (embeddedOwnerField != null || embeddedNullIndicatorColumn != null || embeddedNullIndicatorValue != null || embeddedMembers != null)
                {
                    EmbeddedMetaData embmd = new EmbeddedMetaData();
                    embmd.setOwnerMember(embeddedOwnerField);
                    embmd.setNullIndicatorColumn(embeddedNullIndicatorColumn);
                    embmd.setNullIndicatorValue(embeddedNullIndicatorValue);
                    mmd.setEmbeddedMetaData(embmd);
                    if (embeddedMembers != null && embeddedMembers.length > 0)
                    {
                        for (Persistent embMember : embeddedMembers)
                        {
                            // Add the metadata for the embedded field/property to the embedded metadata
                            String memberName = embMember.name();
                            if (memberName.indexOf('.') > 0)
                            {
                                memberName = memberName.substring(memberName.lastIndexOf('.') + 1);
                            }
                            AbstractMemberMetaData embfmd = getFieldMetaDataForPersistent(embmd, embMember, isMemberOfClassAField(member.getType(), memberName));
                            embmd.addMember(embfmd);
                        }
                    }
                }
            }

            TypeManager typeManager = mmgr.getNucleusContext().getTypeManager();
            ContainerHandler containerHandler = typeManager.getContainerHandler(member.getType());
            ContainerMetaData contmd = null;

            // If the field is a container then add its container element
            if (containerHandler != null)
            {
                contmd = containerHandler.newMetaData();
            }

            if (contmd instanceof CollectionMetaData)
            {
                Class collectionElementType = null;
                StringBuilder elementTypeStr = new StringBuilder();
                if (elementTypes != null && elementTypes.length > 0 && elementTypes[0] != void.class)
                {
                    // User-specified element type(s)
                    for (Class elementType : elementTypes)
                    {
                        if (elementTypeStr.length() > 0)
                        {
                            elementTypeStr.append(',');
                        }
                        elementTypeStr.append(elementType.getName());
                    }
                    collectionElementType = elementTypes[0]; // Use the first only
                }
                else
                {
                    // Try to derive element type from generics info
                    collectionElementType = ClassUtils.getCollectionElementType(member.getType(), member.getGenericType());
                }

                contmd = new CollectionMetaData();
                contmd.setParent(mmd);
                CollectionMetaData collmd = (CollectionMetaData) contmd;

                collmd.setElementType(elementTypeStr.toString());
                if (!StringUtils.isWhitespace(embeddedElement))
                {
                    collmd.setEmbeddedElement(Boolean.valueOf(embeddedElement));
                }
                if (!StringUtils.isWhitespace(serializedElement))
                {
                    collmd.setSerializedElement(Boolean.valueOf(serializedElement));
                }
                if (!StringUtils.isWhitespace(dependentElement))
                {
                    collmd.setDependentElement(Boolean.valueOf(dependentElement));
                }

                if ((embeddedElementMembers != null || "true".equalsIgnoreCase(embeddedElement)) && elemmd == null)
                {
                    elemmd = new ElementMetaData();
                    mmd.setElementMetaData(elemmd);
                }
                if (elemmd != null)
                {
                    if (embeddedElementMembers != null)
                    {
                        // Add any embedded element mappings
                        EmbeddedMetaData embmd = elemmd.getEmbeddedMetaData();
                        if ("true".equalsIgnoreCase(embeddedElement) && elemmd.getEmbeddedMetaData() == null)
                        {
                            // Create EmbeddedMetaData for element since not existing
                            embmd = elemmd.newEmbeddedMetaData();
                        }
                        for (Persistent embeddedElementMember : embeddedElementMembers)
                        {
                            // Add the metadata for the embedded element to the embedded metadata
                            String memberName = embeddedElementMember.name();
                            if (memberName.indexOf('.') > 0)
                            {
                                memberName = memberName.substring(memberName.lastIndexOf('.') + 1);
                            }
                            AbstractMemberMetaData embfmd = getFieldMetaDataForPersistent(embmd, embeddedElementMember, isMemberOfClassAField(collectionElementType, memberName));
                            embmd.addMember(embfmd);
                        }
                    }
                }
            }
            else if (contmd instanceof ArrayMetaData)
            {
                StringBuilder elementTypeStr = new StringBuilder();
                if (elementTypes != null && elementTypes.length > 0 && elementTypes[0] != void.class)
                {
                    // User-specified element type(s)
                    for (Class elementType : elementTypes)
                    {
                        if (elementTypeStr.length() > 0)
                        {
                            elementTypeStr.append(',');
                        }
                        elementTypeStr.append(elementType.getName());
                    }
                }
                else
                {
                    // Derive from component type
                    elementTypeStr.append(member.getType().getComponentType().getName());
                }

                contmd = new ArrayMetaData();
                contmd.setParent(mmd);
                ArrayMetaData arrmd = (ArrayMetaData) contmd;

                arrmd.setElementType(elementTypeStr.toString());
                if (!StringUtils.isWhitespace(embeddedElement))
                {
                    arrmd.setEmbeddedElement(Boolean.valueOf(embeddedElement));
                }
                if (!StringUtils.isWhitespace(serializedElement))
                {
                    arrmd.setSerializedElement(Boolean.valueOf(serializedElement));
                }
                if (!StringUtils.isWhitespace(dependentElement))
                {
                    arrmd.setDependentElement(Boolean.valueOf(dependentElement));
                }
            }
            else if (contmd instanceof MapMetaData)
            {
                Class mapKeyType = null;
                if (keyTypes != null && keyTypes.length > 0 && keyTypes[0] != void.class)
                {
                    // User-specified key type TODO Support multiple keys (interface implementations)
                    mapKeyType = keyTypes[0];
                }
                else
                {
                    // Try to derive key type from generics info
                    mapKeyType = ClassUtils.getMapKeyType(member.getType(), member.getGenericType());
                }

                Class mapValueType = null;
                if (valueTypes != null && valueTypes.length > 0 && valueTypes[0] != void.class)
                {
                    // User-specified value type TODO Support multiple values (interface implementations)
                    mapValueType = valueTypes[0];
                }
                else
                {
                    // Try to derive value type from generics info
                    mapValueType = ClassUtils.getMapValueType(member.getType(), member.getGenericType());
                }

                contmd = new MapMetaData();
                contmd.setParent(mmd);
                MapMetaData mapmd = (MapMetaData) contmd;

                mapmd.setKeyType((mapKeyType != null ? mapKeyType.getName() : null));
                if (!StringUtils.isWhitespace(embeddedKey))
                {
                    mapmd.setEmbeddedKey(Boolean.valueOf(embeddedKey));
                }
                if (!StringUtils.isWhitespace(serializedKey))
                {
                    mapmd.setSerializedKey(Boolean.valueOf(serializedKey));
                }
                if (!StringUtils.isWhitespace(dependentKey))
                {
                    mapmd.setDependentKey(Boolean.valueOf(dependentKey));
                }

                mapmd.setValueType((mapValueType != null ? mapValueType.getName() : null));
                if (!StringUtils.isWhitespace(embeddedValue))
                {
                    mapmd.setEmbeddedValue(Boolean.valueOf(embeddedValue));
                }
                if (!StringUtils.isWhitespace(serializedValue))
                {
                    mapmd.setSerializedValue(Boolean.valueOf(serializedValue));
                }
                if (!StringUtils.isWhitespace(dependentValue))
                {
                    mapmd.setDependentValue(Boolean.valueOf(dependentValue));
                }

                if ((embeddedKeyMembers != null || "true".equalsIgnoreCase(embeddedKey)) && keymd == null)
                {
                    keymd = new KeyMetaData();
                    mmd.setKeyMetaData(keymd);
                }
                if (keymd != null)
                {
                    if (embeddedKeyMembers != null)
                    {
                        // Add any embedded key mappings
                        EmbeddedMetaData embmd = keymd.getEmbeddedMetaData();
                        if ("true".equalsIgnoreCase(embeddedKey) && keymd.getEmbeddedMetaData() == null)
                        {
                            // Create EmbeddedMetaData for key since not existing
                            embmd = keymd.newEmbeddedMetaData();
                        }
                        for (Persistent embeddedKeyMember : embeddedKeyMembers)
                        {
                            // Add the metadata for the embedded key to the embedded metadata
                            String memberName = embeddedKeyMember.name();
                            if (memberName.indexOf('.') > 0)
                            {
                                memberName = memberName.substring(memberName.lastIndexOf('.') + 1);
                            }
                            AbstractMemberMetaData embfmd = getFieldMetaDataForPersistent(embmd, embeddedKeyMember, isMemberOfClassAField(mapKeyType, memberName));
                            embmd.addMember(embfmd);
                        }
                    }
                }

                if ((embeddedKeyMembers != null || "true".equalsIgnoreCase(embeddedKey)) && valuemd == null)
                {
                    valuemd = new ValueMetaData();
                    mmd.setValueMetaData(valuemd);
                }
                if (valuemd != null)
                {
                    if (embeddedValueMembers != null)
                    {
                        // Add any embedded value mappings
                        EmbeddedMetaData embmd = valuemd.getEmbeddedMetaData();
                        if ("true".equalsIgnoreCase(embeddedValue) && valuemd.getEmbeddedMetaData() == null)
                        {
                            // Create EmbeddedMetaData for value since not existing
                            embmd = valuemd.newEmbeddedMetaData();
                        }
                        for (Persistent embeddedValueMember : embeddedValueMembers)
                        {
                            // Add the metadata for the embedded value to the embedded metadata
                            String memberName = embeddedValueMember.name();
                            if (memberName.indexOf('.') > 0)
                            {
                                memberName = memberName.substring(memberName.lastIndexOf('.') + 1);
                            }
                            AbstractMemberMetaData embfmd = getFieldMetaDataForPersistent(embmd, embeddedValueMember, isMemberOfClassAField(mapValueType, memberName));
                            embmd.addMember(embfmd);
                        }
                    }
                }
            }

            if (contmd != null)
            {
                mmd.setContainer(contmd);

                if (elemmd != null)
                {
                    elemmd.setParent(mmd);
                    mmd.setElementMetaData(elemmd);
                    if (elemmd.getMappedBy() != null && mmd.getMappedBy() == null)
                    {
                        // With collection/array this is the same as mapped-by on the field
                        mmd.setMappedBy(elemmd.getMappedBy());
                    }
                }
                if (keymd != null)
                {
                    keymd.setParent(mmd);
                    mmd.setKeyMetaData(keymd);
                }
                if (valuemd != null)
                {
                    valuemd.setParent(mmd);
                    mmd.setValueMetaData(valuemd);
                }
                if (ordermd != null)
                {
                    ordermd.setParent(mmd);
                    mmd.setOrderMetaData(ordermd);
                }
            }
            if (joinmd != null)
            {
                mmd.setJoinMetaData(joinmd);
            }
            if (colmds != null)
            {
                for (ColumnMetaData colmd : colmds)
                {
                    mmd.addColumn(colmd);
                }
            }
            if (idxmd != null)
            {
                mmd.setIndexMetaData(idxmd);
            }
            if (unimd != null)
            {
                mmd.setUniqueMetaData(unimd);
            }
            if (fkmd != null)
            {
                mmd.setForeignKeyMetaData(fkmd);
            }
            if (cacheable != null && cacheable.equalsIgnoreCase("false"))
            {
                mmd.setCacheable(false);
            }
            if (extensions != null)
            {
                mmd.addExtensions(extensions);
            }
        }

        return mmd;
    }

    /**
     * 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/InterfaceMetaData (to be updated)
     * @param method The method
     */
    protected void processMethodAnnotations(AbstractClassMetaData cmd, Method method)
    {
        // do nothing
    }

    /**
     * Convenience method to create MetaData for a @Persistent annotation representing a field or property.
     * @param parent Parent MetaData
     * @param member The @Persistent annotation
     * @param isField Whether this is a field (otherwise is a property)
     * @return The metadata for the field/property
     */
    private AbstractMemberMetaData getFieldMetaDataForPersistent(MetaData parent, Persistent member, boolean isField)
    {
        AbstractMemberMetaData fmd = isField ? new FieldMetaData(parent, member.name()) : new PropertyMetaData(parent, member.name());

        FieldPersistenceModifier modifier = JDOAnnotationUtils.getFieldPersistenceModifier(member.persistenceModifier());
        if (modifier != null)
        {
            fmd.setPersistenceModifier(modifier);
        }
        if (!StringUtils.isWhitespace(member.defaultFetchGroup()))
        {
            fmd.setDefaultFetchGroup(Boolean.valueOf(member.defaultFetchGroup()));
        }
        if (!StringUtils.isWhitespace(member.primaryKey()))
        {
            fmd.setPrimaryKey(Boolean.valueOf(member.primaryKey()));
        }
        if (!StringUtils.isWhitespace(member.embedded()))
        {
            fmd.setEmbedded(Boolean.valueOf(member.embedded()));
        }
        if (!StringUtils.isWhitespace(member.serialized()))
        {
            fmd.setSerialised(Boolean.valueOf(member.serialized()));
        }
        if (!StringUtils.isWhitespace(member.dependent()))
        {
            fmd.setDependent(Boolean.valueOf(member.dependent()));
        }

        String nullValue = JDOAnnotationUtils.getNullValueString(member.nullValue());
        fmd.setNullValue(org.datanucleus.metadata.NullValue.getNullValue(nullValue));

        fmd.setMappedBy(member.mappedBy());
        fmd.setColumn(member.column());
        fmd.setTable(member.table());
        fmd.setLoadFetchGroup(member.loadFetchGroup());
        fmd.setCacheable(Boolean.valueOf(member.cacheable()));
        fmd.setRecursionDepth(member.recursionDepth());

        String valueStrategy = JDOAnnotationUtils.getValueGenerationStrategyString(member.valueStrategy());
        if (!StringUtils.isWhitespace(member.customValueStrategy()))
        {
            // User has provided an extension strategy
            valueStrategy = member.customValueStrategy();
        }
        fmd.setValueStrategy(valueStrategy);
        fmd.setSequence(member.sequence());

        String fieldTypeName = null;
        Class[] fieldTypes = member.types();
        if (fieldTypes != null && fieldTypes.length > 0)
        {
            StringBuilder typeStr = new StringBuilder();
            for (Class fieldType : fieldTypes)
            {
                if (typeStr.length() > 0)
                {
                    typeStr.append(',');
                }
                if (fieldType != null && fieldType != void.class)
                {
                    typeStr.append(fieldType.getName());
                }
            }
            fieldTypeName = typeStr.toString();
        }
        fmd.setFieldTypes(fieldTypeName);

        Class converterCls = member.converter();
        if (converterCls.getName().equals(UseDefault.class.getName()))
        {
            converterCls = null;
        }
        if (converterCls != null)
        {
            TypeManager typeMgr = mmgr.getNucleusContext().getTypeManager();
            if (typeMgr.getTypeConverterForName(converterCls.getName()) == null)
            {
                // Not yet cached an instance of this converter so create one
                // TODO We don't know the type of the member, so cannot do this
                /*AttributeConverter conv = JDOTypeConverterUtils.createAttributeConverter(mmgr.getNucleusContext(), converterCls);
                if (fieldTypeName != null)
                {
                    Class attrType = JDOTypeConverterUtils.getAttributeTypeForAttributeConverter(converterCls, clr.classForName(fieldTypeName));
                    Class dbType = JDOTypeConverterUtils.getDatastoreTypeForAttributeConverter(converterCls, attrType, null);

                    // Register the TypeConverter under the name of the AttributeConverter class
                    JDOTypeConverter typeConv = new JDOTypeConverter(conv);
                    typeMgr.registerConverter(converterCls.getName(), typeConv, attrType, dbType, false, null);
                }*/
            }

            fmd.setTypeConverterName(converterCls.getName());
        }
        if (member.useDefaultConversion())
        {
            fmd.setTypeConverterDisabled();
        }

        // Add any columns defined on the @Persistent
        Column[] columns = member.columns();
        if (columns != null && columns.length > 0)
        {
            for (Column column : columns)
            {
                fmd.addColumn(JDOAnnotationUtils.getColumnMetaDataForColumnAnnotation(column));
            }
        }

        // Add any extensions defined on the @Persistent
        Extension[] memberExts = member.extensions();
        if (memberExts != null && memberExts.length > 0)
        {
            for (Extension memberExt : memberExts)
            {
                fmd.addExtension(memberExt.key(), memberExt.value());
            }
        }

        return fmd;
    }

    /**
     * Convenience method that tries to find if a specified member name (field or property) is for a field.
     * @param cls The class
     * @param memberName Name of the member
     * @return Whether it is a field (else it's a property).
     */
    private boolean isMemberOfClassAField(Class cls, String memberName)
    {
        try
        {
            cls.getDeclaredField(memberName);
        }
        catch (NoSuchFieldException nsfe)
        {
            return false;
        }
        // TODO It is possible that a memberName is a field AND a property. What do we do then ?
        return true;
    }

    /**
     * Check if a class is persistable, by looking at its annotations.
     * @param annotations Annotations for the class
     * @return The annotationObject for PersistenceCapable
     */
    protected AnnotationObject isClassPersistable(AnnotationObject[] annotations)
    {
        AnnotationObject result = null;
        for (AnnotationObject annotation : annotations)
        {
            String annName = annotation.getName();
            if (annName.equals(JDOAnnotationUtils.PERSISTENCE_CAPABLE))
            {
                if (result == null)
                {
                    result = annotation;
                }
                else
                {
                    // Multiple annotations to be merged
                    result = mergeAnnotation(persistenceCapableDefaults, result, annotation);
                }
            }
        }
        return result;
    }

    /**
     * Merge a duplicated annotation into the original annotation. Iterate all values in the default annotation.
     * If the duplicate annotation has a non-default value, add it to the base value.
     * If both annotations have a non-default value, logs a warning.
     */
    AnnotationObject mergeAnnotation(AnnotationObject defaults, AnnotationObject base, AnnotationObject dup)
    {
        Map baseEntry = base.getNameValueMap();
        Map dupEntry = dup.getNameValueMap();

        for (java.util.Map.Entry entry : defaults.getNameValueMap().entrySet())
        {
            String key = entry.getKey();
            Object defaultValue = entry.getValue();
            Object dupValue = dupEntry.get(key);
            if (!valueEqual(defaultValue, dupValue))
            {
                NucleusLogger.METADATA.warn("Merging duplicated PersistenceCapable annotation : using key=" + key + " with value=" + dupValue + " instead of " + baseEntry.get(key));
                baseEntry.put(key, dupValue);
            }
        }
        return base;
    }

    boolean valueEqual(Object defaultValue, Object dupValue)
    {
        if (defaultValue == null)
        {
            return dupValue == null;
        }
        else if (defaultValue == void.class)
        {
            return dupValue == void.class;
        }
        else if ("".equals(defaultValue))
        {
            return "".equals(dupValue);
        }
        else
        {
            return defaultValue.equals(dupValue);
        }
    }

    /**
     * Check if class is persistence aware, by looking at annotations.
     * @param annotations Annotations for the class
     * @return true if the class has @PersistenceAware
     */
    protected boolean isClassPersistenceAware(AnnotationObject[] annotations)
    {
        for (AnnotationObject annotation : annotations)
        {
            String annName = annotation.getName();
            if (annName.equals(JDOAnnotationUtils.PERSISTENCE_AWARE))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if class has Query annotations (for classes that are not persistable but provide named query definitions.
     * @param annotations Annotations for the class
     * @return true if the class has Named query annotations
     */
    protected boolean doesClassHaveNamedQueries(AnnotationObject[] annotations)
    {
        for (AnnotationObject annotation : annotations)
        {
            String annName = annotation.getName();
            if (annName.equals(JDOAnnotationUtils.QUERIES) || annName.equals(JDOAnnotationUtils.QUERY))
            {
                return true;
            }
        }
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy