org.datanucleus.store.schema.table.CompleteClassTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datanucleus-core Show documentation
Show all versions of datanucleus-core Show documentation
DataNucleus Core provides the primary components of a heterogenous Java persistence solution.
It supports persistence API's being layered on top of the core functionality.
/**********************************************************************
Copyright (c) 2012 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.store.schema.table;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.PropertyNames;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.DiscriminatorMetaData;
import org.datanucleus.metadata.EmbeddedMetaData;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.JdbcType;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.metadata.MetaDataUtils;
import org.datanucleus.metadata.RelationType;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.store.StoreManager;
import org.datanucleus.store.schema.naming.ColumnType;
import org.datanucleus.store.schema.naming.NamingFactory;
import org.datanucleus.store.types.TypeManager;
import org.datanucleus.store.types.converters.MultiColumnConverter;
import org.datanucleus.store.types.converters.TypeConverter;
import org.datanucleus.util.NucleusLogger;
/**
* Representation of a table for a class where the class is stored in "complete-table" inheritance (or in JPA "TablePerClass")
* whereby all members (in this class and superclasses) are handled in this table. Also assumes that any persistable fields
* and collection/map fields are stored in this table (i.e not usable where you have foreign keys in the datastore).
* Allows for each member to have potentially multiple columns (using MemberColumnMapping).
* Each column generated will have its position set (origin = 0) and respects "ColumnMetaData.position".
*/
public class CompleteClassTable implements Table
{
StoreManager storeMgr;
AbstractClassMetaData cmd;
String catalogName;
String schemaName;
String identifier;
List columns = null;
Column versionColumn;
Column discriminatorColumn;
Column datastoreIdColumn;
Column multitenancyColumn;
/** Map of member-column mapping, keyed by the metadata for the member. */
Map mappingByMember = new HashMap();
/** Map of member-column mapping, keyed by the navigated path of embedded members. */
Map mappingByEmbeddedMember = new HashMap();
/** Map of DatastoreColumn, keyed by the column identifier. */
Map columnByName = new HashMap();
SchemaVerifier schemaVerifier;
public CompleteClassTable(StoreManager storeMgr, AbstractClassMetaData cmd, SchemaVerifier verifier)
{
this.storeMgr = storeMgr;
this.cmd = cmd;
this.schemaVerifier = verifier;
if (cmd.getSchema() != null)
{
schemaName = cmd.getSchema();
}
else
{
schemaName = storeMgr.getStringProperty(PropertyNames.PROPERTY_MAPPING_SCHEMA);
}
if (cmd.getCatalog() != null)
{
catalogName = cmd.getCatalog();
}
else
{
catalogName = storeMgr.getStringProperty(PropertyNames.PROPERTY_MAPPING_CATALOG);
}
this.identifier = storeMgr.getNamingFactory().getTableName(cmd);
columns = new ArrayList();
TypeManager typeMgr = storeMgr.getNucleusContext().getTypeManager();
ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
int numMembers = cmd.getAllMemberPositions().length;
for (int i=0;i embMmds = new ArrayList();
embMmds.add(mmd);
if (RelationType.isRelationSingleValued(relationType))
{
// Embedded PC field
boolean nested = false;
if (storeMgr.getSupportedOptions().contains(StoreManager.OPTION_ORM_EMBEDDED_PC_NESTED))
{
nested = !storeMgr.getNucleusContext().getConfiguration().getBooleanProperty(PropertyNames.PROPERTY_METADATA_EMBEDDED_PC_FLAT);
String nestedStr = mmd.getValueForExtension("nested");
if (nestedStr != null && nestedStr.equalsIgnoreCase("" + !nested))
{
nested = !nested;
}
}
if (nested)
{
// Embedded object stored as nested under this in the owner table (where the datastore supports that)
// Create column for the embedded owner field (that holds the nested embedded object), typically for the column name only
ColumnMetaData[] colmds = mmd.getColumnMetaData();
String colName = storeMgr.getNamingFactory().getColumnName(mmd, ColumnType.COLUMN, 0);
ColumnImpl col = addColumn(mmd, colName, null);
if (colmds != null && colmds.length == 1)
{
col.setColumnMetaData(colmds[0]);
if (colmds[0].getPosition() != null)
{
col.setPosition(colmds[0].getPosition());
}
if (colmds[0].getJdbcType() != null)
{
col.setJdbcType(colmds[0].getJdbcType());
}
}
MemberColumnMapping mapping = new MemberColumnMappingImpl(mmd, col);
col.setMemberColumnMapping(mapping);
if (schemaVerifier != null)
{
schemaVerifier.attributeMember(mapping, mmd);
}
mappingByMember.put(mmd, mapping);
// TODO Consider adding the embedded info under the above column as related information
processEmbeddedMember(embMmds, clr, mmd.getEmbeddedMetaData());
}
else
{
// Embedded object stored flat into this table, with columns at same level as owner columns
processEmbeddedMember(embMmds, clr, mmd.getEmbeddedMetaData());
}
}
else if (RelationType.isRelationMultiValued(relationType))
{
if (mmd.hasCollection())
{
if (storeMgr.getSupportedOptions().contains(StoreManager.OPTION_ORM_EMBEDDED_COLLECTION_NESTED))
{
// Embedded collection element (nested into owner table where supported)
// Add column for the collection (since the store needs a name to reference it by)
ColumnMetaData[] colmds = mmd.getColumnMetaData();
String colName = storeMgr.getNamingFactory().getColumnName(mmd, ColumnType.COLUMN, 0);
ColumnImpl col = addColumn(mmd, colName, null);
if (colmds != null && colmds.length == 1)
{
col.setColumnMetaData(colmds[0]);
if (colmds[0].getPosition() != null)
{
col.setPosition(colmds[0].getPosition());
}
if (colmds[0].getJdbcType() != null)
{
col.setJdbcType(colmds[0].getJdbcType());
}
}
MemberColumnMapping mapping = new MemberColumnMappingImpl(mmd, col);
col.setMemberColumnMapping(mapping);
if (schemaVerifier != null)
{
schemaVerifier.attributeMember(mapping, mmd);
}
mappingByMember.put(mmd, mapping);
// TODO Consider adding the embedded info under the above column as related information
EmbeddedMetaData embmd = (mmd.getElementMetaData() != null ? mmd.getElementMetaData().getEmbeddedMetaData() : null);
processEmbeddedMember(embMmds, clr, embmd);
}
else
{
NucleusLogger.DATASTORE_SCHEMA.warn("Member " + mmd.getFullFieldName() + " is an embedded collection. Not yet supported. Ignoring");
continue;
}
}
else if (mmd.hasMap())
{
if (storeMgr.getSupportedOptions().contains(StoreManager.OPTION_ORM_EMBEDDED_MAP_NESTED))
{
// TODO Support nested embedded map key/value
// Add column for the map (since the store needs a name to reference it by)
ColumnMetaData[] colmds = mmd.getColumnMetaData();
String colName = storeMgr.getNamingFactory().getColumnName(mmd, ColumnType.COLUMN, 0);
ColumnImpl col = addColumn(mmd, colName, null);
if (colmds != null && colmds.length == 1)
{
col.setColumnMetaData(colmds[0]);
if (colmds[0].getPosition() != null)
{
col.setPosition(colmds[0].getPosition());
}
if (colmds[0].getJdbcType() != null)
{
col.setJdbcType(colmds[0].getJdbcType());
}
}
MemberColumnMapping mapping = new MemberColumnMappingImpl(mmd, col);
col.setMemberColumnMapping(mapping);
if (schemaVerifier != null)
{
schemaVerifier.attributeMember(mapping, mmd);
}
mappingByMember.put(mmd, mapping);
}
NucleusLogger.DATASTORE_SCHEMA.warn("Member " + mmd.getFullFieldName() + " is an embedded collection. Not yet supported. Ignoring");
continue;
}
else if (mmd.hasArray())
{
if (storeMgr.getSupportedOptions().contains(StoreManager.OPTION_ORM_EMBEDDED_ARRAY_NESTED))
{
// TODO Support nested embedded array element
// Add column for the array (since the store needs a name to reference it by)
ColumnMetaData[] colmds = mmd.getColumnMetaData();
String colName = storeMgr.getNamingFactory().getColumnName(mmd, ColumnType.COLUMN, 0);
ColumnImpl col = addColumn(mmd, colName, null);
if (colmds != null && colmds.length == 1)
{
col.setColumnMetaData(colmds[0]);
if (colmds[0].getPosition() != null)
{
col.setPosition(colmds[0].getPosition());
}
if (colmds[0].getJdbcType() != null)
{
col.setJdbcType(colmds[0].getJdbcType());
}
}
MemberColumnMapping mapping = new MemberColumnMappingImpl(mmd, col);
col.setMemberColumnMapping(mapping);
if (schemaVerifier != null)
{
schemaVerifier.attributeMember(mapping, mmd);
}
mappingByMember.put(mmd, mapping);
}
NucleusLogger.DATASTORE_SCHEMA.warn("Member " + mmd.getFullFieldName() + " is an embedded array. Not yet supported. Ignoring");
continue;
}
}
}
else
{
ColumnMetaData[] colmds = mmd.getColumnMetaData();
if (relationType != RelationType.NONE)
{
// 1-1/N-1 stored as single column with persistable-id
// 1-N/M-N stored as single column with collection
String colName = storeMgr.getNamingFactory().getColumnName(mmd, ColumnType.COLUMN, 0);
ColumnImpl col = addColumn(mmd, colName, null);
if (colmds != null && colmds.length == 1)
{
col.setColumnMetaData(colmds[0]);
if (colmds[0].getPosition() != null)
{
col.setPosition(colmds[0].getPosition());
}
if (colmds[0].getJdbcType() != null)
{
col.setJdbcType(colmds[0].getJdbcType());
}
}
MemberColumnMapping mapping = new MemberColumnMappingImpl(mmd, col);
col.setMemberColumnMapping(mapping);
if (schemaVerifier != null)
{
schemaVerifier.attributeMember(mapping, mmd);
}
mappingByMember.put(mmd, mapping);
}
else
{
TypeConverter typeConv = getTypeConverterForMember(mmd, colmds, typeMgr);
if (typeConv != null)
{
// Create column(s) for this TypeConverter
if (typeConv instanceof MultiColumnConverter)
{
Class[] colJavaTypes = ((MultiColumnConverter)typeConv).getDatastoreColumnTypes();
Column[] cols = new Column[colJavaTypes.length];
for (int j=0;j unorderedCols = new ArrayList();
Column[] cols = new Column[columns.size()];
for (Column col : columns)
{
if (col.getPosition() >= columns.size())
{
NucleusLogger.DATASTORE_SCHEMA.warn("Column with name " + col.getName() + " is specified with position=" + col.getPosition() + " which is invalid." +
" This table has " + columns.size() + " columns");
unorderedCols.add(col);
}
else if (col.getPosition() >= 0)
{
if (cols[col.getPosition()] != null)
{
NucleusLogger.DATASTORE_SCHEMA.warn("Column with name " + col.getName() + " defined for position=" + col.getPosition() + " yet there is also " +
cols[col.getPosition()].getName() + " at that position! Ignoring");
unorderedCols.add(col);
}
else
{
cols[col.getPosition()] = col;
}
}
else
{
unorderedCols.add(col);
}
}
if (!unorderedCols.isEmpty())
{
for (int i=0;i();
for (Column col : cols)
{
MemberColumnMapping mapping = col.getMemberColumnMapping();
if (mapping != null)
{
if (!mapping.getMemberMetaData().isInsertable() && !mapping.getMemberMetaData().isUpdateable())
{
// Ignored
NucleusLogger.DATASTORE_SCHEMA.debug("Not adding column " + col.getName() + " for member=" + mapping.getMemberMetaData().getFullFieldName() +
" since is not insertable/updateable");
}
else
{
if (columnByName.containsKey(col.getName()))
{
NucleusLogger.DATASTORE_SCHEMA.error("Unable to add column with name=" + col.getName() + " to table=" + getName() + " for class=" + cmd.getFullClassName() +
" since one with same name already exists.");
throw new NucleusUserException("Unable to add column with name=" + col.getName() + " to table=" + getName() + " for class=" + cmd.getFullClassName() +
" since one with same name already exists.");
}
columns.add(col);
columnByName.put(col.getName(), col);
}
}
else
{
columns.add(col);
columnByName.put(col.getName(), col);
}
}
}
protected TypeConverter getTypeConverterForMember(AbstractMemberMetaData mmd, ColumnMetaData[] colmds, TypeManager typeMgr)
{
TypeConverter typeConv = null;
String typeConvName = mmd.getTypeConverterName();
if (typeConvName != null)
{
// User has specified the TypeConverter
typeConv = typeMgr.getTypeConverterForName(typeConvName);
}
else
{
// No explicit TypeConverter so maybe there is an auto-apply converter for this member type
typeConv = typeMgr.getAutoApplyTypeConverterForType(mmd.getType());
}
if (typeConv == null)
{
// Try to find a TypeConverter matching any column JDBC type definition
if (colmds != null && colmds.length > 1)
{
// Multiple columns, so try to find a converter with the right number of columns (note we could, in future, check the types of columns also)
Collection converters = typeMgr.getTypeConvertersForType(mmd.getType());
if (converters != null && !converters.isEmpty())
{
for (TypeConverter conv : converters)
{
if (conv instanceof MultiColumnConverter && ((MultiColumnConverter)conv).getDatastoreColumnTypes().length == colmds.length)
{
typeConv = conv;
break;
}
}
}
if (typeConv == null)
{
// TODO Throw exception since user column specification leaves no possible converter
}
}
else
{
// Single column, so try to match the JDBC type if provided
JdbcType jdbcType = (colmds != null && colmds.length > 0 ? colmds[0].getJdbcType() : null);
if (jdbcType != null)
{
// JDBC type specified so don't just take the default
if (MetaDataUtils.isJdbcTypeString(jdbcType))
{
typeConv = typeMgr.getTypeConverterForType(mmd.getType(), String.class);
}
else if (MetaDataUtils.isJdbcTypeNumeric(jdbcType))
{
typeConv = typeMgr.getTypeConverterForType(mmd.getType(), Long.class);
}
else if (jdbcType == JdbcType.TIMESTAMP)
{
typeConv = typeMgr.getTypeConverterForType(mmd.getType(), Timestamp.class);
}
else if (jdbcType == JdbcType.TIME)
{
typeConv = typeMgr.getTypeConverterForType(mmd.getType(), Time.class);
}
else if (jdbcType == JdbcType.DATE)
{
typeConv = typeMgr.getTypeConverterForType(mmd.getType(), Date.class);
}
// TODO Support other JDBC types
}
else
{
// Fallback to default type converter for this member type (if any)
typeConv = typeMgr.getDefaultTypeConverterForType(mmd.getType());
}
}
}
if (schemaVerifier != null)
{
// Make sure that the schema verifier supports this conversion
typeConv = schemaVerifier.verifyTypeConverterForMember(mmd, typeConv);
}
return typeConv;
}
protected void processEmbeddedMember(List mmds, ClassLoaderResolver clr, EmbeddedMetaData embmd)
{
TypeManager typeMgr = storeMgr.getNucleusContext().getTypeManager();
MetaDataManager mmgr = storeMgr.getMetaDataManager();
NamingFactory namingFactory = storeMgr.getNamingFactory();
AbstractMemberMetaData lastMmd = mmds.get(mmds.size()-1);
AbstractClassMetaData embCmd = null;
if (lastMmd.hasCollection())
{
// Embedded collection element
embCmd = mmgr.getMetaDataForClass(lastMmd.getCollection().getElementType(), clr);
}
else if (lastMmd.hasArray())
{
// Embedded array element
embCmd = mmgr.getMetaDataForClass(lastMmd.getArray().getElementType(), clr);
}
else
{
// Embedded 1-1
embCmd = mmgr.getMetaDataForClass(lastMmd.getType(), clr);
}
// Go through all members of the embedded class
int[] memberPositions = embCmd.getAllMemberPositions();
for (int i=0;i embMmds = new ArrayList(mmds);
embMmds.add(mmd);
if (nested)
{
// Embedded object stored as nested under this in the owner table (where the datastore supports that)
// Add column for the owner of the embedded object, typically for the column name only
ColumnMetaData[] colmds = mmd.getColumnMetaData();
String colName = namingFactory.getColumnName(embMmds, 0);
ColumnImpl col = addEmbeddedColumn(colName, null);
if (embmdMmd != null && embmdMmd.getColumnMetaData() != null && embmdMmd.getColumnMetaData().length == 1 && embmdMmd.getColumnMetaData()[0].getPosition() != null)
{
col.setPosition(embmdMmd.getColumnMetaData()[0].getPosition());
}
else if (colmds != null && colmds.length == 1 && colmds[0].getPosition() != null)
{
col.setPosition(colmds[0].getPosition());
}
if (embmdMmd != null && embmdMmd.getColumnMetaData() != null && embmdMmd.getColumnMetaData().length == 1 && embmdMmd.getColumnMetaData()[0].getJdbcType() != null)
{
col.setJdbcType(embmdMmd.getColumnMetaData()[0].getJdbcType());
}
else if (colmds != null && colmds.length == 1 && colmds[0].getJdbcType() != null)
{
col.setJdbcType(colmds[0].getJdbcType());
}
MemberColumnMapping mapping = new MemberColumnMappingImpl(mmd, col);
col.setMemberColumnMapping(mapping);
if (schemaVerifier != null)
{
schemaVerifier.attributeEmbeddedMember(mapping, embMmds);
}
mappingByEmbeddedMember.put(getEmbeddedMemberNavigatedPath(embMmds), mapping);
// TODO Create mapping for the related info under the above column
processEmbeddedMember(embMmds, clr, (embmdMmd != null ? embmdMmd.getEmbeddedMetaData() : null));
}
else
{
// Embedded object stored flat into this table, with columns at same level as owner columns
processEmbeddedMember(embMmds, clr, (embmdMmd != null ? embmdMmd.getEmbeddedMetaData() : null));
}
}
else
{
if (mmd.hasCollection())
{
if (storeMgr.getSupportedOptions().contains(StoreManager.OPTION_ORM_EMBEDDED_COLLECTION_NESTED))
{
// TODO Support nested embedded collection element
}
NucleusLogger.DATASTORE_SCHEMA.warn("Member " + mmd.getFullFieldName() + " is an embedded collection. Not yet supported. Ignoring");
continue;
}
else if (mmd.hasMap())
{
if (storeMgr.getSupportedOptions().contains(StoreManager.OPTION_ORM_EMBEDDED_MAP_NESTED))
{
// TODO Support nested embedded map key/value
}
NucleusLogger.DATASTORE_SCHEMA.warn("Member " + mmd.getFullFieldName() + " is an embedded collection. Not yet supported. Ignoring");
continue;
}
else if (mmd.hasArray())
{
if (storeMgr.getSupportedOptions().contains(StoreManager.OPTION_ORM_EMBEDDED_ARRAY_NESTED))
{
// TODO Support nested embedded array element
}
NucleusLogger.DATASTORE_SCHEMA.warn("Member " + mmd.getFullFieldName() + " is an embedded array. Not yet supported. Ignoring");
continue;
}
}
}
else
{
List embMmds = new ArrayList(mmds);
embMmds.add(mmd);
ColumnMetaData[] colmds = mmd.getColumnMetaData();
if (relationType != RelationType.NONE)
{
// 1-1/N-1 stored as single column with persistable-id
// 1-N/M-N stored as single column with collection
// Create column for basic type
String colName = namingFactory.getColumnName(embMmds, 0);
ColumnImpl col = addEmbeddedColumn(colName, null);
if (embmdMmd != null && embmdMmd.getColumnMetaData() != null && embmdMmd.getColumnMetaData().length == 1 && embmdMmd.getColumnMetaData()[0].getPosition() != null)
{
col.setPosition(embmdMmd.getColumnMetaData()[0].getPosition());
}
else if (colmds != null && colmds.length == 1 && colmds[0].getPosition() != null)
{
col.setPosition(colmds[0].getPosition());
}
if (embmdMmd != null && embmdMmd.getColumnMetaData() != null && embmdMmd.getColumnMetaData().length == 1 && embmdMmd.getColumnMetaData()[0].getJdbcType() != null)
{
col.setJdbcType(embmdMmd.getColumnMetaData()[0].getJdbcType());
}
else if (colmds != null && colmds.length == 1 && colmds[0].getJdbcType() != null)
{
col.setJdbcType(colmds[0].getJdbcType());
}
MemberColumnMapping mapping = new MemberColumnMappingImpl(mmd, col);
col.setMemberColumnMapping(mapping);
if (schemaVerifier != null)
{
schemaVerifier.attributeEmbeddedMember(mapping, embMmds);
}
mappingByEmbeddedMember.put(getEmbeddedMemberNavigatedPath(embMmds), mapping);
}
else
{
TypeConverter typeConv = getTypeConverterForMember(mmd, colmds, typeMgr); // TODO Pass in embedded colmds if they have jdbcType info?
if (typeConv != null)
{
// Create column(s) for this TypeConverter
if (typeConv instanceof MultiColumnConverter)
{
Class[] colJavaTypes = ((MultiColumnConverter)typeConv).getDatastoreColumnTypes();
Column[] cols = new Column[colJavaTypes.length];
for (int j=0;j mmds)
{
Iterator mmdIter = mmds.iterator();
StringBuilder strBldr = new StringBuilder(mmdIter.next().getFullFieldName());
while (mmdIter.hasNext())
{
strBldr.append('.').append(mmdIter.next().getName());
}
return strBldr.toString();
}
public AbstractClassMetaData getClassMetaData()
{
return cmd;
}
public StoreManager getStoreManager()
{
return storeMgr;
}
public String getSchemaName()
{
return schemaName;
}
public String getCatalogName()
{
return catalogName;
}
public String getName()
{
return identifier;
}
public int getNumberOfColumns()
{
return columns.size();
}
public List getColumns()
{
return columns;
}
public Column getColumnForPosition(int pos)
{
if (pos < 0 || pos >= columns.size())
{
throw new ArrayIndexOutOfBoundsException("There are " + columns.size() + " columns, so specify a value between 0 and " + (columns.size()-1));
}
return columns.get(pos);
}
public Column getDatastoreIdColumn()
{
return datastoreIdColumn;
}
public Column getVersionColumn()
{
return versionColumn;
}
public Column getDiscriminatorColumn()
{
return discriminatorColumn;
}
public Column getMultitenancyColumn()
{
return multitenancyColumn;
}
public Column getColumnForName(String name)
{
Column col = columnByName.get(name);
if (col != null)
{
return col;
}
if (!name.startsWith("\""))
{
col = columnByName.get("\"" + name + "\"");
}
return col;
}
public MemberColumnMapping getMemberColumnMappingForMember(AbstractMemberMetaData mmd)
{
return mappingByMember.get(mmd);
}
public MemberColumnMapping getMemberColumnMappingForEmbeddedMember(List mmds)
{
return mappingByEmbeddedMember.get(getEmbeddedMemberNavigatedPath(mmds));
}
public Set getMemberColumnMappings()
{
Set mappings = new HashSet(mappingByMember.values());
mappings.addAll(mappingByEmbeddedMember.values());
return mappings;
}
public String toString()
{
return "Table: " + identifier;
}
}