org.datanucleus.store.rdbms.table.ClassTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datanucleus-rdbms Show documentation
Show all versions of datanucleus-rdbms Show documentation
Plugin for DataNucleus providing persistence to RDBMS datastores.
The newest version!
/**********************************************************************
Copyright (c) 2002 Kelly Grizzle (TJDO) 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:
2003 Andy Jefferson - added localiser
2003 Andy Jefferson - replaced TableMetadata with identifier.
2003 Erik Bengtson - refactored the datastore identity together with the SQLIdentifier class
2003 Erik Bengtson - added OptimisticMapping
2003 Andy Jefferson - coding standards
2004 Andy Jefferson - merged with JDOBaseTable
2004 Andy Jefferson - changed to store map of fieldMappings keyed by absolute field num. Added start point for inheritance strategy handling
2004 Andy Jefferson - changed consumer to set highest field number based on actual highest (to allow for inheritance strategies)
2004 Andy Jefferson - added DiscriminatorMapping
2004 Andy Jefferson - enabled use of Long/String datastore identity column
2004 Andy Jefferson - added capability to handle 1-N inverse unidirectional FKs
2004 Andy Jefferson - removed the majority of the value-strategy code - done elsewhere
...
**********************************************************************/
package org.datanucleus.store.rdbms.table;
import java.sql.Connection;
import java.sql.SQLException;
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.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ClassMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.ColumnMetaDataContainer;
import org.datanucleus.metadata.DiscriminatorMetaData;
import org.datanucleus.metadata.ElementMetaData;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.metadata.FieldRole;
import org.datanucleus.metadata.ForeignKeyAction;
import org.datanucleus.metadata.ForeignKeyMetaData;
import org.datanucleus.metadata.ValueGenerationStrategy;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.IndexMetaData;
import org.datanucleus.metadata.InheritanceStrategy;
import org.datanucleus.metadata.InterfaceMetaData;
import org.datanucleus.metadata.JdbcType;
import org.datanucleus.metadata.JoinMetaData;
import org.datanucleus.metadata.MetaData;
import org.datanucleus.metadata.MultitenancyMetaData;
import org.datanucleus.metadata.OrderMetaData;
import org.datanucleus.metadata.PrimaryKeyMetaData;
import org.datanucleus.metadata.PropertyMetaData;
import org.datanucleus.metadata.RelationType;
import org.datanucleus.metadata.SoftDeleteMetaData;
import org.datanucleus.metadata.UniqueMetaData;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.metadata.VersionStrategy;
import org.datanucleus.store.rdbms.adapter.DatastoreAdapter;
import org.datanucleus.store.rdbms.exceptions.ClassDefinitionException;
import org.datanucleus.store.rdbms.exceptions.DuplicateColumnException;
import org.datanucleus.store.rdbms.exceptions.NoSuchPersistentFieldException;
import org.datanucleus.store.rdbms.exceptions.NoTableManagedException;
import org.datanucleus.store.rdbms.RDBMSPropertyNames;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.identifier.DatastoreIdentifier;
import org.datanucleus.store.rdbms.identifier.IdentifierFactory;
import org.datanucleus.store.rdbms.identifier.IdentifierType;
import org.datanucleus.store.rdbms.key.CandidateKey;
import org.datanucleus.store.rdbms.key.ForeignKey;
import org.datanucleus.store.rdbms.key.Index;
import org.datanucleus.store.rdbms.key.PrimaryKey;
import org.datanucleus.store.rdbms.mapping.CorrespondentColumnsMapper;
import org.datanucleus.store.rdbms.mapping.MappingConsumer;
import org.datanucleus.store.rdbms.mapping.MappingType;
import org.datanucleus.store.rdbms.mapping.column.ColumnMapping;
import org.datanucleus.store.rdbms.mapping.java.BooleanMapping;
import org.datanucleus.store.rdbms.mapping.java.DiscriminatorMapping;
import org.datanucleus.store.rdbms.mapping.java.EmbeddedPCMapping;
import org.datanucleus.store.rdbms.mapping.java.OrderIndexMapping;
import org.datanucleus.store.rdbms.mapping.java.IntegerMapping;
import org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping;
import org.datanucleus.store.rdbms.mapping.java.LongMapping;
import org.datanucleus.store.rdbms.mapping.java.PersistableMapping;
import org.datanucleus.store.rdbms.mapping.java.ReferenceMapping;
import org.datanucleus.store.rdbms.mapping.java.SerialisedMapping;
import org.datanucleus.store.rdbms.mapping.java.SqlTimestampMapping;
import org.datanucleus.store.rdbms.mapping.java.StringMapping;
import org.datanucleus.store.rdbms.mapping.java.VersionMapping;
import org.datanucleus.store.rdbms.schema.SQLTypeInfo;
import org.datanucleus.store.schema.table.SurrogateColumnType;
import org.datanucleus.store.types.SCOUtils;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.MacroString;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;
/**
* Table representing a Java class (or classes) as a first class object (FCO).
* Uses the inheritance strategy to control whether this represents multiple classes or just the one class.
* Mappings
* This class adds some additional mappings over what the superclass provides. Here we add
*
* - externalFkMappings - any mappings for Collections that have no associated field in this class, providing the foreign key column(s)
* - externalOrderMappings - mappings for any ordering column used by Lists for ordering elements of this class
* - externalFkDiscriminatorMappings - mappings for any discriminator column used when sharing external foreign keys to distinguish the element owner field
*
* Classes
* A table can represent multiple classes. It has a nominal owner which is the class that has an inheritance strategy of "new-table".
* All classes that utilise this table have their MetaData stored in this object.
* Secondary Tables
* This class represents a "primary" table. That is, the main table where objects of a class are persisted.
* It can have several "secondary" tables where some of the classes fields are stored at persistence.
*/
public class ClassTable extends AbstractClassTable implements DatastoreClass
{
/**
* MetaData for the principal class being stored here.
* In inheritance situations where multiple classes share the same table, this will be the class which uses "new-table" strategy.
*/
private final ClassMetaData cmd;
/** MetaData for all classes being managed here. */
private final Collection managedClassMetaData = new HashSet<>();
/** Callbacks that have been applied keyed by the managed class. */
private final Map> callbacksAppliedForManagedClass = new HashMap<>();
/** Table above this table storing superclass information (if any). */
private ClassTable supertable;
/** Secondary tables for this table (if any). */
private Map secondaryTables;
/**
* Mappings for FK Collections/Lists not managed by this class (1-N unidirectional).
* Keyed by the metadata of the owner field/property.
*/
private Map externalFkMappings;
/**
* Mappings for FK Collections/Lists relation discriminators not managed by this class (1-N unidirectional).
* Keyed by the metadata of the owner field/property.
*/
private Map externalFkDiscriminatorMappings;
/**
* Mappings for FK Lists order columns not managed by this class (1-N unidirectional).
* Keyed by the metadata of the owner field/property.
*/
private Map externalOrderMappings;
/** User defined table schema **/
private MacroString tableDef;
/** DDL statement for creating the table, if using user defined table schema. */
private String createStatementDDL;
Map candidateKeysByMapField = new HashMap<>();
/** Set of unmapped "Column" objects that have no associated field (and hence ColumnMapping). */
Set unmappedColumns = null;
/**
* Constructor.
* @param tableName Table name SQL identifier
* @param storeMgr Store Manager to manage this table
* @param cmd MetaData for the class.
*/
public ClassTable(DatastoreIdentifier tableName, RDBMSStoreManager storeMgr, ClassMetaData cmd)
{
super(tableName, storeMgr);
this.cmd = cmd;
// Check if this is a valid class to map to its own table
if (cmd.getInheritanceMetaData().getStrategy() != InheritanceStrategy.NEW_TABLE &&
cmd.getInheritanceMetaData().getStrategy() != InheritanceStrategy.COMPLETE_TABLE)
{
throw new NucleusUserException(Localiser.msg("057003", cmd.getFullClassName(), cmd.getInheritanceMetaData().getStrategy().toString())).setFatal();
}
highestMemberNumber = cmd.getNoOfManagedMembers() + cmd.getNoOfInheritedManagedMembers();
// Extract the table definition from MetaData, if exists
String tableImpStr = cmd.getValueForExtension("ddl-imports");
String tableDefStr = null;
if (dba.getVendorID() != null)
{
tableDefStr = cmd.getValueForExtension("ddl-definition" + '-' + dba.getVendorID());
}
if (tableDefStr == null)
{
tableDefStr = cmd.getValueForExtension("ddl-definition");
}
if (tableDefStr != null)
{
tableDef = new MacroString(cmd.getFullClassName(), tableImpStr, tableDefStr);
}
}
/**
* Pre-initialize.
* We require any supertable, and the PK to be ready before we start initialisation.
* @param clr the ClassLoaderResolver
*/
@Override
public void preInitialize(final ClassLoaderResolver clr)
{
assertIsPKUninitialized();
if (cmd.getInheritanceMetaData().getStrategy() != InheritanceStrategy.COMPLETE_TABLE)
{
// Inheritance strategy may imply having a supertable, so identify it
supertable = getSupertable(cmd, clr);
if (supertable != null && !supertable.isInitialized() && !supertable.isPKInitialized())
{
// Make sure that the supertable is preinitialised before we think about initialising here
supertable.preInitialize(clr);
}
}
// Initialise the PK field(s)
if (!isPKInitialized())
{
initializePK(clr);
}
}
/**
* Method to initialise the table.
* This adds the columns based on the MetaData representation for the class being represented by this table.
* @param clr The ClassLoaderResolver
*/
@Override
public void initialize(ClassLoaderResolver clr)
{
// if already initialized, we have nothing further to do here
if (isInitialized())
{
return;
}
// initialize any supertable first - this will ensure that any resources
// we may inherit from that table are initialized at the point at which we may need them
if (supertable != null)
{
supertable.initialize(clr);
}
// Add the fields for this class (and any other superclasses that we need to manage the
// fields for (inheritance-strategy="subclass-table" in the superclass)
initializeForClass(cmd, clr);
// Add Version where specified in MetaData
// TODO If there is a superclass table that has a version we should omit from here even if in MetaData. See "getTableWithDiscriminator()" for the logic
versionMetaData = cmd.getVersionMetaDataForTable();
if (versionMetaData != null && versionMetaData.getMemberName() == null)
{
if (versionMetaData.getStrategy() == VersionStrategy.NONE)
{
// Equate NONE to an integral column (TODO Allow for using timestamp when not checking the version?)
// See also LockManagerImpl
versionMapping = new VersionMapping.VersionLongMapping(this, storeMgr.getMappingManager().getMapping(Long.class));
}
else if (versionMetaData.getStrategy() == VersionStrategy.VERSION_NUMBER)
{
versionMapping = new VersionMapping.VersionLongMapping(this, storeMgr.getMappingManager().getMapping(Long.class));
}
else if (versionMetaData.getStrategy() == VersionStrategy.DATE_TIME)
{
if (!dba.supportsOption(DatastoreAdapter.DATETIME_STORES_MILLISECS))
{
// TODO Localise this
throw new NucleusException("Class " + cmd.getFullClassName() + " is defined " +
"to use date-time versioning, yet this datastore doesnt support storing " +
"milliseconds in DATETIME/TIMESTAMP columns. Use version-number");
}
versionMapping = new VersionMapping.VersionTimestampMapping(this, storeMgr.getMappingManager().getMapping(Timestamp.class));
}
else if (versionMetaData.getStrategy() == VersionStrategy.STATE_IMAGE)
{
// TODO Support state-image strategy
throw new NucleusUserException("DataNucleus doesnt currently support version strategy \"state-image\"");
}
if (versionMapping != null)
{
logMapping("VERSION", versionMapping);
}
}
DiscriminatorMetaData dismd = cmd.getDiscriminatorMetaDataForTable();
if (dismd != null)
{
// Surrogate discriminator
discriminatorMetaData = dismd;
if (storeMgr.getBooleanProperty(RDBMSPropertyNames.PROPERTY_RDBMS_DISCRIM_PER_SUBCLASS_TABLE))
{
// Backwards compatibility only. Creates discriminator in all subclass tables even though not needed
// TODO Remove this in the future
discriminatorMapping = DiscriminatorMapping.createDiscriminatorMapping(this, dismd);
}
else
{
// Create discriminator column only in top most table that needs it
ClassTable tableWithDiscrim = getTableWithDiscriminator();
if (tableWithDiscrim == this)
{
// No superclass with a discriminator so add it in this table
discriminatorMapping = DiscriminatorMapping.createDiscriminatorMapping(this, dismd);
}
}
if (discriminatorMapping != null)
{
logMapping("DISCRIMINATOR", discriminatorMapping);
}
}
// TODO Only put on root table (i.e "if (supertable != null)" then omit)
MultitenancyMetaData mtmd = cmd.getMultitenancyMetaData();
if (mtmd != null)
{
// Surrogate multi-tenancy discriminator
ColumnMetaData colmd = mtmd.getColumnMetaData();
if (colmd == null)
{
colmd = new ColumnMetaData();
if (mtmd.getColumnName() != null)
{
colmd.setName(mtmd.getColumnName());
}
}
String colName = (colmd.getName() != null) ? colmd.getName() : "TENANT_ID";
String typeName = (colmd.getJdbcType() == JdbcType.INTEGER) ? Integer.class.getName() : String.class.getName();
multitenancyMapping = (typeName.equals(Integer.class.getName())) ? new IntegerMapping() : new StringMapping();
multitenancyMapping.setTable(this);
multitenancyMapping.initialize(storeMgr, typeName);
Column tenantColumn = addColumn(typeName, storeMgr.getIdentifierFactory().newIdentifier(IdentifierType.COLUMN, colName), multitenancyMapping, colmd);
storeMgr.getMappingManager().createColumnMapping(multitenancyMapping, tenantColumn, typeName);
logMapping("MULTITENANCY", multitenancyMapping);
}
SoftDeleteMetaData sdmd = cmd.getSoftDeleteMetaData();
if (sdmd != null)
{
// Surrogate "SoftDelete" flag column
ColumnMetaData colmd = sdmd.getColumnMetaData();
if (colmd == null)
{
colmd = new ColumnMetaData();
if (sdmd.getColumnName() != null)
{
colmd.setName(sdmd.getColumnName());
}
}
String colName = (colmd.getName() != null) ? colmd.getName() : "DELETED";
String typeName = Boolean.class.getName(); // TODO Allow integer using JDBC type
softDeleteMapping = new BooleanMapping();
softDeleteMapping.setTable(this);
softDeleteMapping.initialize(storeMgr, typeName);
Column tenantColumn = addColumn(typeName, storeMgr.getIdentifierFactory().newIdentifier(IdentifierType.COLUMN, colName), softDeleteMapping, colmd);
storeMgr.getMappingManager().createColumnMapping(softDeleteMapping, tenantColumn, typeName);
logMapping("SOFTDELETE", softDeleteMapping);
}
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_CREATEUSER))
{
// Surrogate "create user" column
ColumnMetaData colmd = new ColumnMetaData();
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_CREATEUSER_COLUMN_NAME))
{
colmd.setName(cmd.getValueForExtension(MetaData.EXTENSION_CLASS_CREATEUSER_COLUMN_NAME));
}
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_CREATEUSER_COLUMN_LENGTH))
{
colmd.setLength(cmd.getValueForExtension(MetaData.EXTENSION_CLASS_CREATEUSER_COLUMN_LENGTH));
}
String colName = (colmd.getName() != null) ? colmd.getName() : "CREATE_USER";
String typeName = String.class.getName();
createUserMapping = new StringMapping();
createUserMapping.setTable(this);
createUserMapping.initialize(storeMgr, typeName);
Column auditColumn = addColumn(typeName, storeMgr.getIdentifierFactory().newIdentifier(IdentifierType.COLUMN, colName), createUserMapping, colmd);
storeMgr.getMappingManager().createColumnMapping(createUserMapping, auditColumn, typeName);
logMapping("CREATEUSER", createUserMapping);
}
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_CREATETIMESTAMP))
{
// Surrogate "create timestamp" column
ColumnMetaData colmd = new ColumnMetaData();
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_CREATETIMESTAMP_COLUMN_NAME))
{
colmd.setName(cmd.getValueForExtension(MetaData.EXTENSION_CLASS_CREATETIMESTAMP_COLUMN_NAME));
}
String colName = (colmd.getName() != null) ? colmd.getName() : "CREATE_TIMESTAMP";
String typeName = Timestamp.class.getName();
createTimestampMapping = new SqlTimestampMapping();
createTimestampMapping.setTable(this);
createTimestampMapping.initialize(storeMgr, typeName);
Column auditColumn = addColumn(typeName, storeMgr.getIdentifierFactory().newIdentifier(IdentifierType.COLUMN, colName), createTimestampMapping, colmd);
storeMgr.getMappingManager().createColumnMapping(createTimestampMapping, auditColumn, typeName);
logMapping("CREATETIMESTAMP", createTimestampMapping);
}
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_UPDATEUSER))
{
// Surrogate "update user" column
ColumnMetaData colmd = new ColumnMetaData();
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_UPDATEUSER_COLUMN_NAME))
{
colmd.setName(cmd.getValueForExtension(MetaData.EXTENSION_CLASS_UPDATEUSER_COLUMN_NAME));
}
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_UPDATEUSER_COLUMN_LENGTH))
{
colmd.setLength(cmd.getValueForExtension(MetaData.EXTENSION_CLASS_UPDATEUSER_COLUMN_LENGTH));
}
String colName = (colmd.getName() != null) ? colmd.getName() : "UPDATE_USER";
String typeName = String.class.getName();
updateUserMapping = new StringMapping();
updateUserMapping.setTable(this);
updateUserMapping.initialize(storeMgr, typeName);
Column auditColumn = addColumn(typeName, storeMgr.getIdentifierFactory().newIdentifier(IdentifierType.COLUMN, colName), updateUserMapping, colmd);
storeMgr.getMappingManager().createColumnMapping(updateUserMapping, auditColumn, typeName);
logMapping("UPDATEUSER", updateUserMapping);
}
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_UPDATETIMESTAMP))
{
// Surrogate "update timestamp" column
ColumnMetaData colmd = new ColumnMetaData();
if (cmd.hasExtension(MetaData.EXTENSION_CLASS_UPDATETIMESTAMP_COLUMN_NAME))
{
colmd.setName(cmd.getValueForExtension(MetaData.EXTENSION_CLASS_UPDATETIMESTAMP_COLUMN_NAME));
}
String colName = (colmd.getName() != null) ? colmd.getName() : "UPDATE_TIMESTAMP";
String typeName = Timestamp.class.getName();
updateTimestampMapping = new SqlTimestampMapping();
updateTimestampMapping.setTable(this);
updateTimestampMapping.initialize(storeMgr, typeName);
Column auditColumn = addColumn(typeName, storeMgr.getIdentifierFactory().newIdentifier(IdentifierType.COLUMN, colName), updateTimestampMapping, colmd);
storeMgr.getMappingManager().createColumnMapping(updateTimestampMapping, auditColumn, typeName);
logMapping("UPDATETIMESTAMP", updateTimestampMapping);
}
// Initialise any SecondaryTables
if (secondaryTables != null)
{
Iterator> secondaryTableEntryIter = secondaryTables.entrySet().iterator();
while (secondaryTableEntryIter.hasNext())
{
Map.Entry secondaryTableEntry = secondaryTableEntryIter.next();
SecondaryTable second = secondaryTableEntry.getValue();
if (!second.isInitialized())
{
second.initialize(clr);
}
}
}
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("057023", this));
}
storeMgr.registerTableInitialized(this);
state = TABLE_STATE_INITIALIZED;
}
/**
* Post initilize. For things that must be set after all classes have been initialized before
* @param clr the ClassLoaderResolver
*/
@Override
public void postInitialize(final ClassLoaderResolver clr)
{
assertIsInitialized();
runCallBacks(clr);
if (tableDef != null)
{
createStatementDDL = tableDef.substituteMacros(new MacroString.MacroHandler()
{
public void onIdentifierMacro(MacroString.IdentifierMacro im)
{
storeMgr.resolveIdentifierMacro(im, clr);
}
public void onParameterMacro(MacroString.ParameterMacro pm)
{
throw new NucleusUserException(Localiser.msg("057033", cmd.getFullClassName(), pm));
}
}, clr
);
}
}
/** Name of class currently being processed in manageClass (if any). */
protected transient String managingClassCurrent = null;
/** Flag to run the callbacks after the current class is managed fully. */
protected boolean runCallbacksAfterManageClass = false;
/**
* Method that adds the specified class to be managed by this table.
* Will provide mapping of all persistent fields to their underlying columns, map all necessary identity fields,
* and manage all "unmapped" columns that have no associated field.
* @param theCmd ClassMetaData for the class to be managed
* @param clr The ClassLoaderResolver
*/
public void manageClass(AbstractClassMetaData theCmd, ClassLoaderResolver clr)
{
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("057024", toString(), theCmd.getFullClassName(), theCmd.getInheritanceMetaData().getStrategy().toString()));
}
managingClassCurrent = theCmd.getFullClassName();
managedClassMetaData.add(theCmd);
// Manage all fields of this class and all fields of superclasses that this is overriding
manageMembers(theCmd, clr, theCmd.getManagedMembers());
manageMembers(theCmd, clr, theCmd.getOverriddenMembers());
// Manage all "unmapped" columns (that have no field)
manageUnmappedColumns(theCmd, clr);
managingClassCurrent = null;
if (runCallbacksAfterManageClass)
{
// We need to run the callbacks now that this class is fully managed
runCallBacks(clr);
runCallbacksAfterManageClass = false;
}
}
/**
* Accessor for the names of all classes managed by this table.
* @return Names of the classes managed (stored) here
*/
public String[] getManagedClasses()
{
String[] classNames = new String[managedClassMetaData.size()];
Iterator iter = managedClassMetaData.iterator();
int i = 0;
while (iter.hasNext())
{
classNames[i++] = (iter.next()).getFullClassName();
}
return classNames;
}
/**
* Goes through all specified members for the specified class and adds a mapping for each.
* Ignores primary-key fields which are added elsewhere.
* @param theCmd ClassMetaData for the class to be managed
* @param clr The ClassLoaderResolver
* @param mmds the fields/properties to manage
*/
private void manageMembers(AbstractClassMetaData theCmd, ClassLoaderResolver clr, AbstractMemberMetaData[] mmds)
{
// Go through the fields for this class and add columns for them
for (int fieldNumber=0; fieldNumber 0)
{
// Apply this set of ColumnMetaData to the existing mapping
int colnum = 0;
IdentifierFactory idFactory = getStoreManager().getIdentifierFactory();
for (int i=0;i 0)
{
// Field is for a subclass and so column(s) has to either allow nulls, or have default
int numCols = fieldMapping.getNumberOfColumnMappings();
for (int colNum = 0;colNum < numCols; colNum++)
{
Column col = fieldMapping.getColumnMapping(colNum).getColumn();
if (col.getDefaultValue() == null && !col.isNullable())
{
// Column needs to be nullable
NucleusLogger.DATASTORE_SCHEMA.debug("Member " + mmd.getFullFieldName() +
" uses superclass-table yet the field is not marked as nullable " +
" nor does it have a default value, so setting the column as nullable");
col.setNullable(true);
}
}
}
addMemberMapping(fieldMapping);
}
else
{
// Add the field to the appropriate secondary table
if (secondaryTables == null)
{
secondaryTables = new HashMap<>();
}
SecondaryTable secTable = secondaryTables.get(mmd.getTable());
if (secTable == null)
{
// Secondary table doesnt exist yet so create it to users specifications.
List joinmds = theCmd.getJoinMetaData();
JoinMetaData theJoinMD = null;
if (joinmds != null)
{
for (JoinMetaData joinmd : joinmds)
{
if (joinmd.getTable().equalsIgnoreCase(mmd.getTable()) &&
(joinmd.getCatalog() == null || (joinmd.getCatalog() != null && joinmd.getCatalog().equalsIgnoreCase(mmd.getCatalog()))) &&
(joinmd.getSchema() == null || (joinmd.getSchema() != null && joinmd.getSchema().equalsIgnoreCase(mmd.getSchema()))))
{
theJoinMD = joinmd;
break;
}
}
}
// Create identifier - use specified catalog, else take catalog of the owning table
DatastoreIdentifier secTableIdentifier = storeMgr.getIdentifierFactory().newTableIdentifier(mmd.getTable());
String catalogName = mmd.getCatalog();
if (catalogName == null)
{
catalogName = getCatalogName();
}
// Use specified schema, else take schema of the owning table
String schemaName = mmd.getSchema();
if (schemaName == null)
{
schemaName = getSchemaName();
}
secTableIdentifier.setCatalogName(catalogName);
secTableIdentifier.setSchemaName(schemaName);
secTable = new SecondaryTable(secTableIdentifier, storeMgr, this, theJoinMD, clr);
secTable.preInitialize(clr);
secTable.initialize(clr);
secTable.postInitialize(clr);
secondaryTables.put(mmd.getTable(), secTable);
}
secTable.addMemberMapping(storeMgr.getMappingManager().getMapping(secTable, mmd,
clr, FieldRole.ROLE_FIELD));
}
}
else if (mmd.getPersistenceModifier() != FieldPersistenceModifier.TRANSACTIONAL)
{
throw new NucleusException(Localiser.msg("057006",mmd.getName())).setFatal();
}
// Calculate if we need a FK adding due to a 1-N (FK) relationship
boolean needsFKToContainerOwner = false;
RelationType relationType = mmd.getRelationType(clr);
if (relationType == RelationType.ONE_TO_MANY_BI)
{
AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
if (mmd.getJoinMetaData() == null && relatedMmds[0].getJoinMetaData() == null)
{
needsFKToContainerOwner = true;
}
}
else if (relationType == RelationType.ONE_TO_MANY_UNI && !mmd.isSingleCollection() )
{
if (mmd.getJoinMetaData() == null)
{
needsFKToContainerOwner = true;
}
}
if (needsFKToContainerOwner)
{
// 1-N uni/bidirectional using FK, so update the element side with a FK
if ((mmd.getCollection() != null && !SCOUtils.collectionHasSerialisedElements(mmd)) ||
(mmd.getArray() != null && !SCOUtils.arrayIsStoredInSingleColumn(mmd, storeMgr.getMetaDataManager())))
{
// 1-N ForeignKey collection/array, so add FK to element table
AbstractClassMetaData elementCmd = null;
if (mmd.hasCollection())
{
// Collection
elementCmd = storeMgr.getMetaDataManager().getMetaDataForClass(mmd.getCollection().getElementType(), clr);
}
else
{
// Array
elementCmd = storeMgr.getMetaDataManager().getMetaDataForClass(mmd.getType().getComponentType(), clr);
}
if (elementCmd == null)
{
String[] implClassNames = storeMgr.getMetaDataManager().getClassesImplementingInterface(mmd.getCollection().getElementType(), clr);
if (implClassNames != null && implClassNames.length > 0)
{
// Collection/array of interface type so apply callback to all implementation types
AbstractClassMetaData[] elementCmds = new AbstractClassMetaData[implClassNames.length];
for (int i=0;i elementSubclassNames = storeMgr.getSubClassesForClass(elementCmd.getFullClassName(), true, clr);
elementCmds = new ClassMetaData[elementSubclassNames != null ? 1+elementSubclassNames.size() : 1];
int elemNo = 0;
elementCmds[elemNo++] = elementCmd;
if (elementSubclassNames != null)
{
for (String elementSubclassName : elementSubclassNames)
{
AbstractClassMetaData elemSubCmd = storeMgr.getMetaDataManager().getMetaDataForClass(elementSubclassName, clr);
elementCmds[elemNo++] = elemSubCmd;
}
}
}
else if (elementCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.SUBCLASS_TABLE)
{
elementCmds = storeMgr.getClassesManagingTableForClass(elementCmd, clr);
}
else
{
elementCmds = new ClassMetaData[1];
elementCmds[0] = elementCmd;
}
ElementMetaData elemmd = mmd.getElementMetaData();
if (elemmd != null && !StringUtils.isWhitespace(elemmd.getTable()))
{
DatastoreIdentifier requiredTableId = storeMgr.getIdentifierFactory().newTableIdentifier(elemmd.getTable());
DatastoreClass requiredTable = storeMgr.getDatastoreClass(requiredTableId);
if (requiredTable != null)
{
// TODO Respect specification of table in ElementMetaData rather than just defaulting to table of element type
// Note that this will need updates to FKListStore, FKSetStore etc to look for the table
NucleusLogger.GENERAL.warn("Member=" + mmd.getFullFieldName() + " has 1-N FK with required table=" + requiredTable +
" : we don't currently support specification of the element table, and always take the default table for the element type");
/*for (int i=0;i 0)
{
Iterator colsIter = cols.iterator();
while (colsIter.hasNext())
{
ColumnMetaData colmd = (ColumnMetaData)colsIter.next();
// Create a column with the specified name and jdbc-type
if (colmd.getJdbcType() == JdbcType.VARCHAR && colmd.getLength() == null)
{
colmd.setLength(storeMgr.getIntProperty(RDBMSPropertyNames.PROPERTY_RDBMS_STRING_DEFAULT_LENGTH));
}
IdentifierFactory idFactory = getStoreManager().getIdentifierFactory();
DatastoreIdentifier colIdentifier = idFactory.newIdentifier(IdentifierType.COLUMN, colmd.getName());
Column col = addColumn(null, colIdentifier, null, colmd);
SQLTypeInfo sqlTypeInfo = storeMgr.getSQLTypeInfoForJDBCType(dba.getJDBCTypeForName(colmd.getJdbcTypeName()));
col.setTypeInfo(sqlTypeInfo);
if (unmappedColumns == null)
{
unmappedColumns = new HashSet<>();
}
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("057011", col.toString(), colmd.getJdbcType()));
}
unmappedColumns.add(col);
}
}
}
/**
* Accessor for whether this table manages the specified class.
* @param className Name of the class
* @return Whether it is managed by this table
*/
public boolean managesClass(String className)
{
if (className == null)
{
return false;
}
Iterator iter = managedClassMetaData.iterator();
while (iter.hasNext())
{
AbstractClassMetaData managedCmd = iter.next();
if (managedCmd.getFullClassName().equals(className))
{
return true;
}
}
return false;
}
/**
* Method to initialise the table primary key field(s).
* @param clr The ClassLoaderResolver
*/
@Override
protected void initializePK(ClassLoaderResolver clr)
{
assertIsPKUninitialized();
AbstractMemberMetaData[] membersToAdd = new AbstractMemberMetaData[cmd.getNoOfPrimaryKeyMembers()];
// Initialise Primary Key mappings for application id with PK fields in this class
int pkFieldNum=0;
int fieldCount = cmd.getNoOfManagedMembers();
boolean hasPrimaryKeyInThisClass = false;
if (cmd.getNoOfPrimaryKeyMembers() > 0)
{
pkMappings = new JavaTypeMapping[cmd.getNoOfPrimaryKeyMembers()];
if (cmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE)
{
// COMPLETE-TABLE so use root class metadata and add PK members
// TODO Does this allow for overridden PK field info ?
AbstractClassMetaData baseCmd = cmd.getBaseAbstractClassMetaData();
fieldCount = baseCmd.getNoOfManagedMembers();
for (int relFieldNum = 0; relFieldNum < fieldCount; ++relFieldNum)
{
AbstractMemberMetaData mmd = baseCmd.getMetaDataForManagedMemberAtRelativePosition(relFieldNum);
if (mmd.isPrimaryKey())
{
AbstractMemberMetaData overriddenMmd = cmd.getOverriddenMember(mmd.getName());
if (overriddenMmd != null)
{
// PK field is overridden so use the overriding definition
mmd = overriddenMmd;
}
if (mmd.getPersistenceModifier() == FieldPersistenceModifier.PERSISTENT)
{
membersToAdd[pkFieldNum++] = mmd;
hasPrimaryKeyInThisClass = true;
}
else if (mmd.getPersistenceModifier() != FieldPersistenceModifier.TRANSACTIONAL)
{
throw new NucleusException(Localiser.msg("057006", mmd.getName())).setFatal();
}
// Check if auto-increment and that it is supported by this RDBMS
if ((mmd.getValueStrategy() == ValueGenerationStrategy.IDENTITY) && !dba.supportsOption(DatastoreAdapter.IDENTITY_COLUMNS))
{
throw new NucleusException(Localiser.msg("057020", cmd.getFullClassName(), mmd.getName())).setFatal();
}
}
}
}
else
{
for (int relFieldNum=0; relFieldNum...
colContainer = cmd.getInheritanceMetaData().getJoinMetaData();
}
if (colContainer == null)
{
// Try via ...
colContainer = cmd.getPrimaryKeyMetaData();
}
addApplicationIdUsingClassTableId(colContainer, superTable, clr, cmd);
}
else
{
// No supertable to copy, so find superclass with PK fields and create new mappings and columns
AbstractClassMetaData pkCmd = getClassWithPrimaryKeyForClass(cmd.getSuperAbstractClassMetaData(), clr);
if (pkCmd != null)
{
// TODO Just use cmd.getPKMemberPositions to avoid iteration to find PKs
pkMappings = new JavaTypeMapping[pkCmd.getNoOfPrimaryKeyMembers()];
pkFieldNum = 0;
fieldCount = pkCmd.getNoOfInheritedManagedMembers() + pkCmd.getNoOfManagedMembers();
for (int absFieldNum = 0; absFieldNum < fieldCount; ++absFieldNum)
{
AbstractMemberMetaData fmd = pkCmd.getMetaDataForManagedMemberAtAbsolutePosition(absFieldNum);
if (fmd.isPrimaryKey())
{
AbstractMemberMetaData overriddenFmd = cmd.getOverriddenMember(fmd.getName());
if (overriddenFmd != null)
{
// PK field is overridden so use the overriding definition
fmd = overriddenFmd;
}
else
{
AbstractClassMetaData thisCmd = cmd;
while (thisCmd.getSuperAbstractClassMetaData() != null && thisCmd.getSuperAbstractClassMetaData() != pkCmd)
{
thisCmd = thisCmd.getSuperAbstractClassMetaData();
overriddenFmd = thisCmd.getOverriddenMember(fmd.getName());
if (overriddenFmd != null)
{
// PK field is overridden so use the overriding definition
fmd = overriddenFmd;
break;
}
}
}
if (fmd.getPersistenceModifier() == FieldPersistenceModifier.PERSISTENT)
{
membersToAdd[pkFieldNum++] = fmd;
}
else if (fmd.getPersistenceModifier() != FieldPersistenceModifier.TRANSACTIONAL)
{
throw new NucleusException(Localiser.msg("057006",fmd.getName())).setFatal();
}
}
}
}
}
}
else if (cmd.getIdentityType() == IdentityType.DATASTORE)
{
// datastore-identity
ColumnMetaData colmd = null;
if (cmd.getDatastoreIdentityMetaData() != null && cmd.getDatastoreIdentityMetaData().getColumnMetaData() != null)
{
// Try via ...
colmd = cmd.getDatastoreIdentityMetaData().getColumnMetaData();
}
if (colmd == null)
{
// Try via ...
if (cmd.getPrimaryKeyMetaData() != null && cmd.getPrimaryKeyMetaData().getColumnMetaData() != null &&
cmd.getPrimaryKeyMetaData().getColumnMetaData().length > 0)
{
colmd = cmd.getPrimaryKeyMetaData().getColumnMetaData()[0];
}
}
addDatastoreId(colmd, null, cmd);
}
else if (cmd.getIdentityType() == IdentityType.NONDURABLE)
{
// Do nothing since no identity!
}
}
//add field mappings in the end, so we compute all columns after the post initialize
for (int i=0; i 0 && cmd.getSuperAbstractClassMetaData().getNoOfPrimaryKeyMembers() == 0)
{
// This class has PK members but the superclass doesn't, so return this
return cmd;
}
return getClassWithPrimaryKeyForClass(cmd.getSuperAbstractClassMetaData(), clr);
}
/**
* Method to initialise this table to include all fields in the specified class.
* This is used to recurse up the hierarchy so that we include all immediate superclasses
* that have "subclass-table" specified as their inheritance strategy. If we encounter the parent of
* this class with other than "subclass-table" we stop the process.
* @param theCmd The ClassMetaData for the class
*/
private void initializeForClass(AbstractClassMetaData theCmd, ClassLoaderResolver clr)
{
String columnOrdering = storeMgr.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_TABLE_COLUMN_ORDER);
if (columnOrdering.equalsIgnoreCase("superclass-first"))
{
// Superclasses persisted into this table
AbstractClassMetaData parentCmd = theCmd.getSuperAbstractClassMetaData();
if (parentCmd != null)
{
if (cmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE)
{
// Managed class requires all superclasses managed here too
initializeForClass(parentCmd, clr);
}
else if (parentCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.SUBCLASS_TABLE)
{
// Superclass uses "subclass-table" so needs managing here
initializeForClass(parentCmd, clr);
}
}
// Owning class
manageClass(theCmd, clr);
}
else
{
// Owning class
manageClass(theCmd, clr);
// Superclasses persisted into this table
AbstractClassMetaData parentCmd = theCmd.getSuperAbstractClassMetaData();
if (parentCmd != null)
{
if (cmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE)
{
// Managed class requires all superclasses managed here too
initializeForClass(parentCmd, clr);
}
else if (parentCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.SUBCLASS_TABLE)
{
// Superclass uses "subclass-table" so needs managing here
initializeForClass(parentCmd, clr);
}
}
}
}
/**
* Execute the callbacks for the classes that this table maps to.
* @param clr ClassLoader resolver
*/
private void runCallBacks(ClassLoaderResolver clr)
{
// Run callbacks for all classes managed by this table
Iterator cmdIter = managedClassMetaData.iterator();
while (cmdIter.hasNext())
{
AbstractClassMetaData managedCmd = cmdIter.next();
if (managingClassCurrent != null && managingClassCurrent.equals(managedCmd.getFullClassName()))
{
// We can't run callbacks for this class since it is still being initialised. Mark callbacks to run after it completes
runCallbacksAfterManageClass = true;
break;
}
Collection processedCallbacks = callbacksAppliedForManagedClass.get(managedCmd.getFullClassName());
Collection c = storeMgr.getSchemaCallbacks().get(managedCmd.getFullClassName());
if (c != null)
{
if (processedCallbacks == null)
{
processedCallbacks = new HashSet<>();
callbacksAppliedForManagedClass.put(managedCmd.getFullClassName(), processedCallbacks);
}
for (Iterator it = c.iterator(); it.hasNext();)
{
AbstractMemberMetaData callbackMmd = it.next();
if (processedCallbacks.contains(callbackMmd))
{
continue;
}
processedCallbacks.add(callbackMmd);
if (callbackMmd.getJoinMetaData() == null)
{
// 1-N FK relationship
AbstractMemberMetaData ownerFmd = callbackMmd;
if (ownerFmd.getMappedBy() != null)
{
// Bidirectional (element has a PC mapping to the owner)
// Check that the "mapped-by" field in the other class actually exists
AbstractMemberMetaData fmd = null;
if (ownerFmd.getMappedBy().indexOf('.') > 0)
{
// TODO Can we just use getRelatedMemberMetaData always?
AbstractMemberMetaData[] relMmds = ownerFmd.getRelatedMemberMetaData(clr);
fmd = (relMmds != null && relMmds.length > 0) ? relMmds[0] : null;
}
else
{
fmd = managedCmd.getMetaDataForMember(ownerFmd.getMappedBy());
}
if (fmd == null)
{
throw new NucleusUserException(Localiser.msg("057036", ownerFmd.getMappedBy(), managedCmd.getFullClassName(), ownerFmd.getFullFieldName()));
}
if (ownerFmd.getMap() != null && storeMgr.getBooleanProperty(RDBMSPropertyNames.PROPERTY_RDBMS_UNIQUE_CONSTRAINTS_MAP_INVERSE))
{
initializeFKMapUniqueConstraints(ownerFmd);
}
boolean duplicate = false;
JavaTypeMapping fkDiscrimMapping = null;
JavaTypeMapping orderMapping = null;
if (ownerFmd.hasExtension(MetaData.EXTENSION_MEMBER_RELATION_DISCRIM_COLUMN))
{
// Collection has a relation discriminator so we need to share the FK. Check for the required discriminator
String colName = ownerFmd.getValueForExtension(MetaData.EXTENSION_MEMBER_RELATION_DISCRIM_COLUMN);
if (colName == null)
{
// No column defined so use a fallback name
colName = "RELATION_DISCRIM";
}
Set fkDiscrimEntries = getExternalFkDiscriminatorMappings().entrySet();
Iterator discrimMappingIter = fkDiscrimEntries.iterator();
while (discrimMappingIter.hasNext())
{
Map.Entry entry = (Map.Entry)discrimMappingIter.next();
JavaTypeMapping discrimMapping = (JavaTypeMapping)entry.getValue();
String discrimColName = (discrimMapping.getColumnMapping(0).getColumn().getColumnMetaData()).getName();
if (discrimColName.equalsIgnoreCase(colName))
{
duplicate = true;
fkDiscrimMapping = discrimMapping;
orderMapping = getExternalOrderMappings().get(entry.getKey());
break;
}
}
if (!duplicate)
{
// Create the relation discriminator column since we dont have this discriminator
ColumnMetaData colmd = new ColumnMetaData();
colmd.setName(colName);
colmd.setAllowsNull(Boolean.TRUE); // Allow for elements not in any discriminated collection
fkDiscrimMapping = storeMgr.getMappingManager().getMapping(String.class); // Only support String discriminators currently
fkDiscrimMapping.setTable(this);
ColumnCreator.createIndexColumn(fkDiscrimMapping, storeMgr, clr, this, colmd, false);
}
if (fkDiscrimMapping != null)
{
getExternalFkDiscriminatorMappings().put(ownerFmd, fkDiscrimMapping);
}
}
// Add the order mapping as necessary
addOrderMapping(ownerFmd, orderMapping, clr);
}
else
{
// Unidirectional (element knows nothing about the owner)
String ownerClassName = ownerFmd.getAbstractClassMetaData().getFullClassName();
JavaTypeMapping fkMapping = new PersistableMapping();
fkMapping.setTable(this);
fkMapping.initialize(storeMgr, ownerClassName);
JavaTypeMapping fkDiscrimMapping = null;
JavaTypeMapping orderMapping = null;
boolean duplicate = false;
try
{
// Get the owner id mapping of the "1" end
DatastoreClass ownerTbl = storeMgr.getDatastoreClass(ownerClassName, clr);
if (ownerTbl == null)
{
// Class doesn't have its own table (subclass-table) so find where it persists
AbstractClassMetaData[] ownerParentCmds = storeMgr.getClassesManagingTableForClass(ownerFmd.getAbstractClassMetaData(), clr);
if (ownerParentCmds.length > 1)
{
throw new NucleusUserException("Relation (" + ownerFmd.getFullFieldName() +
") with multiple related tables (using subclass-table). Not supported");
}
ownerClassName = ownerParentCmds[0].getFullClassName();
ownerTbl = storeMgr.getDatastoreClass(ownerClassName, clr);
if (ownerTbl == null)
{
throw new NucleusException("Failed to get owner table at other end of relation for field=" + ownerFmd.getFullFieldName());
}
}
JavaTypeMapping ownerIdMapping = ownerTbl.getIdMapping();
ColumnMetaDataContainer colmdContainer = null;
if (ownerFmd.hasCollection() || ownerFmd.hasArray())
{
// 1-N Collection/array
colmdContainer = ownerFmd.getElementMetaData();
}
else if (ownerFmd.hasMap() && ownerFmd.getKeyMetaData() != null && ownerFmd.getKeyMetaData().getMappedBy() != null)
{
// 1-N Map with key stored in the value
colmdContainer = ownerFmd.getValueMetaData();
}
else if (ownerFmd.hasMap() && ownerFmd.getValueMetaData() != null && ownerFmd.getValueMetaData().getMappedBy() != null)
{
// 1-N Map with value stored in the key
colmdContainer = ownerFmd.getKeyMetaData();
}
CorrespondentColumnsMapper correspondentColumnsMapping = new CorrespondentColumnsMapper(colmdContainer, this, ownerIdMapping, true);
int countIdFields = ownerIdMapping.getNumberOfColumnMappings();
for (int i=0; i and is indexed list so needs order mapping
needsOrderMapping = true;
if (omd.getMappedBy() != null)
{
// Try to find the mapping if already created
orderMapping = getMemberMapping(omd.getMappedBy());
}
}
if (needsOrderMapping)
{
// if the field is list or array type, add index column
state = TABLE_STATE_NEW;
if (orderMapping == null)
{
// Create new order mapping since we need one and we aren't using a shared FK
orderMapping = this.addOrderColumn(mmd, clr);
}
getExternalOrderMappings().put(mmd, orderMapping);
state = TABLE_STATE_INITIALIZED;
}
return orderMapping;
}
/**
* Accessor for the main class represented.
* @return The name of the class
**/
public String getType()
{
return cmd.getFullClassName();
}
/**
* Accessor for the identity-type.
* @return identity-type tag value
*/
public IdentityType getIdentityType()
{
return cmd.getIdentityType();
}
/**
* Accessor for versionMetaData
* @return Returns the versionMetaData.
*/
public final VersionMetaData getVersionMetaData()
{
return versionMetaData;
}
/**
* Accessor for Discriminator MetaData
* @return Returns the Discriminator MetaData.
*/
public final DiscriminatorMetaData getDiscriminatorMetaData()
{
return discriminatorMetaData;
}
/**
* Convenience method to return the root table with a discriminator in this inheritance tree.
* @return The root table which has the discriminator in this inheritance tree
*/
public final ClassTable getTableWithDiscriminator()
{
if (supertable != null)
{
ClassTable tbl = supertable.getTableWithDiscriminator();
if (tbl != null)
{
return tbl;
}
}
if (discriminatorMetaData != null)
{
// Initialised and discriminator metadata set so return this
return this;
}
else if (cmd.getInheritanceMetaData() != null && cmd.getInheritanceMetaData().getDiscriminatorMetaData() != null)
{
// Not initialised but has discriminator MetaData so return this
return this;
}
return null;
}
/**
* Whether this table or super table has id (primary key) attributed by the datastore
* @return true if the id attributed by the datastore
*/
@Override
public boolean isObjectIdDatastoreAttributed()
{
if (storeMgr.isValueGenerationStrategyDatastoreAttributed(cmd, -1))
{
return true;
}
int numCols = columns.size();
for (int i=0; i getSecondaryDatastoreClasses()
{
return (secondaryTables != null ? secondaryTables.values() : null);
}
@Override
public JavaTypeMapping getSurrogateMapping(SurrogateColumnType colType, boolean allowSuperclasses)
{
if (colType == SurrogateColumnType.DISCRIMINATOR)
{
if (discriminatorMapping != null)
{
// We have the mapping so return it
return discriminatorMapping;
}
if (allowSuperclasses && supertable != null)
{
// Return what the supertable has if it has the mapping
return supertable.getSurrogateMapping(colType, allowSuperclasses);
}
}
else if (colType == SurrogateColumnType.VERSION)
{
if (versionMapping != null)
{
// We have the mapping so return it
return versionMapping;
}
if (allowSuperclasses && supertable != null)
{
// Return what the supertable has if it has the mapping
return supertable.getSurrogateMapping(colType, allowSuperclasses);
}
}
return super.getSurrogateMapping(colType, allowSuperclasses);
}
public ClassTable getTableManagingMapping(JavaTypeMapping mapping)
{
if (managesMapping(mapping))
{
return this;
}
else if (supertable != null)
{
return supertable.getTableManagingMapping(mapping);
}
return null;
}
/**
* Utility to find the table above this one.
* Will recurse to cater for inheritance strategies where fields are handed up to the super class, or down to this class.
* @param theCmd ClassMetaData of the class to find the supertable for.
* @return The table above this one in any inheritance hierarchy
*/
private ClassTable getSupertable(AbstractClassMetaData theCmd, ClassLoaderResolver clr)
{
if (cmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE)
{
// "complete-table" has no super table. All is persisted into this table
return null;
}
AbstractClassMetaData superCmd = theCmd.getSuperAbstractClassMetaData();
if (superCmd != null)
{
if (superCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.NEW_TABLE)
{
// This class has its own table, so return it.
return (ClassTable) storeMgr.getDatastoreClass(superCmd.getFullClassName(), clr);
}
else if (superCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.SUBCLASS_TABLE)
{
// This class is mapped to the same table, so go up another level.
return getSupertable(superCmd, clr);
}
else
{
// This class is mapped to its superclass table, so go up to that and try again.
return getSupertable(superCmd, clr);
}
}
return null;
}
/**
* Convenience accessor for the base table for this table which has the specified member.
* @param mmd Member MetaData for this field
* @return The base table which has the field specified
*/
public DatastoreClass getBaseDatastoreClassWithMember(AbstractMemberMetaData mmd)
{
if (mmd == null)
{
return null;
}
if (mmd.isPrimaryKey())
{
// If this is a PK field then it will be in all tables up to the base so we need to continue navigating up
if (getSuperDatastoreClass() != null)
{
return getSuperDatastoreClass().getBaseDatastoreClassWithMember(mmd);
}
}
if (memberMappingsMap.get(mmd) != null)
{
// We have this field so return this table
return this;
}
else if (externalFkMappings != null && externalFkMappings.get(mmd) != null)
{
return this;
}
else if (externalFkDiscriminatorMappings != null && externalFkDiscriminatorMappings.get(mmd) != null)
{
return this;
}
else if (externalOrderMappings != null && externalOrderMappings.get(mmd) != null)
{
return this;
}
else if (getSuperDatastoreClass() == null)
{
// We don't have the field, but have no superclass, so return null
return this;
}
else
{
// Return the superclass since we don't have it
return getSuperDatastoreClass().getBaseDatastoreClassWithMember(mmd);
}
}
/**
* Accessor for the (primary) class MetaData.
* Package-level access to restrict to other table types only.
* @return The (primary) class MetaData
**/
public ClassMetaData getClassMetaData()
{
return cmd;
}
/**
* Accessor for the indices for this table. This includes both the
* user-defined indices (via MetaData), and the ones required by foreign
* keys (required by relationships).
* @param clr The ClassLoaderResolver
* @return The indices
*/
@Override
protected Set getExpectedIndices(ClassLoaderResolver clr)
{
// Auto mode allows us to decide which indices are needed as well as using what is in the users MetaData
boolean autoMode = false;
if (storeMgr.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_CONSTRAINT_CREATE_MODE).equals("DataNucleus"))
{
autoMode = true;
}
Set indices = new HashSet<>();
// Add on any user-required indices for the fields/properties
Set memberNumbersSet = memberMappingsMap.keySet();
Iterator iter = memberNumbersSet.iterator();
while (iter.hasNext())
{
AbstractMemberMetaData fmd = (AbstractMemberMetaData) iter.next();
JavaTypeMapping fieldMapping = memberMappingsMap.get(fmd);
if (fieldMapping instanceof EmbeddedPCMapping)
{
// Add indexes for fields of this embedded PC object
EmbeddedPCMapping embMapping = (EmbeddedPCMapping)fieldMapping;
for (int i=0;i 0)
{
Index index = new Index(this, false, null);
for (int i=0;i)
Iterator cmdIter = managedClassMetaData.iterator();
while (cmdIter.hasNext())
{
AbstractClassMetaData thisCmd = cmdIter.next();
List classIndices = thisCmd.getIndexMetaData();
if (classIndices != null)
{
for (IndexMetaData idxmd : classIndices)
{
Index index = getIndexForIndexMetaData(idxmd);
if (index != null)
{
indices.add(index);
}
}
}
}
if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
// Make sure there is no reuse of PK fields that cause a duplicate index for the PK. Remove it if required
PrimaryKey pk = getPrimaryKey();
Iterator indicesIter = indices.iterator();
while (indicesIter.hasNext())
{
Index idx = indicesIter.next();
if (idx.getColumnList().equals(pk.getColumnList()))
{
NucleusLogger.DATASTORE_SCHEMA.debug("Index " + idx + " is for the same columns as the PrimaryKey so being removed from expected set of indices. PK is always indexed");
indicesIter.remove();
}
}
}
return indices;
}
/**
* Convenience method to convert an IndexMetaData and a mapping into an Index.
* @param imd The Index MetaData
* @param mapping The mapping
* @return The Index
*/
private Index getIndexForIndexMetaDataAndMapping(IndexMetaData imd, JavaTypeMapping mapping)
{
// Verify if a unique index is needed
boolean unique = imd.isUnique();
Index index = new Index(this, unique, imd.getExtensions());
// Set the index name if required
if (imd.getName() != null)
{
index.setName(imd.getName());
}
int numCols = mapping.getNumberOfColumnMappings();
for (int i=0;i 0)
{
// a). Columns specified directly
String[] columnNames = imd.getColumnNames();
for (String columnName : columnNames)
{
DatastoreIdentifier colName = storeMgr.getIdentifierFactory().newColumnIdentifier(columnName);
Column col = columnsByIdentifier.get(colName);
if (col == null)
{
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("058001", toString(), index.getName(), columnName));
break;
}
index.addColumn(col);
}
// Apply any user-provided ordering of the columns
String idxOrdering = imd.getValueForExtension(MetaData.EXTENSION_INDEX_COLUMN_ORDERING);
if (!StringUtils.isWhitespace(idxOrdering))
{
index.setColumnOrdering(idxOrdering);
}
}
else if (imd.getNumberOfMembers() > 0)
{
// b). Columns specified using members
String[] memberNames = imd.getMemberNames();
for (int i=0;i getExpectedForeignKeys(ClassLoaderResolver clr)
{
assertIsInitialized();
// Auto mode allows us to decide which FKs are needed as well as using what is in the users MetaData.
boolean autoMode = false;
if (storeMgr.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_CONSTRAINT_CREATE_MODE).equals("DataNucleus"))
{
autoMode = true;
}
ArrayList foreignKeys = new ArrayList<>();
// Check each field for FK requirements (user-defined, or required)
// ...
Set memberNumbersSet = memberMappingsMap.keySet();
Iterator iter = memberNumbersSet.iterator();
while (iter.hasNext())
{
AbstractMemberMetaData mmd = (AbstractMemberMetaData) iter.next();
JavaTypeMapping memberMapping = memberMappingsMap.get(mmd);
if (memberMapping instanceof EmbeddedPCMapping)
{
EmbeddedPCMapping embMapping = (EmbeddedPCMapping)memberMapping;
addExpectedForeignKeysForEmbeddedPCField(foreignKeys, autoMode, clr, embMapping);
}
else
{
if (ClassUtils.isReferenceType(mmd.getType()) && memberMapping instanceof ReferenceMapping)
{
// Field is a reference type, so add a FK to the table of the PC for each PC implementation
Collection fks = TableUtils.getForeignKeysForReferenceField(memberMapping, mmd, autoMode, storeMgr, clr);
foreignKeys.addAll(fks);
}
else if (storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(mmd.getType(), clr) != null &&
memberMapping.getNumberOfColumnMappings() > 0 && memberMapping instanceof PersistableMapping)
{
// Field is for a PC class with the FK at this side, so add a FK to the table of this PC
ForeignKey fk = TableUtils.getForeignKeyForPCField(memberMapping, mmd, autoMode, storeMgr, clr);
if (fk != null)
{
// Check for dups (can happen if we override a persistent property for 1-1/N-1 in a subclass)
boolean exists = false;
for (ForeignKey theFK : foreignKeys)
{
if (theFK.isEqual(fk))
{
exists = true;
break;
}
}
if (!exists)
{
foreignKeys.add(fk);
}
}
}
}
}
// FK from id column(s) to id column(s) of superclass, as specified by
//
ForeignKeyMetaData idFkmd = (cmd.getInheritanceMetaData().getJoinMetaData() != null) ?
cmd.getInheritanceMetaData().getJoinMetaData().getForeignKeyMetaData() : null;
if (supertable != null && (autoMode || (idFkmd != null && idFkmd.getDeleteAction() != ForeignKeyAction.NONE)))
{
ForeignKey fk = new ForeignKey(getIdMapping(), dba, supertable, false);
if (idFkmd != null && idFkmd.getName() != null)
{
fk.setName(idFkmd.getName());
}
foreignKeys.add(0, fk);
}
// Add any user-required FKs for the class as a whole
// ...
Iterator cmdIter = managedClassMetaData.iterator();
while (cmdIter.hasNext())
{
AbstractClassMetaData thisCmd = cmdIter.next();
List fkmds = thisCmd.getForeignKeyMetaData();
if (fkmds != null)
{
for (ForeignKeyMetaData fkmd : fkmds)
{
ForeignKey fk = getForeignKeyForForeignKeyMetaData(fkmd);
if (fk != null)
{
foreignKeys.add(fk);
}
}
}
}
Map externalFks = getExternalFkMappings();
if (!externalFks.isEmpty())
{
// 1-N FK relationships - FK to id column(s) of owner table where this is the element table and we have a FK
Set> externalFkKeys = externalFks.entrySet();
Iterator> externalFkKeysIter = externalFkKeys.iterator();
while (externalFkKeysIter.hasNext())
{
Map.Entry entry = externalFkKeysIter.next();
AbstractMemberMetaData fmd = entry.getKey();
DatastoreClass referencedTable = storeMgr.getDatastoreClass(fmd.getAbstractClassMetaData().getFullClassName(), clr);
if (referencedTable != null)
{
// Take from either or
ForeignKeyMetaData fkmd = fmd.getForeignKeyMetaData();
if (fkmd == null && fmd.getElementMetaData() != null)
{
fkmd = fmd.getElementMetaData().getForeignKeyMetaData();
}
if ((fkmd != null && fkmd.getDeleteAction() != ForeignKeyAction.NONE) || autoMode)
{
// Either has been specified by user, or using autoMode, so add FK
JavaTypeMapping fkMapping = entry.getValue();
ForeignKey fk = new ForeignKey(fkMapping, dba, referencedTable, true);
fk.setForMetaData(fkmd); // Does nothing when no FK MetaData
if (!foreignKeys.contains(fk))
{
// Only add when not already present (in the case of shared FKs there can be dups here)
foreignKeys.add(fk);
}
}
}
}
}
return foreignKeys;
}
/**
* Convenience method to add the expected FKs for an embedded PC field.
* @param foreignKeys The list of FKs to add the FKs to
* @param autoMode Whether operating in "auto-mode" where DataNucleus can create its own FKs
* @param clr ClassLoader resolver
* @param embeddedMapping The embedded PC mapping
*/
private void addExpectedForeignKeysForEmbeddedPCField(List foreignKeys, boolean autoMode, ClassLoaderResolver clr, EmbeddedPCMapping embeddedMapping)
{
for (int i=0;i fks = TableUtils.getForeignKeysForReferenceField(embFieldMapping, embFmd, autoMode, storeMgr, clr);
foreignKeys.addAll(fks);
}
else if (storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(embFmd.getType(), clr) != null &&
embFieldMapping.getNumberOfColumnMappings() > 0 && embFieldMapping instanceof PersistableMapping)
{
// Field is for a PC class with the FK at this side, so add a FK to the table of this PC
ForeignKey fk = TableUtils.getForeignKeyForPCField(embFieldMapping, embFmd, autoMode, storeMgr, clr);
if (fk != null)
{
foreignKeys.add(fk);
}
}
}
}
}
/**
* Convenience method to create a FK for the specified ForeignKeyMetaData.
* Used for foreign-keys specified at <class> level.
* @param fkmd ForeignKey MetaData
* @return The ForeignKey
*/
private ForeignKey getForeignKeyForForeignKeyMetaData(ForeignKeyMetaData fkmd)
{
if (fkmd == null)
{
return null;
}
// Create the ForeignKey base details
ForeignKey fk = new ForeignKey(dba, fkmd.isDeferred());
fk.setForMetaData(fkmd);
if (fkmd.getFkDefinitionApplies())
{
// User-defined FK definition should be used
return fk;
}
// Find the target of the foreign-key
AbstractClassMetaData acmd = cmd;
if (fkmd.getTable() == null)
{
// Can't create a FK if we don't know where it goes to
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("058105", acmd.getFullClassName()));
return null;
}
DatastoreIdentifier tableId = storeMgr.getIdentifierFactory().newTableIdentifier(fkmd.getTable());
ClassTable refTable = (ClassTable)storeMgr.getDatastoreClass(tableId);
if (refTable == null)
{
// TODO Go to the datastore and query for this table to get the columns of the PK
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("058106", acmd.getFullClassName(), fkmd.getTable()));
return null;
}
PrimaryKey pk = refTable.getPrimaryKey();
List targetCols = pk.getColumns();
// Generate the columns for the source of the foreign-key
List sourceCols = new ArrayList<>();
ColumnMetaData[] colmds = fkmd.getColumnMetaData();
String[] memberNames = fkmd.getMemberNames();
if (colmds != null && colmds.length > 0)
{
// FK specified via
for (int i=0;i 0)
{
// FK specified via
for (int i=0;i 0)
{
for (int i=0;i getExpectedCandidateKeys()
{
assertIsInitialized();
// The candidate keys required by the basic table
List candidateKeys = super.getExpectedCandidateKeys();
// Add any constraints required for a FK map
Iterator cks = candidateKeysByMapField.values().iterator();
while (cks.hasNext())
{
CandidateKey ck = cks.next();
candidateKeys.add(ck);
}
// Add on any user-required candidate keys for the fields
Set fieldNumbersSet = memberMappingsMap.keySet();
Iterator iter = fieldNumbersSet.iterator();
while (iter.hasNext())
{
AbstractMemberMetaData fmd=(AbstractMemberMetaData) iter.next();
JavaTypeMapping fieldMapping = memberMappingsMap.get(fmd);
if (fieldMapping instanceof EmbeddedPCMapping)
{
// Add indexes for fields of this embedded PC object
EmbeddedPCMapping embMapping = (EmbeddedPCMapping)fieldMapping;
for (int i=0;i cmdIter = managedClassMetaData.iterator();
while (cmdIter.hasNext())
{
AbstractClassMetaData thisCmd = cmdIter.next();
List classCKs = thisCmd.getUniqueMetaData();
if (classCKs != null)
{
for (UniqueMetaData unimd : classCKs)
{
CandidateKey ck = getCandidateKeyForUniqueMetaData(unimd);
if (ck != null)
{
candidateKeys.add(ck);
}
}
}
}
if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
// Make sure there is no reuse of PK fields that cause a duplicate index for the PK. Remove it if required
PrimaryKey pk = getPrimaryKey();
Iterator candidatesIter = candidateKeys.iterator();
while (candidatesIter.hasNext())
{
CandidateKey key = candidatesIter.next();
if (key.getColumnList().equals(pk.getColumnList()))
{
NucleusLogger.DATASTORE_SCHEMA.debug("Candidate key " + key + " is for the same columns as the PrimaryKey so being removed from expected set of candidates. PK is always unique");
candidatesIter.remove();
}
}
}
return candidateKeys;
}
/**
* Convenience method to convert a UniqueMetaData into a CandidateKey.
* @param umd The Unique MetaData
* @return The Candidate Key
*/
private CandidateKey getCandidateKeyForUniqueMetaData(UniqueMetaData umd)
{
CandidateKey ck = new CandidateKey(this, umd.getExtensions());
// Set the key name if required
if (umd.getName() != null)
{
ck.setName(umd.getName());
}
// Class-level index so use its column definition
// a). Columns specified directly
if (umd.getNumberOfColumns() > 0)
{
String[] columnNames = umd.getColumnNames();
for (String columnName : columnNames)
{
DatastoreIdentifier colName = storeMgr.getIdentifierFactory().newColumnIdentifier(columnName);
Column col = columnsByIdentifier.get(colName);
if (col == null)
{
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("058202", toString(), ck.getName(), columnName));
break;
}
ck.addColumn(col);
}
}
// b). Columns specified using fields
else if (umd.getNumberOfMembers() > 0)
{
String[] memberNames = umd.getMemberNames();
for (String memberName : memberNames)
{
// Find the metadata for the actual field with the same name as this "unique" field
AbstractMemberMetaData realMmd = getMetaDataForMember(memberName);
if (realMmd == null)
{
throw new NucleusUserException("Table " + this + " has unique key specified on member " + memberName +
" but that member does not exist in the class that this table represents");
}
JavaTypeMapping memberMapping = memberMappingsMap.get(realMmd);
int countFields = memberMapping.getNumberOfColumnMappings();
for (int j=0; j getSQLCreateStatements(Properties props)
{
List stmts;
// Create statements for this table
Properties tableProps = null;
if (createStatementDDL != null)
{
// User has specified the DDL
stmts = new ArrayList<>();
StringTokenizer tokens = new StringTokenizer(createStatementDDL, ";");
while (tokens.hasMoreTokens())
{
stmts.add(tokens.nextToken());
}
}
else
{
// Create the DDL
Map emds = cmd.getExtensions();
if (emds != null)
{
tableProps = new Properties();
if (emds.size() > 0)
{
tableProps.putAll(emds);
}
}
stmts = super.getSQLCreateStatements(tableProps);
}
// Create statements for any secondary tables
// Since the secondary tables are managed by us, we need to create their table
if (secondaryTables != null)
{
Set secondaryTableNames = secondaryTables.keySet();
for (String secTableName : secondaryTableNames)
{
SecondaryTable secTable = secondaryTables.get(secTableName);
stmts.addAll(secTable.getSQLCreateStatements(tableProps)); // Use same tableProps as the primary table
}
}
return stmts;
}
/**
* Accessor for the DROP statements for this table.
* Drops all secondary tables (if any) followed by the table itself.
* @return List of statements
*/
protected List getSQLDropStatements()
{
assertIsInitialized();
List stmts = new ArrayList<>();
// Drop any secondary tables
if (secondaryTables != null)
{
Set secondaryTableNames = secondaryTables.keySet();
for (String secTableName : secondaryTableNames)
{
SecondaryTable secTable = secondaryTables.get(secTableName);
stmts.addAll(secTable.getSQLDropStatements());
}
}
// Drop this table
stmts.add(dba.getDropTableStatement(this));
return stmts;
}
/**
* Method to initialise unique constraints for 1-N Map using FK.
* @param ownerMmd metadata for the field/property with the map in the owner class
*/
private void initializeFKMapUniqueConstraints(AbstractMemberMetaData ownerMmd)
{
// Load "mapped-by"
AbstractMemberMetaData mfmd = null;
String map_field_name = ownerMmd.getMappedBy(); // Field in our class that maps back to the owner class
if (map_field_name != null)
{
// Bidirectional
mfmd = cmd.getMetaDataForMember(map_field_name);
if (mfmd == null)
{
// Field not in primary class so may be in subclass so check all managed classes
Iterator cmdIter = managedClassMetaData.iterator();
while (cmdIter.hasNext())
{
AbstractClassMetaData managedCmd = cmdIter.next();
mfmd = managedCmd.getMetaDataForMember(map_field_name);
if (mfmd != null)
{
break;
}
}
}
if (mfmd == null)
{
// "mapped-by" refers to a field in our class that doesnt exist!
throw new NucleusUserException(Localiser.msg("057036", map_field_name, cmd.getFullClassName(), ownerMmd.getFullFieldName()));
}
if (ownerMmd.getJoinMetaData() == null)
{
// Load field of key in value
if (ownerMmd.getKeyMetaData() != null && ownerMmd.getKeyMetaData().getMappedBy() != null)
{
// Key field is stored in the value table
AbstractMemberMetaData kmd = null;
String key_field_name = ownerMmd.getKeyMetaData().getMappedBy();
if (key_field_name != null)
{
kmd = cmd.getMetaDataForMember(key_field_name);
}
if (kmd == null)
{
// Field not in primary class so may be in subclass so check all managed classes
Iterator cmdIter = managedClassMetaData.iterator();
while (cmdIter.hasNext())
{
AbstractClassMetaData managedCmd = cmdIter.next();
kmd = managedCmd.getMetaDataForMember(key_field_name);
if (kmd != null)
{
break;
}
}
}
if (kmd == null)
{
throw new ClassDefinitionException(Localiser.msg("057007", mfmd.getFullFieldName(), key_field_name));
}
JavaTypeMapping ownerMapping = getMemberMapping(map_field_name);
JavaTypeMapping keyMapping = getMemberMapping(kmd.getName());
if (dba.supportsOption(DatastoreAdapter.NULLS_IN_CANDIDATE_KEYS) || (!ownerMapping.isNullable() && !keyMapping.isNullable()))
{
// If the owner and key fields are represented in this table then we can impose
// a unique constraint on them. If the key field is in a superclass then we
// cannot do this so just omit it.
if (keyMapping.getTable() == this && ownerMapping.getTable() == this)
{
CandidateKey ck = new CandidateKey(this, null);
// This HashSet is to avoid duplicate adding of columns.
Set addedColumns = new HashSet<>();
// Add columns for the owner field
int countOwnerFields = ownerMapping.getNumberOfColumnMappings();
for (int i = 0; i < countOwnerFields; i++)
{
Column col = ownerMapping.getColumnMapping(i).getColumn();
addedColumns.add(col);
ck.addColumn(col);
}
// Add columns for the key field
int countKeyFields = keyMapping.getNumberOfColumnMappings();
for (int i = 0; i < countKeyFields; i++)
{
Column col = keyMapping.getColumnMapping(i).getColumn();
if (!addedColumns.contains(col))
{
addedColumns.add(col);
ck.addColumn(col);
}
else
{
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("057041", ownerMmd.getName()));
}
}
if (candidateKeysByMapField.put(mfmd, ck) != null)
{
// We have multiple "mapped-by" coming to this field so give a warning that this may potentially
// cause problems. For example if they have the key field defined here for 2 different relations
// so you may get keys/values appearing in the other relation that shouldn't be.
// Logged as a WARNING for now.
// If there is a situation where this should throw an exception, please update this AND COMMENT WHY.
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("057012", mfmd.getFullFieldName(), ownerMmd.getFullFieldName()));
}
}
}
}
else if (ownerMmd.getValueMetaData() != null && ownerMmd.getValueMetaData().getMappedBy() != null)
{
// Value field is stored in the key table
AbstractMemberMetaData vmd = null;
String value_field_name = ownerMmd.getValueMetaData().getMappedBy();
if (value_field_name != null)
{
vmd = cmd.getMetaDataForMember(value_field_name);
}
if (vmd == null)
{
throw new ClassDefinitionException(Localiser.msg("057008", mfmd));
}
JavaTypeMapping ownerMapping = getMemberMapping(map_field_name);
JavaTypeMapping valueMapping = getMemberMapping(vmd.getName());
if (dba.supportsOption(DatastoreAdapter.NULLS_IN_CANDIDATE_KEYS) || (!ownerMapping.isNullable() && !valueMapping.isNullable()))
{
// If the owner and value fields are represented in this table then we can impose
// a unique constraint on them. If the value field is in a superclass then we
// cannot do this so just omit it.
if (valueMapping.getTable() == this && ownerMapping.getTable() == this)
{
CandidateKey ck = new CandidateKey(this, null);
// This HashSet is to avoid duplicate adding of columns.
Set addedColumns = new HashSet<>();
// Add columns for the owner field
int countOwnerFields = ownerMapping.getNumberOfColumnMappings();
for (int i = 0; i < countOwnerFields; i++)
{
Column col = ownerMapping.getColumnMapping(i).getColumn();
addedColumns.add(col);
ck.addColumn(col);
}
// Add columns for the value field
int countValueFields = valueMapping.getNumberOfColumnMappings();
for (int i = 0; i < countValueFields; i++)
{
Column col = valueMapping.getColumnMapping(i).getColumn();
if (!addedColumns.contains(col))
{
addedColumns.add(col);
ck.addColumn(col);
}
else
{
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("057042", ownerMmd.getName()));
}
}
if (candidateKeysByMapField.put(mfmd, ck) != null)
{
// We have multiple "mapped-by" coming to this field so give a warning that this may potentially
// cause problems. For example if they have the key field defined here for 2 different relations
// so you may get keys/values appearing in the other relation that shouldn't be.
// Logged as a WARNING for now.
// If there is a situation where this should throw an exception, please update this AND COMMENT WHY.
NucleusLogger.DATASTORE_SCHEMA.warn(Localiser.msg("057012", mfmd.getFullFieldName(), ownerMmd.getFullFieldName()));
}
}
}
}
else
{
// We can only have either the key stored in the value or the value stored in the key but nothing else!
throw new ClassDefinitionException(Localiser.msg("057009", ownerMmd.getFullFieldName()));
}
}
}
}
/**
* Initialize the ID Mapping.
* For datastore identity this will be a PCMapping that contains the DatastoreIdMapping.
* For application identity this will be a PCMapping that contains the PK mapping(s).
*/
private void initializeIDMapping()
{
if (idMapping != null)
{
return;
}
final PersistableMapping mapping = new PersistableMapping();
mapping.setTable(this);
mapping.initialize(getStoreManager(), cmd.getFullClassName());
if (getIdentityType() == IdentityType.DATASTORE)
{
mapping.addJavaTypeMapping(datastoreIdMapping);
}
else if (getIdentityType() == IdentityType.APPLICATION)
{
for (int i = 0; i < pkMappings.length; i++)
{
mapping.addJavaTypeMapping(pkMappings[i]);
}
}
else
{
// Nothing to do for nondurable since no identity
}
idMapping = mapping;
}
/**
* Accessor for a mapping for the ID (persistable) for this table.
* @return The (persistable) ID mapping.
*/
public JavaTypeMapping getIdMapping()
{
return idMapping;
}
/**
* Accessor for all of the order mappings (used by FK Lists, Collections, Arrays)
* @return The mappings for the order columns.
*/
private Map getExternalOrderMappings()
{
if (externalOrderMappings == null)
{
externalOrderMappings = new HashMap<>();
}
return externalOrderMappings;
}
public boolean hasExternalFkMappings()
{
return externalFkMappings != null && externalFkMappings.size() > 0;
}
/**
* Accessor for all of the external FK mappings.
* @return The mappings for external FKs
*/
private Map getExternalFkMappings()
{
if (externalFkMappings == null)
{
externalFkMappings = new HashMap<>();
}
return externalFkMappings;
}
/**
* Accessor for an external mapping for the specified field of the required type.
* @param mmd MetaData for the field/property
* @param mappingType Type of mapping
* @return The (external) mapping
*/
public JavaTypeMapping getExternalMapping(AbstractMemberMetaData mmd, MappingType mappingType)
{
if (mappingType == MappingType.EXTERNAL_FK)
{
return getExternalFkMappings().get(mmd);
}
else if (mappingType == MappingType.EXTERNAL_FK_DISCRIMINATOR)
{
return getExternalFkDiscriminatorMappings().get(mmd);
}
else if (mappingType == MappingType.EXTERNAL_INDEX)
{
return getExternalOrderMappings().get(mmd);
}
else
{
return null;
}
}
/**
* Accessor for the MetaData for the (owner) field that an external mapping corresponds to.
* @param mapping The mapping
* @param mappingType The mapping type
* @return metadata for the external mapping
*/
public AbstractMemberMetaData getMetaDataForExternalMapping(JavaTypeMapping mapping, MappingType mappingType)
{
if (mappingType == MappingType.EXTERNAL_FK)
{
Set entries = getExternalFkMappings().entrySet();
Iterator iter = entries.iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
if (entry.getValue() == mapping)
{
return (AbstractMemberMetaData)entry.getKey();
}
}
}
else if (mappingType == MappingType.EXTERNAL_FK_DISCRIMINATOR)
{
Set entries = getExternalFkDiscriminatorMappings().entrySet();
Iterator iter = entries.iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
if (entry.getValue() == mapping)
{
return (AbstractMemberMetaData)entry.getKey();
}
}
}
else if (mappingType == MappingType.EXTERNAL_INDEX)
{
Set entries = getExternalOrderMappings().entrySet();
Iterator iter = entries.iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
if (entry.getValue() == mapping)
{
return (AbstractMemberMetaData)entry.getKey();
}
}
}
return null;
}
/**
* Accessor for all of the external FK discriminator mappings.
* @return The mappings for external FKs
*/
private Map getExternalFkDiscriminatorMappings()
{
if (externalFkDiscriminatorMappings == null)
{
externalFkDiscriminatorMappings = new HashMap<>();
}
return externalFkDiscriminatorMappings;
}
/**
* Accessor for the field mapping for the specified field.
* The field can be managed by a supertable of this table.
* @param mmd MetaData for this field/property
* @return the Mapping for the field/property
*/
public JavaTypeMapping getMemberMapping(AbstractMemberMetaData mmd)
{
if (mmd == null)
{
return null;
}
if (mmd instanceof PropertyMetaData && mmd.getAbstractClassMetaData() instanceof InterfaceMetaData)
{
// "Persistent Interfaces" don't do lookups correctly in here so just use the field name for now
// TODO Fix Persistent Interfaces lookup
return getMemberMapping(mmd.getName());
}
if (mmd.isPrimaryKey())
{
assertIsPKInitialized();
}
else
{
assertIsInitialized();
}
// Check if we manage this field
JavaTypeMapping m = memberMappingsMap.get(mmd);
if (m != null)
{
return m;
}
if (mmd.isPrimaryKey() && pkMappings != null)
{
// pkMappings aren't in memberMappingsMap when in subclasses
for (int i=0;i> memberMapIter = memberMappingsMap.entrySet().iterator();
while (memberMapIter.hasNext())
{
// If we have overridden this member then it may have a different AbstractMemberMetaData passed in (i.e the override)
Map.Entry entry = memberMapIter.next();
if (entry.getKey().getFullFieldName().equals(mmd.getFullFieldName()))
{
return entry.getValue();
}
}
// Check supertable
if (mmd.getAbsoluteFieldNumber() < cmd.getNoOfInheritedManagedMembers())
{
if (supertable != null)
{
m = supertable.getMemberMapping(mmd);
if (m != null)
{
return m;
}
}
}
// Check secondary tables
if (secondaryTables != null)
{
Collection secTables = secondaryTables.values();
for (SecondaryTable secTable : secTables)
{
m = secTable.getMemberMapping(mmd);
if (m != null)
{
return m;
}
}
}
return null;
}
/**
* Accessor for the mapping for the specified field only in this datastore class.
* @param mmd Metadata of the field/property
* @return The Mapping for the field/property (or null if not present here)
*/
public JavaTypeMapping getMemberMappingInDatastoreClass(AbstractMemberMetaData mmd)
{
if (mmd == null)
{
return null;
}
if (mmd instanceof PropertyMetaData && mmd.getAbstractClassMetaData() instanceof InterfaceMetaData)
{
// "Persistent Interfaces" don't do lookups correctly in here so just use the field name for now
// TODO Fix Persistent Interfaces lookup
return getMemberMapping(mmd.getName());
}
if (mmd.isPrimaryKey())
{
assertIsPKInitialized();
}
else
{
assertIsInitialized();
}
// Check if we manage this field
JavaTypeMapping m = memberMappingsMap.get(mmd);
if (m != null)
{
return m;
}
// Check if it is a PK field
if (pkMappings != null)
{
for (int i=0;i 0 ? omd.getColumnMetaData()[0] : null);
if (omd.getMappedBy() != null)
{
// User has defined ordering using the column(s) of an existing field.
state = TABLE_STATE_INITIALIZED; // Not adding anything so just set table back to "initialised"
JavaTypeMapping orderMapping = getMemberMapping(omd.getMappedBy());
if (!(orderMapping instanceof IntegerMapping) && !(orderMapping instanceof LongMapping))
{
throw new NucleusUserException(Localiser.msg("057022", mmd.getFullFieldName(), omd.getMappedBy()));
}
return orderMapping;
}
String colName = null;
if (omd.getColumnMetaData() != null && omd.getColumnMetaData().length > 0 && omd.getColumnMetaData()[0].getName() != null)
{
// User-defined name so create an identifier using it
colName = omd.getColumnMetaData()[0].getName();
indexColumnName = idFactory.newColumnIdentifier(colName);
}
}
if (indexColumnName == null)
{
// No name defined so generate one
indexColumnName = idFactory.newForeignKeyFieldIdentifier(mmd, null, null,
storeMgr.getNucleusContext().getTypeManager().isDefaultEmbeddedType(indexType), FieldRole.ROLE_INDEX);
}
Column column = addColumn(indexType.getName(), indexColumnName, orderIndexMapping, colmd);
if (colmd == null || (colmd.getAllowsNull() == null) || (colmd.getAllowsNull() != null && colmd.isAllowsNull()))
{
// User either wants it nullable, or havent specified anything, so make it nullable
column.setNullable(true);
}
storeMgr.getMappingManager().createColumnMapping(orderIndexMapping, column, indexType.getName());
return orderIndexMapping;
}
/**
* Provide the mappings to the consumer for all primary-key fields mapped to this table.
* @param consumer Consumer for the mappings
*/
@Override
public void providePrimaryKeyMappings(MappingConsumer consumer)
{
consumer.preConsumeMapping(highestMemberNumber + 1);
if (pkMappings != null)
{
// Application identity
int[] primaryKeyFieldNumbers = cmd.getPKMemberPositions();
for (int i=0;i iter = externalFkMappings.keySet().iterator();
while (iter.hasNext())
{
AbstractMemberMetaData fmd = iter.next();
JavaTypeMapping fieldMapping = externalFkMappings.get(fmd);
if (fieldMapping != null)
{
consumer.consumeMapping(fieldMapping, MappingType.EXTERNAL_FK);
}
}
}
else if (mappingType == MappingType.EXTERNAL_FK_DISCRIMINATOR && externalFkDiscriminatorMappings != null)
{
consumer.preConsumeMapping(highestMemberNumber + 1);
Iterator iter = externalFkDiscriminatorMappings.keySet().iterator();
while (iter.hasNext())
{
AbstractMemberMetaData fmd = iter.next();
JavaTypeMapping fieldMapping = externalFkDiscriminatorMappings.get(fmd);
if (fieldMapping != null)
{
consumer.consumeMapping(fieldMapping, MappingType.EXTERNAL_FK_DISCRIMINATOR);
}
}
}
else if (mappingType == MappingType.EXTERNAL_INDEX && externalOrderMappings != null)
{
consumer.preConsumeMapping(highestMemberNumber + 1);
Iterator iter = externalOrderMappings.keySet().iterator();
while (iter.hasNext())
{
AbstractMemberMetaData fmd = iter.next();
JavaTypeMapping fieldMapping = externalOrderMappings.get(fmd);
if (fieldMapping != null)
{
consumer.consumeMapping(fieldMapping, MappingType.EXTERNAL_INDEX);
}
}
}
}
/**
* Provide the mappings to the consumer for all absolute field Numbers in this table that are container in the fieldNumbers parameter.
* @param consumer Consumer for the mappings
* @param fieldMetaData MetaData for the fields to provide mappings for
* @param includeSecondaryTables Whether to provide fields in secondary tables
*/
@Override
public void provideMappingsForMembers(MappingConsumer consumer, AbstractMemberMetaData[] fieldMetaData, boolean includeSecondaryTables)
{
super.provideMappingsForMembers(consumer, fieldMetaData, true);
if (includeSecondaryTables && secondaryTables != null)
{
Collection secTables = secondaryTables.values();
Iterator iter = secTables.iterator();
while (iter.hasNext())
{
SecondaryTable secTable = (SecondaryTable)iter.next();
secTable.provideMappingsForMembers(consumer, fieldMetaData, false);
}
}
}
/**
* Method to provide all unmapped columns to the consumer.
* @param consumer Consumer of information
*/
@Override
public void provideUnmappedColumns(MappingConsumer consumer)
{
if (unmappedColumns != null)
{
Iterator iter = unmappedColumns.iterator();
while (iter.hasNext())
{
consumer.consumeUnmappedColumn(iter.next());
}
}
}
/**
* Method to validate the constraints of this table.
* @param conn Connection to use in validation
* @param autoCreate Whether to auto create the constraints
* @param autoCreateErrors Whether to log a warning only on errors during "auto create"
* @param clr The ClassLoaderResolver
* @return Whether the DB was modified
* @throws SQLException Thrown when an error occurs in validation
*/
@Override
public boolean validateConstraints(Connection conn, boolean autoCreate, Collection autoCreateErrors, ClassLoaderResolver clr)
throws SQLException
{
boolean modified = false;
if (super.validateConstraints(conn, autoCreate, autoCreateErrors, clr))
{
modified = true;
}
// Validate our secondary tables since we manage them
if (secondaryTables != null)
{
Collection secTables = secondaryTables.values();
Iterator iter = secTables.iterator();
while (iter.hasNext())
{
SecondaryTable secTable = iter.next();
if (secTable.validateConstraints(conn, autoCreate, autoCreateErrors, clr))
{
modified = true;
}
}
}
return modified;
}
}