org.datanucleus.metadata.EmbeddedMetaData Maven / Gradle / Ivy
Show all versions of datanucleus-core Show documentation
/**********************************************************************
Copyright (c) 2004 Erik Bengtson 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:
2004 Andy Jefferson - toString(), MetaData, javadocs
2004 Andy Jefferson - nullIndicatorColumn/Value, ownerField
...
**********************************************************************/
package org.datanucleus.metadata;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.ClassNotResolvedException;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;
/**
* This element specifies the mapping for an embedded type.
* It contains multiple field elements, one for each field in the type.
*
* The nullIndicatorColumn optionally identifies the name of the column used to indicate whether the embedded instance is null.
* By default, if the value of this column is null, then the embedded instance is null.
* This column might be mapped to a field of the embedded instance but might be a synthetic column for the sole purpose of indicating a null reference.
* The nullIndicatorValue specifies the value to indicate that the embedded instance is null.
* This is only used for non-nullable columns.
* If nullIndicatorColumn is omitted, then the embedded instance is assumed always to exist.
*/
public class EmbeddedMetaData extends MetaData
{
private static final long serialVersionUID = -1180186183944475444L;
/** Name of the field/property in the embedded object that refers to the owner (bidirectional relation). */
protected String ownerMember;
/** Name of a column used for determining if the embedded object is null */
protected String nullIndicatorColumn;
/** Value in the null column indicating that the embedded object is null */
protected String nullIndicatorValue;
/** Discriminator for use when embedding objects with inheritance. */
protected DiscriminatorMetaData discriminatorMetaData;
/** Member definition of the embedded object. */
protected final List members = new ArrayList<>();
/**
* Constructor to create a copy of the passed metadata.
* @param embmd The metadata to copy
*/
public EmbeddedMetaData(EmbeddedMetaData embmd)
{
super(null, embmd);
this.ownerMember = embmd.ownerMember;
this.nullIndicatorColumn = embmd.nullIndicatorColumn;
this.nullIndicatorValue = embmd.nullIndicatorValue;
List embmdMmds = embmd.members;
for (AbstractMemberMetaData embmdMmd : embmdMmds)
{
if (embmdMmd instanceof FieldMetaData)
{
addMember(new FieldMetaData(this, embmdMmd));
}
else
{
addMember(new PropertyMetaData(this, (PropertyMetaData)embmdMmd));
}
}
}
/**
* Default constructor. Use setters to set fields, before calling populate().
*/
public EmbeddedMetaData()
{
}
/**
* Method to populate the embedded MetaData.
* This performs checks on the validity of the field types for embedding.
* @param clr The class loader to use where necessary
* @param primary the primary ClassLoader to use (or null)
*/
public void populate(ClassLoaderResolver clr, ClassLoader primary)
{
// Find the class that the embedded fields apply to
MetaDataManager mmgr = getMetaDataManager();
AbstractMemberMetaData apmd = null; // Field that has
AbstractClassMetaData embCmd = null; // Definition for the embedded class
String embeddedType = null; // Name of the embedded type
MetaData md = getParent();
if (md instanceof AbstractMemberMetaData)
{
// PC embedded in PC object
apmd = (AbstractMemberMetaData)md;
embeddedType = apmd.getTypeName();
embCmd = mmgr.getMetaDataForClassInternal(apmd.getType(), clr);
if (embCmd == null && apmd.getFieldTypes() != null && apmd.getFieldTypes().length == 1)
{
// The specified field is not embeddable, nor is it persistent-interface, so try field-type for embedding
embCmd = mmgr.getMetaDataForClassInternal(clr.classForName(apmd.getFieldTypes()[0]), clr);
}
if (embCmd == null)
{
NucleusLogger.METADATA.error(Localiser.msg("044121", apmd.getFullFieldName(), apmd.getTypeName()));
throw new InvalidMemberMetaDataException("044121", apmd.getClassName(), apmd.getName(), apmd.getTypeName());
}
}
else if (md instanceof ElementMetaData)
{
// PC element embedded in collection
ElementMetaData elemmd = (ElementMetaData)md;
apmd = (AbstractMemberMetaData)elemmd.getParent();
embeddedType = apmd.getCollection().getElementType();
try
{
Class cls = clr.classForName(embeddedType, primary);
embCmd = mmgr.getMetaDataForClassInternal(cls, clr);
}
catch (ClassNotResolvedException cnre)
{
// Should be caught by populating the Collection
}
if (embCmd == null)
{
NucleusLogger.METADATA.error(Localiser.msg("044122", apmd.getFullFieldName(), embeddedType));
throw new InvalidMemberMetaDataException("044122", apmd.getClassName(), apmd.getName(), embeddedType);
}
}
else if (md instanceof KeyMetaData)
{
// PC key embedded in Map
KeyMetaData keymd = (KeyMetaData)md;
apmd = (AbstractMemberMetaData)keymd.getParent();
embeddedType = apmd.getMap().getKeyType();
try
{
Class cls = clr.classForName(embeddedType, primary);
embCmd = mmgr.getMetaDataForClassInternal(cls, clr);
}
catch (ClassNotResolvedException cnre)
{
// Should be caught by populating the Map
}
if (embCmd == null)
{
NucleusLogger.METADATA.error(Localiser.msg("044123", apmd.getFullFieldName(), embeddedType));
throw new InvalidMemberMetaDataException("044123", apmd.getClassName(), apmd.getName(), embeddedType);
}
}
else if (md instanceof ValueMetaData)
{
// PC value embedded in Map
ValueMetaData valuemd = (ValueMetaData)md;
apmd = (AbstractMemberMetaData)valuemd.getParent();
embeddedType = apmd.getMap().getValueType();
try
{
Class cls = clr.classForName(embeddedType, primary);
embCmd = mmgr.getMetaDataForClassInternal(cls, clr);
}
catch (ClassNotResolvedException cnre)
{
// Should be caught by populating the Map
}
if (embCmd == null)
{
NucleusLogger.METADATA.error(Localiser.msg("044124", apmd.getFullFieldName(), embeddedType));
throw new InvalidMemberMetaDataException("044124", apmd.getClassName(), apmd.getName(), embeddedType);
}
}
else
{
throw new InvalidMemberMetaDataException("044165", md.getClass().getName());
}
// Check that all "members" are of the correct type for the embedded object
for (AbstractMemberMetaData mmd : members)
{
if (embCmd instanceof InterfaceMetaData && mmd instanceof FieldMetaData)
{
// Cannot have a field within a persistent interface
throw new InvalidMemberMetaDataException("044129", apmd.getClassName(), apmd.getName(), mmd.getName());
}
}
Set memberNames = new HashSet<>();
for (AbstractMemberMetaData mmd : members)
{
memberNames.add(mmd.getName());
}
// Add fields for the class that aren't in the block using Reflection.
// TODO Consider getting rid of this ... should fall back to the ClassMetaData for the embedded class
// NOTE 1 : We ignore fields in superclasses
// NOTE 2 : We ignore "enhanced" fields (added by the JDO enhancer)
// NOTE 3 : We ignore inner class fields (containing "$")
// NOTE 4 : We sort the fields into ascending alphabetical order
Class embeddedClass = null;
Collections.sort(members);
try
{
// Load the embedded class
embeddedClass = clr.classForName(embeddedType, primary);
// TODO Cater for properties in the populating class when the user defines using setters
// Get all (reflected) fields in the populating class
Field[] cls_fields = embeddedClass.getDeclaredFields();
for (int i=0;i embMmdClass = embeddedClass;
if (!mmd.fieldBelongsToClass())
{
try
{
embMmdClass = clr.classForName(mmd.getClassName(true));
}
catch (ClassNotResolvedException cnre)
{
// Maybe the user specified just "classBasicName.fieldName", so try with package name of this
String fieldClsName = embeddedClass.getPackage().getName() + "." + mmd.getClassName(true);
mmd.setClassName(fieldClsName);
embMmdClass = clr.classForName(fieldClsName);
}
}
if (mmd instanceof FieldMetaData)
{
Field cls_field = null;
try
{
cls_field = embMmdClass.getDeclaredField(mmd.getName());
}
catch (Exception e)
{
// MetaData field doesn't exist in the class!
throw new InvalidMemberMetaDataException("044071", embMmdClass.getName(), mmd.getName());
}
mmd.populate(clr, cls_field, null, primary, mmgr);
}
else
{
Method clsMethod = null;
try
{
clsMethod = embMmdClass.getDeclaredMethod(ClassUtils.getJavaBeanGetterName(mmd.getName(),true));
}
catch(Exception e)
{
try
{
clsMethod = embMmdClass.getDeclaredMethod(ClassUtils.getJavaBeanGetterName(mmd.getName(),false));
}
catch (Exception e2)
{
// MetaData field doesn't exist in the class!
throw new InvalidMemberMetaDataException("044071", embMmdClass.getName(), mmd.getName());
}
}
mmd.populate(clr, null, clsMethod, primary, mmgr);
}
}
if (embCmd.isEmbeddedOnly())
{
// Check for recursive embedding of the same type and throw exception if so.
// We do not support recursive embedding since if a 1-1 this would result in adding embedded columns infinite times, and for 1-N infinite join tables.
for (AbstractMemberMetaData mmd : members)
{
if (mmd.getTypeName().equals(embCmd.getFullClassName()))
{
throw new InvalidMetaDataException("044128", embCmd.getFullClassName(), mmd.getName());
}
else if (mmd.hasCollection() && mmd.getCollection().getElementType().equals(embCmd.getFullClassName()))
{
throw new InvalidMetaDataException("044128", embCmd.getFullClassName(), mmd.getName());
}
}
}
}
/**
* Method to initialise the object, creating all internal convenience arrays.
* @param clr ClassLoader resolver
*/
public void initialise(ClassLoaderResolver clr)
{
for (AbstractMemberMetaData mmd : members)
{
mmd.initialise(clr);
}
if (discriminatorMetaData != null)
{
discriminatorMetaData.initialise(clr);
}
setInitialised();
}
/**
* Accessor for metadata for the embedded members.
* @return Returns the metadata for any defined members.
*/
public final List getMemberMetaData()
{
return members;
}
public final String getOwnerMember()
{
return ownerMember;
}
public EmbeddedMetaData setOwnerMember(String ownerMember)
{
this.ownerMember = StringUtils.isWhitespace(ownerMember) ? null : ownerMember;
return this;
}
public final String getNullIndicatorColumn()
{
return nullIndicatorColumn;
}
public EmbeddedMetaData setNullIndicatorColumn(String column)
{
this.nullIndicatorColumn = StringUtils.isWhitespace(column) ? null : column;
return this;
}
public final String getNullIndicatorValue()
{
return nullIndicatorValue;
}
public EmbeddedMetaData setNullIndicatorValue(String value)
{
this.nullIndicatorValue = StringUtils.isWhitespace(value) ? null : value;
return this;
}
public final DiscriminatorMetaData getDiscriminatorMetaData()
{
return discriminatorMetaData;
}
public EmbeddedMetaData setDiscriminatorMetaData(DiscriminatorMetaData dismd)
{
this.discriminatorMetaData = dismd;
this.discriminatorMetaData.parent = this;
return this;
}
/**
* Method to create a new discriminator metadata, assign it to this inheritance, and return it.
* @return The discriminator metadata
*/
public DiscriminatorMetaData newDiscriminatorMetaData()
{
DiscriminatorMetaData dismd = new DiscriminatorMetaData();
setDiscriminatorMetaData(dismd);
return dismd;
}
/**
* Method to add a member to the embedded definition.
* Rejects the addition of duplicate named members, or when we have already initialised this embedded definition.
* @param mmd Meta-Data for the member
*/
public void addMember(AbstractMemberMetaData mmd)
{
if (mmd == null)
{
return;
}
if (isInitialised())
{
throw new InvalidMemberMetaDataException("044108", mmd.getClassName(), mmd.getName());
}
// Check for existence of this member (name) already
for (AbstractMemberMetaData md : members)
{
if (mmd.getName().equals(md.getName()))
{
throw new InvalidMemberMetaDataException("044112", mmd.getClassName(), mmd.getName());
}
}
members.add(mmd);
mmd.parent = this;
}
/**
* Method to create a new FieldMetaData, add it, and return it.
* @param name Name of the field
* @return The FieldMetaData
*/
public FieldMetaData newFieldMetaData(String name)
{
FieldMetaData fmd = new FieldMetaData(this, name);
addMember(fmd);
return fmd;
}
/**
* Method to create a new PropertyMetaData, add it, and return it.
* @param name Name of the property
* @return The PropertyMetaData
*/
public PropertyMetaData newPropertyMetaData(String name)
{
PropertyMetaData pmd = new PropertyMetaData(this, name);
addMember(pmd);
return pmd;
}
public String toString()
{
StringBuilder str = new StringBuilder(super.toString());
str.append(" [" + members.size() + " members] (");
Iterator memberIter = members.iterator();
while (memberIter.hasNext())
{
AbstractMemberMetaData mmd = memberIter.next();
str.append(mmd.getName());
if (memberIter.hasNext())
{
str.append(",");
}
}
str.append(")");
return str.toString();
}
}