/**********************************************************************
Copyright (c) 2004 Erik Bengtson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
2004 Andy Jefferson - Updated to use CorrespondentColumnsMapping to match up column specs.
2004 Andy Jefferson - changed class.forName to use ClassLoaderResolver.classForName
2005 Andy Jefferson - fix for use of "subclass-table" owners.
...
**********************************************************************/
package org.jpox.store.rdbms.table;
import org.jpox.ClassLoaderResolver;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.ColumnMetaData;
import org.jpox.metadata.ColumnMetaDataContainer;
import org.jpox.metadata.FieldRole;
import org.jpox.metadata.InheritanceStrategy;
import org.jpox.metadata.MetaDataUtils;
import org.jpox.store.exceptions.NoTableManagedException;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreField;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.mapping.CorrespondentColumnsMapper;
import org.jpox.store.mapped.mapping.InterfaceMapping;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.PersistenceCapableMapping;
import org.jpox.store.mapped.mapping.ReferenceMapping;
import org.jpox.store.mapped.mapping.SerialisedPCMapping;
import org.jpox.store.mapped.mapping.SerialisedReferenceMapping;
import org.jpox.store.rdbms.Column;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.store.rdbms.exceptions.DuplicateColumnNameException;
import org.jpox.store.rdbms.sqlidentifier.RDBMSIdentifierFactory;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
/**
* Helper class to create columns.
* Used for generating columns for reference (Object, interface) fields, and for join
* table fields.
*
* @version $Revision: 1.93 $
*/
public final class ColumnCreator
{
/** Localiser for messages. */
protected static final Localiser LOCALISER = Localiser.getInstance("org.jpox.store.rdbms.Localisation",
RDBMSManager.class.getClassLoader());
/**
* Private constructor to prevent instantiation.
*/
private ColumnCreator()
{
// private constructor
}
/**
* Convenience method to add the column for an index mapping.
* @param mapping The mapping
* @param storeMgr Manager for the store
* @param clr ClassLoaderResolver
* @param table Table where we create the column
* @param colmd The column MetaData
* @param pk Whether this column is (part of) the PK.
* @return The added column
*/
public static DatastoreField createIndexColumn(JavaTypeMapping mapping,
MappedStoreManager storeMgr,
ClassLoaderResolver clr,
DatastoreContainerObject table,
ColumnMetaData colmd,
boolean pk)
{
DatastoreIdentifier identifier = null;
if (colmd != null && colmd.getName() != null)
{
// User defined name
identifier = storeMgr.getIdentifierFactory().newDatastoreFieldIdentifier(colmd.getName());
}
else
{
// No name so generate one
identifier = ((RDBMSIdentifierFactory)storeMgr.getIdentifierFactory()).newAdapterIndexFieldIdentifier();
}
DatastoreField column = table.addDatastoreField(mapping.getType(), identifier, mapping, colmd);
storeMgr.getMappingManager().createDatastoreMapping(mapping, storeMgr, column, mapping.getJavaType().getName());
if (pk)
{
column.setAsPrimaryKey();
}
return column;
}
/**
* Method to create the required columns (and mapping if necessary) for a field
* @param javaType The java type of the field
* @param fmd Metadata for the field
* @param columnMetaData MetaData defining the columns
* @param storeMgr Store Manager
* @param table The table to add the mapping to
* @param primaryKey Whether this field is the PK
* @param nullable Whether this field is to be nullable
* @param serialised Whether the field is serialised
* @param embedded Whether the field is embedded
* @param fieldRole The role of this field (if any)
* @param clr ClassLoader resolver
* @return The java type mapping for this field
*/
public static JavaTypeMapping createColumnsForJoinTables(Class javaType,
AbstractMemberMetaData fmd,
ColumnMetaData[] columnMetaData,
MappedStoreManager storeMgr,
DatastoreContainerObject table,
boolean primaryKey,
boolean nullable,
boolean serialised,
boolean embedded,
int fieldRole,
ClassLoaderResolver clr)
{
return createColumnsForField(javaType, null, table, storeMgr, fmd, primaryKey, nullable, serialised, embedded,
fieldRole, columnMetaData, clr, false);
}
/**
* Create columns for a field that uses subclass-table inheritance and where
* we want to have a FK for each subclass with its own table.
* @param mapping the mapping for the field
* @param table the Table which will hold the columns
* @param fmd MetaData for the field
* @param clr The ClassLoaderResolver
*/
public static void createColumnsForFieldUsingSubclassTable(JavaTypeMapping mapping,
DatastoreContainerObject table,
AbstractMemberMetaData fmd,
ClassLoaderResolver clr)
{
MappedStoreManager storeMgr = table.getStoreManager();
AbstractClassMetaData refCmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(fmd.getType(), clr);
if (refCmd.getInheritanceMetaData().getStrategyValue() != InheritanceStrategy.SUBCLASS_TABLE)
{
throw new JPOXUserException(LOCALISER.msg("020185", fmd.getFullFieldName()));
}
AbstractClassMetaData[] subclassCmds = storeMgr.getClassesManagingTableForClass(refCmd, clr);
boolean pk = false;
if (subclassCmds.length > 1)
{
pk = false;
}
boolean nullable = true;
if (subclassCmds.length > 1)
{
nullable = true;
}
int colPos = 0;
for (int i=0; i<subclassCmds.length; i++)
{
Class type = clr.classForName(subclassCmds[i].getFullClassName());
DatastoreClass dc = storeMgr.getDatastoreClass(subclassCmds[i].getFullClassName(), clr);
JavaTypeMapping m = dc.getIDMapping();
// obtain the column names for this type
// TODO Fix this. The <column> elements have no way of being ordered to match the subclasses
ColumnMetaData[] columnMetaDataForType = null;
if (fmd.getColumnMetaData() != null && fmd.getColumnMetaData().length > 0)
{
if (fmd.getColumnMetaData().length < colPos+m.getNumberOfDatastoreFields())
{
throw new JPOXUserException(LOCALISER.msg("020186",
fmd.getFullFieldName(), "" + fmd.getColumnMetaData().length,
"" + (colPos + m.getNumberOfDatastoreFields())));
}
columnMetaDataForType = new ColumnMetaData[m.getNumberOfDatastoreFields()];
System.arraycopy(fmd.getColumnMetaData(), colPos, columnMetaDataForType, 0, columnMetaDataForType.length);
colPos += columnMetaDataForType.length;
}
// Create the FK columns for this subclass
createColumnsForField(type, mapping, table, storeMgr, fmd, pk, nullable, false, false, FieldRole.ROLE_FIELD,
columnMetaDataForType, clr, true);
if (JPOXLogger.DATASTORE.isInfoEnabled())
{
JPOXLogger.DATASTORE.info(LOCALISER.msg("020187", type, fmd.getName()));
}
}
}
/**
* Create columns for reference (Object/interface) fields.
* @param mapping the mapping for the field
* @param table the Table which will hold the columns
* @param fmd MetaData for the field
* @param clr The ClassLoaderResolver
* @param embedded Whether the field is embedded
*/
public static void createColumnsForFieldUsingReference(JavaTypeMapping mapping,
DatastoreContainerObject table,
AbstractMemberMetaData fmd,
ClassLoaderResolver clr,
boolean embedded)
{
createColumnsForReferenceField(mapping, table, fmd, false, true, false, embedded, FieldRole.ROLE_FIELD,
fmd.getColumnMetaData(), clr);
}
/**
* Create columns for reference (Interface/Object) fields.
* This call createColumnsForField for each implementation class of the reference.
* @param mapping the InterfaceMapping/ObjectMapping
* @param table the Table which will hold the columns
* @param fmd the Field
*/
private static void createColumnsForReferenceField(
JavaTypeMapping mapping,
DatastoreContainerObject table,
AbstractMemberMetaData fmd,
boolean isPrimaryKey,
boolean isNullable,
boolean serialised,
boolean embedded,
int fieldRole,
ColumnMetaData[] columnMetaData,
ClassLoaderResolver clr)
{
String[] implTypes = MetaDataUtils.getInstance().getImplementationNamesForReferenceField(fmd, fieldRole, clr);
// Extract the type of the interface/Object field
String fieldTypeName = fmd.getTypeName();
if (fmd.getFieldTypes() != null && fmd.getFieldTypes().length == 1)
{
// "field-type" specified
fieldTypeName = fmd.getFieldTypes()[0];
}
if (fmd.hasCollection())
{
fieldTypeName = fmd.getCollection().getElementType();
}
else if (fmd.hasArray())
{
fieldTypeName = fmd.getArray().getElementType();
}
else if (fmd.hasMap())
{
if (fieldRole == FieldRole.ROLE_MAP_KEY)
{
fieldTypeName = fmd.getMap().getKeyType();
}
else if (fieldRole == FieldRole.ROLE_MAP_VALUE)
{
fieldTypeName = fmd.getMap().getValueType();
}
}
MappedStoreManager storeMgr = table.getStoreManager();
boolean isPersistentInterfaceField = storeMgr.getOMFContext().getMetaDataManager().isPersistentInterface(fieldTypeName);
// Set the PK and nullability of column(s) for the implementations (based on the number of impls etc)
boolean pk = isPrimaryKey;
boolean nullable = isNullable;
if (implTypes.length > 1)
{
pk = false; // Cannot be part of PK if more than 1 implementation
}
if (implTypes.length > 1 && !pk)
{
nullable = true; // Must be nullable if more than 1 impl (since only 1 impl can have value at a time)
}
int colPos = 0;
Class[] implTypeClasses = new Class[implTypes.length];
for (int i=0; i<implTypes.length; i++)
{
String implName = implTypes[i];
Class type = clr.classForName(implName);
if (type == null)
{
throw new JPOXUserException(LOCALISER.msg("020189",
fmd.getTypeName(), implName));
}
else if (type.isInterface())
{
throw new JPOXUserException(LOCALISER.msg("020190",
fmd.getFullFieldName(), fmd.getTypeName(), implName));
}
implTypeClasses[i] = type;
// Check if this implementation needs columns (or if its inheritance tree is already catered for in previous impls)
boolean columnsNeeded = true;
for (int j=0;j<i;j++)
{
if (type.isAssignableFrom(implTypeClasses[j]) || implTypeClasses[j].isAssignableFrom(type))
{
// This implementation is part of the same inheritance tree as another implementation already processed
// so we already have its FK present
columnsNeeded = false;
break;
}
}
if (isPersistentInterfaceField &&
!storeMgr.getOMFContext().getMetaDataManager().isPersistentInterfaceImplementation(fieldTypeName, implName))
{
// We have a "persistent-interface" field yet this is not a JPOX-generated implementation so ignore it
// It is arguable if we should allow the real implementations of this interface here, but the JDO2 TCK doesn't
// make that assumption so we don't either
columnsNeeded = false;
}
if (columnsNeeded)
{
// Get the mapping for this implementation
JavaTypeMapping m;
if (storeMgr.getOMFContext().getTypeManager().isSupportedType(type.getName()))
{
m = storeMgr.getDatastoreAdapter().getMapping(type, storeMgr, serialised, embedded,
fmd.getFullFieldName());
}
else
{
try
{
DatastoreClass dc = storeMgr.getDatastoreClass(type.getName(), clr);
m = dc.getIDMapping();
}
catch (NoTableManagedException ex)
{
// TODO Localise this message
throw new JPOXUserException("Cannot define columns for " + fmd.getFullFieldName() + " due to " + ex.getMessage(), ex);
}
}
ColumnMetaData[] columnMetaDataForType = null;
if (columnMetaData != null && columnMetaData.length > 0)
{
if (columnMetaData.length < colPos+m.getNumberOfDatastoreFields())
{
throw new JPOXUserException(LOCALISER.msg("020186",
fmd.getFullFieldName(), "" + columnMetaData.length,
"" + (colPos + m.getNumberOfDatastoreFields())));
}
columnMetaDataForType = new ColumnMetaData[m.getNumberOfDatastoreFields()];
System.arraycopy(columnMetaData, colPos, columnMetaDataForType, 0, columnMetaDataForType.length);
colPos += columnMetaDataForType.length;
}
// Create the FK column(s) for this implementation
createColumnsForField(type, mapping, table, storeMgr, fmd, pk, nullable, serialised, embedded,
fieldRole, columnMetaDataForType, clr, true);
if( JPOXLogger.DATASTORE.isInfoEnabled() )
{
JPOXLogger.DATASTORE.info(LOCALISER.msg("020188", type,
fmd.getName()));
}
}
}
}
/**
* Method to create the column(s) for a field in either a join table or for a reference field.
* @param javaType The java type of the field being stored
* @param mapping The JavaTypeMapping (if existing, otherwise created and returned by this method)
* @param table The table to insert the columns into (join table, or primary table (if ref field))
* @param storeMgr Manager for the store
* @param fmd MetaData for the field (or null if a collection field)
* @param isPrimaryKey Whether to create the columns as part of the PK
* @param isNullable Whether the columns should be nullable
* @param serialised Whether the field is serialised
* @param embedded Whether the field is embedded
* @param fieldRole The role of the field (when part of a join table)
* @param columnMetaData MetaData for the column(s)
* @param clr ClassLoader resolver
* @param isReferenceField Whether this field is part of a reference field
* @return The JavaTypeMapping for the table
*/
private static JavaTypeMapping createColumnsForField(
Class javaType,
JavaTypeMapping mapping,
DatastoreContainerObject table,
MappedStoreManager storeMgr,
AbstractMemberMetaData fmd,
boolean isPrimaryKey,
boolean isNullable,
boolean serialised,
boolean embedded,
int fieldRole,
ColumnMetaData[] columnMetaData,
ClassLoaderResolver clr,
boolean isReferenceField)
{
if (mapping == null)
{
// No mapping so create it for the passed java type
mapping = storeMgr.getDatastoreAdapter().getMapping(javaType, storeMgr, serialised,
embedded, fmd != null ? fmd.getFullFieldName() : null);
}
if (mapping instanceof InterfaceMapping && fmd != null && fmd.hasExtension("implementation-classes"))
{
// Store the implementation-classes with the mapping
((InterfaceMapping) mapping).setImplementationClasses(fmd.getValueForExtension("implementation-classes"));
}
IdentifierFactory idFactory = storeMgr.getIdentifierFactory();
if (storeMgr.getOMFContext().getTypeManager().isReferenceType(javaType) && !serialised && !embedded)
{
// Reference mapping
// Create column(s) for the reference field (typically one column per implementation type)
createColumnsForReferenceField(mapping, table, fmd, isPrimaryKey, isNullable, serialised, embedded,
fieldRole, columnMetaData, clr);
}
else if (mapping instanceof ReferenceMapping ||
mapping instanceof SerialisedPCMapping ||
mapping instanceof SerialisedReferenceMapping ||
mapping instanceof PersistenceCapableMapping)
/*else if (!storeMgr.getOMFContext().getTypeManager().isSupportedType(javaType.getName()))*/
{
// PC mapping
JavaTypeMapping container = mapping;
if (mapping instanceof ReferenceMapping)
{
// Interface/Object has child mappings for each implementation
container = storeMgr.getDatastoreAdapter().getMapping(javaType, storeMgr, serialised, embedded, fmd != null ? fmd.getFullFieldName() : null);
((ReferenceMapping) mapping).addJavaTypeMapping(container);
}
if (container instanceof SerialisedPCMapping || container instanceof SerialisedReferenceMapping)
{
// Serialised column will be added by mapping constructor, so just return it
return mapping;
}
// Get the table that we want our column to be a FK to
// This could be the owner table, element table, key table, value table etc
DatastoreClass destinationTable = storeMgr.getDatastoreClass(javaType.getName(), clr);
if (destinationTable == null)
{
// Maybe the owner hasn't got its own table (e.g "subclass-table" inheritance strategy)
AbstractClassMetaData ownerCmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(javaType, clr);
AbstractClassMetaData[] ownerCmds = storeMgr.getClassesManagingTableForClass(ownerCmd, clr);
if (ownerCmds == null || ownerCmds.length == 0)
{
throw new JPOXUserException(LOCALISER.msg("057023", javaType.getName())).setFatal();
}
// Use the first one since they should all have the same id column(s)
destinationTable = storeMgr.getDatastoreClass(ownerCmds[0].getFullClassName(), clr);
}
if (destinationTable != null)
{
// Foreign-Key to the destination table
JavaTypeMapping m = destinationTable.getIDMapping();
// For each datastore mapping, add a column.
ColumnMetaDataContainer columnContainer = null;
if (columnMetaData != null && columnMetaData.length > 0)
{
columnContainer = (ColumnMetaDataContainer)columnMetaData[0].getParent();
}
CorrespondentColumnsMapper correspondentColumnsMapping =
new CorrespondentColumnsMapper(columnContainer, columnMetaData, m, true);
for (int i=0; i<m.getNumberOfDatastoreFields(); i++)
{
JavaTypeMapping refDatastoreMapping =
storeMgr.getDatastoreAdapter().getMapping(m.getDataStoreMapping(i).getJavaTypeMapping().getJavaType(), storeMgr);
ColumnMetaData colmd = correspondentColumnsMapping.getColumnMetaDataByIdentifier(
m.getDataStoreMapping(i).getDatastoreField().getIdentifier());
try
{
DatastoreIdentifier identifier = null;
if (colmd.getName() == null)
{
// User hasn't provided a name, so we use default naming
if (isReferenceField)
{
// Create reference identifier
identifier = idFactory.newReferenceFieldIdentifier(fmd,
storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(javaType, clr),
m.getDataStoreMapping(i).getDatastoreField().getIdentifier(),
storeMgr.getOMFContext().getTypeManager().isDefaultEmbeddedType(javaType), fieldRole);
}
else
{
// Create join table identifier (FK using destination table identifier)
AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
// TODO Cater for more than 1 related field
identifier = ((RDBMSIdentifierFactory)idFactory).newJoinTableFieldIdentifier(fmd,
relatedMmds != null ? relatedMmds[0] : null,
m.getDataStoreMapping(i).getDatastoreField().getIdentifier(),
storeMgr.getOMFContext().getTypeManager().isDefaultEmbeddedType(javaType), fieldRole);
}
}
else
{
// User defined name, so we use that.
identifier = idFactory.newDatastoreFieldIdentifier(colmd.getName());
}
DatastoreField column = table.addDatastoreField(javaType.getName(), identifier, refDatastoreMapping, colmd);
((Column) m.getDataStoreMapping(i).getDatastoreField()).copyConfigurationTo(column);
if (isPrimaryKey)
{
column.setAsPrimaryKey();
}
if (isNullable)
{
column.setNullable();
}
storeMgr.getMappingManager().createDatastoreMapping(refDatastoreMapping, storeMgr, column,
m.getDataStoreMapping(i).getJavaTypeMapping().getJavaTypeForDatastoreMapping(i));
}
catch (DuplicateColumnNameException ex)
{
throw new JPOXUserException("Cannot create column for field "+fmd.getFullFieldName()+" column metadata "+colmd,ex);
}
((PersistenceCapableMapping) container).addJavaTypeMapping(refDatastoreMapping);
}
}
}
else
{
// Non-PC mapping
// Add column for the field
DatastoreField column = null;
ColumnMetaData colmd = null;
if (columnMetaData != null && columnMetaData.length > 0)
{
colmd = columnMetaData[0];
}
DatastoreIdentifier identifier = null;
if (colmd != null && colmd.getName() != null)
{
// User specified name
identifier = idFactory.newDatastoreFieldIdentifier(colmd.getName());
}
else
{
// No user-supplied name so generate one
identifier = ((RDBMSIdentifierFactory)idFactory).newJoinTableFieldIdentifier(fmd, null, null,
storeMgr.getOMFContext().getTypeManager().isDefaultEmbeddedType(javaType), fieldRole);
}
column = table.addDatastoreField(javaType.getName(), identifier, mapping, colmd);
storeMgr.getMappingManager().createDatastoreMapping(mapping, storeMgr, column,
mapping.getJavaTypeForDatastoreMapping(0));
if (isNullable)
{
column.setNullable();
}
}
return mapping;
}
}