/**********************************************************************
Copyright (c) 2002 Mike Martin (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 Erik Bengtson - the fields to fetch are better managed for application
identity. Their selection were moved from execute
method to the constructor
2003 Andy Jefferson - coding standards
2004 Andy Jefferson - conversion to use Logger
2004 Erik Bengtson - changed to use mapping consumer
2004 Andy Jefferson - added discriminator support
2005 Andy Jefferson - fixed 1-1 bidir order of insertion
...
**********************************************************************/
package org.jpox.store.rdbms.request;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jpox.ClassLoaderResolver;
import org.jpox.ManagedConnection;
import org.jpox.ObjectManager;
import org.jpox.StateManager;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.ColumnMetaData;
import org.jpox.metadata.DiscriminatorMetaData;
import org.jpox.metadata.DiscriminatorStrategy;
import org.jpox.metadata.IdentityType;
import org.jpox.metadata.Relation;
import org.jpox.metadata.VersionMetaData;
import org.jpox.state.ActivityState;
import org.jpox.store.exceptions.NotYetFlushedException;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreField;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.StatementExpressionIndex;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.MappingCallbacks;
import org.jpox.store.mapped.mapping.MappingConsumer;
import org.jpox.store.mapped.mapping.PersistenceCapableMapping;
import org.jpox.store.mapped.mapping.ReferenceMapping;
import org.jpox.store.mapped.mapping.VersionMapping;
import org.jpox.store.rdbms.Column;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.store.rdbms.SQLController;
import org.jpox.store.rdbms.adapter.RDBMSAdapter;
import org.jpox.store.rdbms.fieldmanager.ParameterSetter;
import org.jpox.store.rdbms.mapping.RDBMSMapping;
import org.jpox.store.rdbms.table.SecondaryTable;
import org.jpox.store.rdbms.table.Table;
import org.jpox.util.JPOXLogger;
import org.jpox.util.JavaUtils;
import org.jpox.util.StringUtils;
/**
* Class to provide a means of insertion of records to a data store.
* Extends basic request class implementing the execute method to do a JDBC
* insert operation.
* <p>
* When inserting an object with inheritance this will involve 1 InsertRequest
* for each table involved. So if we have a class B that extends class A and they
* both use "new-table" inheritance strategy, we will have 2 InsertRequests, one for
* table A, and one for table B. When the InsertRequest starts to populate its statement
* and it has a PC field, this calls PersistenceCapableMapping.setObject(). This then
* checks if the other PC object is yet persistent and, if not, will persist it
* before processing this objects INSERT. This forms the key to
* "persistence-by-reachability".
* </p>
*
* @version $Revision: 1.94 $
**/
public class InsertRequest extends Request
{
private static final int IDPARAMNUMBER = 1;
private final MappingCallbacks[] callbacks;
/** Numbers of fields in the INSERT statement (excluding PK). */
private final int[] insertFieldNumbers;
/** Numbers of Primary key fields. */
private final int[] pkFieldNumbers;
/** Numbers of fields that are reachable yet have no datastore column in this table. Used for reachability. */
private final int[] reachableFieldNumbers;
/** Numbers of fields that are relations that may be detached when persisting but not bidir so cant attach yet. */
private final int[] relationFieldNumbers;
/** SQL statement for the INSERT. */
private final String insertStmt;
/** Whether the class has an auto-increment column */
private boolean hasAutoIncrementColumn = false;
/** one StatementExpressionIndex for each field **/
private StatementExpressionIndex[] statementExpressionIndex;
/** StatementExpressionIndex for fields to be "retrieved" **/
private StatementExpressionIndex[] retrievedStatementExpressionIndex;
/** StatementExpressionIndex for version **/
private StatementExpressionIndex versionStatementExpressionIndex;
/** StatementExpressionIndex for discriminator **/
private StatementExpressionIndex discriminatorStatementExpressionIndex;
/** StatementExpressionIndex for external FKs */
private StatementExpressionIndex[] externalFKStatementExpressionIndex;
/** StatementExpressionIndex for external FK discriminators (shared FKs) */
private StatementExpressionIndex[] externalFKDiscrimStatementExpressionIndex;
/** StatementExpressionIndex for external indices */
private StatementExpressionIndex[] externalOrderStatementExpressionIndex;
/** Whether to batch the INSERT SQL. */
private boolean batch = false;
/**
* Constructor, taking the table. Uses the structure of the datastore table to build a basic query.
* @param table The Class Table representing the datastore table to insert.
* @param cls Class of objects being updated
* @param clr ClassLoader resolver
**/
public InsertRequest(DatastoreClass table, Class cls, ClassLoaderResolver clr)
{
super(table);
AbstractClassMetaData cmd = table.getStoreManager().getOMFContext().getMetaDataManager().getMetaDataForClass(cls, clr);
InsertMappingConsumer consumer = new InsertMappingConsumer(clr, cmd, IDPARAMNUMBER);
table.provideDatastoreIdMappings(consumer);
table.provideNonPrimaryKeyMappings(consumer);
table.providePrimaryKeyMappings(consumer);
table.provideVersionMappings(consumer);
table.provideDiscriminatorMappings(consumer);
table.provideExternalMappings(consumer, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
table.provideExternalMappings(consumer, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK_DISCRIM);
table.provideExternalMappings(consumer, MappingConsumer.MAPPING_TYPE_EXTERNAL_INDEX);
table.provideUnmappedDatastoreFields(consumer);
callbacks = (MappingCallbacks[])consumer.getMappingCallbacks().toArray(new MappingCallbacks[consumer.getMappingCallbacks().size()]);
statementExpressionIndex = consumer.getStatementExpressionIndex();
versionStatementExpressionIndex = consumer.getVersionStatementExpressionIndex();
discriminatorStatementExpressionIndex = consumer.getDiscriminatorStatementExpressionIndex();
externalFKStatementExpressionIndex = consumer.getExternalFKStatementExpressionIndex();
externalFKDiscrimStatementExpressionIndex = consumer.getExternalFKDiscrimStatementExpressionIndex();
externalOrderStatementExpressionIndex = consumer.getExternalOrderStatementExpressionIndex();
pkFieldNumbers = consumer.getPrimaryKeyFieldNumbers();
if (table.getIdentityType() == IdentityType.APPLICATION && pkFieldNumbers.length < 1 && !hasAutoIncrementColumn)
{
throw new JPOXException(LOCALISER.msg("052200", cmd.getFullClassName())).setFatal();
}
insertFieldNumbers = consumer.getInsertFieldNumbers();
retrievedStatementExpressionIndex = consumer.getReachableStatementIndex();
reachableFieldNumbers = consumer.getReachableFieldNumbers();
relationFieldNumbers = consumer.getRelationFieldNumbers();
insertStmt = consumer.getInsertStmt();
/*if (!hasAutoIncrementColumn && !cmd.hasRelations(clr) && externalFKStatementExpressionIndex == null)
{
// No auto-increment, no persistence-by-reachability and no external FKs so should be safe to batch this
batch = true;
}*/
}
/**
* Method performing the insertion of the record from the datastore.
* Takes the constructed insert query and populates with the specific record information.
* @param sm The state manager for the record to be inserted
*/
public void execute(StateManager sm)
{
if (JPOXLogger.PERSISTENCE.isDebugEnabled())
{
// Debug information about what we are inserting
JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("052207", StringUtils.toJVMIDString(sm.getObject()), table));
}
try
{
VersionMetaData vermd = table.getVersionMetaData();
if (vermd != null && vermd.getFieldName() != null)
{
// Version field - Update the version in the object
AbstractMemberMetaData verfmd =
((AbstractClassMetaData)vermd.getParent()).getMetaDataForMember(vermd.getFieldName());
Object currentVersion = sm.getObjectManager().getApiAdapter().getVersion(sm);
if (currentVersion instanceof Integer)
{
// Cater for Integer based versions TODO Generalise this
currentVersion = new Long(((Integer)currentVersion).longValue());
}
Object nextOptimisticVersion =
VersionMapping.getNextVersion(table.getVersionMetaData(), currentVersion);
if (verfmd.getType() == Integer.class || verfmd.getType() == int.class)
{
// Cater for Integer based versions TODO Generalise this
nextOptimisticVersion = new Integer(((Long)nextOptimisticVersion).intValue());
}
sm.replaceField(verfmd.getAbsoluteFieldNumber(), nextOptimisticVersion, false);
}
// Set the state to "inserting" (may already be at this state if multiple inheritance level INSERT)
sm.changeActivityState(ActivityState.INSERTING, table);
ObjectManager om = sm.getObjectManager();
RDBMSManager storeMgr = (RDBMSManager)om.getStoreManager();
SQLController sqlControl = storeMgr.getSQLController();
ManagedConnection mconn = storeMgr.getConnection(om);
try
{
PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, insertStmt, batch);
try
{
// Provide the primary key field(s)
if (table.getIdentityType() == IdentityType.DATASTORE)
{
if (!table.isObjectIDDatastoreAttributed() || !table.isBaseDatastoreClass())
{
int[] paramNumber = {IDPARAMNUMBER};
table.getDataStoreObjectIdMapping().setObject(om, ps, paramNumber, sm.getInternalObjectId());
}
}
else if (table.getIdentityType() == IdentityType.APPLICATION)
{
sm.provideFields(pkFieldNumbers, new ParameterSetter(sm, ps, statementExpressionIndex, true));
}
// Provide all non-key fields needed for the insert.
// This provides "persistence-by-reachability" for these fields
int numberOfFieldsToProvide = 0;
for (int i = 0; i < insertFieldNumbers.length; i++)
{
if (insertFieldNumbers[i] < sm.getHighestFieldNumber())
{
numberOfFieldsToProvide++;
}
}
int j = 0;
int[] classFieldNumbers = new int[numberOfFieldsToProvide];
for (int i = 0; i < insertFieldNumbers.length; i++)
{
if (insertFieldNumbers[i] < sm.getHighestFieldNumber())
{
classFieldNumbers[j++] = insertFieldNumbers[i];
}
else
{
// Any fields out of range for the class, we default or null.
// This happens when we insert a base class object
// and the sub-class is persisted to the same table ("superclass-table").
StatementExpressionIndex stmtExprIndex = statementExpressionIndex[insertFieldNumbers[i]];
stmtExprIndex.getMapping().setDefault(om, ps, stmtExprIndex.getParameterIndex());
}
}
sm.provideFields(classFieldNumbers, new ParameterSetter(sm, ps, statementExpressionIndex, true));
if (table.getVersionMapping(false) != null)
{
// Surrogate version - set the new version for the object
Object currentVersion = sm.getObjectManager().getApiAdapter().getVersion(sm);
Object nextOptimisticVersion = VersionMapping.getNextVersion(table.getVersionMetaData(),
currentVersion);
table.getVersionMapping(false).setObject(om, ps, versionStatementExpressionIndex.getParameterIndex(), nextOptimisticVersion);
sm.setTransactionalVersion(nextOptimisticVersion);
}
else if (vermd != null && vermd.getFieldName() != null)
{
// Version field - set the new version for the object
Object currentVersion = sm.getObjectManager().getApiAdapter().getVersion(sm);
Object nextOptimisticVersion = VersionMapping.getNextVersion(table.getVersionMetaData(),
currentVersion);
sm.setTransactionalVersion(nextOptimisticVersion);
}
// Discriminator mapping (optional)
if (table.getDiscriminatorMapping(false) != null)
{
DiscriminatorMetaData dismd = table.getDiscriminatorMetaData();
if (dismd.getStrategy() == DiscriminatorStrategy.CLASS_NAME)
{
table.getDiscriminatorMapping(false).setObject(om, ps, discriminatorStatementExpressionIndex.getParameterIndex(),
sm.getObject().getClass().getName());
}
else if (dismd.getStrategy() == DiscriminatorStrategy.VALUE_MAP)
{
// Use Discriminator info for the actual class
dismd = sm.getClassMetaData().getInheritanceMetaData().getDiscriminatorMetaData();
table.getDiscriminatorMapping(false).setObject(om, ps, discriminatorStatementExpressionIndex.getParameterIndex(), dismd.getValue());
}
}
// External FK columns (optional)
if (externalFKStatementExpressionIndex != null)
{
for (int i=0;i<externalFKStatementExpressionIndex.length;i++)
{
Object fkValue = sm.getValueForExternalField(externalFKStatementExpressionIndex[i].getMapping());
if (fkValue != null)
{
// Need to provide the owner field number so PCMapping can work out if it is inserted yet
AbstractMemberMetaData ownerFmd =
table.getMetaDataForExternalMapping(externalFKStatementExpressionIndex[i].getMapping(),
MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
externalFKStatementExpressionIndex[i].getMapping().setObject(om, ps,
externalFKStatementExpressionIndex[i].getParameterIndex(), fkValue, null, ownerFmd.getAbsoluteFieldNumber());
}
else
{
// We're inserting a null so dont need the owner field
externalFKStatementExpressionIndex[i].getMapping().setObject(om, ps,
externalFKStatementExpressionIndex[i].getParameterIndex(), null);
}
}
}
// External FK discriminator columns (optional)
if (externalFKDiscrimStatementExpressionIndex != null)
{
for (int i=0;i<externalFKDiscrimStatementExpressionIndex.length;i++)
{
Object discrimValue = sm.getValueForExternalField(externalFKDiscrimStatementExpressionIndex[i].getMapping());
externalFKDiscrimStatementExpressionIndex[i].getMapping().setObject(om, ps,
externalFKDiscrimStatementExpressionIndex[i].getParameterIndex(), discrimValue);
}
}
// External order columns (optional)
if (externalOrderStatementExpressionIndex != null)
{
for (int i=0;i<externalOrderStatementExpressionIndex.length;i++)
{
Object orderValue = sm.getValueForExternalField(externalOrderStatementExpressionIndex[i].getMapping());
if (orderValue == null)
{
// No order value so use -1
orderValue = new Integer(-1);
}
externalOrderStatementExpressionIndex[i].getMapping().setObject(om, ps,
externalOrderStatementExpressionIndex[i].getParameterIndex(), orderValue);
}
}
sqlControl.executeStatementUpdate(mconn, insertStmt, ps, !batch);
if (hasAutoIncrementColumn)
{
// Identity was set in the datastore using auto-increment/identity/serial etc
Object newId = getInsertedDatastoreIdentity(om, sqlControl, sm, mconn, ps);
if (JPOXLogger.DATASTORE_PERSIST.isDebugEnabled())
{
JPOXLogger.DATASTORE_PERSIST.debug(LOCALISER.msg("052206",
StringUtils.toJVMIDString(sm.getObject()), newId));
}
sm.setPostStoreNewObjectId(newId);
}
// Update the insert status for this table
sm.changeActivityState(ActivityState.INSERTING_CALLBACKS, table);
// Make sure all relation fields (1-1, N-1 with FK) we processed in the INSERT are attached.
// This is necessary because with a bidir relation and the other end attached we can just
// do the INSERT above first and THEN attach the other end here
// (if we did it the other way around we would get a NotYetFlushedException thrown above).
for (int i=0;i<relationFieldNumbers.length;i++)
{
Object value = sm.provideField(relationFieldNumbers[i]);
if (value != null && om.getApiAdapter().isDetached(value))
{
Object valueAttached = om.persistObjectInternal(value, null, null, -1, StateManager.PC);
sm.replaceField(relationFieldNumbers[i], valueAttached, false);
}
}
// Perform reachability on all fields that have no datastore column (1-1 bi non-owner, N-1 bi join)
int numberOfReachableFields = 0;
for (int i = 0; i < reachableFieldNumbers.length; i++)
{
if (reachableFieldNumbers[i] < sm.getHighestFieldNumber())
{
numberOfReachableFields++;
}
}
classFieldNumbers = new int[numberOfReachableFields];
j = 0;
for (int i = 0; i < reachableFieldNumbers.length; i++)
{
if (reachableFieldNumbers[i] < sm.getHighestFieldNumber())
{
classFieldNumbers[j++] = reachableFieldNumbers[i];
}
else
{
// Any fields out of range for the class, we default or null.
// This happens when we insert a base class object
// and the sub-class is persisted to the same table ("superclass-table").
StatementExpressionIndex stmtExprIndex = retrievedStatementExpressionIndex[reachableFieldNumbers[i]];
stmtExprIndex.getMapping().setDefault(om, ps, stmtExprIndex.getParameterIndex());
}
}
sm.provideFields(classFieldNumbers, new ParameterSetter(sm, ps, retrievedStatementExpressionIndex, true));
}
finally
{
sqlControl.closeStatement(mconn, ps);
}
}
finally
{
mconn.release();
}
}
catch (SQLException e)
{
String msg = LOCALISER.msg("052208",
StringUtils.toJVMIDString(sm.getObject()), insertStmt, e.getMessage());
JPOXLogger.DATASTORE_PERSIST.warn(msg);
List exceptions = new ArrayList();
exceptions.add(e);
while((e = e.getNextException())!=null)
{
exceptions.add(e);
}
throw new JPOXDataStoreException(msg, (Throwable[])exceptions.toArray(new Throwable[exceptions.size()]));
}
// Execute any mapping actions now that we have inserted the element
// (things like inserting any association parent-child).
for (int i = 0; i < callbacks.length; ++i)
{
try
{
if (JPOXLogger.PERSISTENCE.isDebugEnabled())
{
JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("052209",
StringUtils.toJVMIDString(sm.getObject()),
((JavaTypeMapping)callbacks[i]).getFieldMetaData().getFullFieldName()));
}
callbacks[i].postInsert(sm);
}
catch (NotYetFlushedException e)
{
sm.updateFieldAfterInsert(e.getPersistable(), ((JavaTypeMapping) callbacks[i]).getFieldMetaData().getAbsoluteFieldNumber());
}
}
}
/**
* Method to obtain the identity attributed by the datastore when using auto-increment/IDENTITY/SERIAL.
* @param om ObjectManager
* @param sqlControl SQLController
* @param sm StateManager of the object
* @param mconn The Connection
* @param ps PreparedStatement for the INSERT
* @return The identity
* @throws SQLException Thrown if an error occurs retrieving the identity
*/
private Object getInsertedDatastoreIdentity(ObjectManager om, SQLController sqlControl, StateManager sm,
ManagedConnection mconn, PreparedStatement ps)
throws SQLException
{
Object datastoreId = null;
MappedStoreManager storeMgr = (MappedStoreManager)om.getStoreManager();
if (((RDBMSAdapter) storeMgr.getDatastoreAdapter()).supportsStatementGetGeneratedKeys() &&
JavaUtils.isJRE1_4OrAbove())
{
// Try getGeneratedKeys() method to avoid extra SQL calls (only in more recent JDBC drivers)
ResultSet rs = null;
try
{
rs = ps.getGeneratedKeys();
if (rs != null && rs.next())
{
datastoreId = rs.getObject(1);
}
}
catch (Throwable e)
{
// Not supported maybe (e.g HSQL), or the driver is too old
}
finally
{
if (rs != null)
{
rs.close();
}
}
}
if (datastoreId == null)
{
// Not found, so try the native method for retrieving it
String columnName = null;
JavaTypeMapping idMapping = table.getIDMapping();
if (idMapping != null)
{
for (int i=0;i<idMapping.getNumberOfDatastoreFields();i++)
{
Column col = (Column)idMapping.getDataStoreMapping(i).getDatastoreField();
if (col.isAutoIncrement())
{
columnName = col.getIdentifier().toString();
break;
}
}
}
String autoIncStmt =
((RDBMSAdapter) storeMgr.getDatastoreAdapter()).getAutoIncrementStmt((Table)table, columnName);
PreparedStatement psAutoIncrement = sqlControl.getStatementForQuery(mconn, autoIncStmt);
ResultSet rs = null;
try
{
rs = sqlControl.executeStatementQuery(mconn, autoIncStmt, psAutoIncrement);
if (rs.next())
{
datastoreId = rs.getObject(1);
}
}
finally
{
if (rs != null)
{
rs.close();
}
if (psAutoIncrement != null)
{
psAutoIncrement.close();
}
}
}
if (datastoreId == null)
{
throw new JPOXDataStoreException(LOCALISER.msg("052205",
this.table));
}
return datastoreId;
}
/**
* Internal class to provide mapping consumption for an INSERT.
*/
private class InsertMappingConsumer implements MappingConsumer
{
/** Numbers of all fields to be inserted. */
List insertFields = new ArrayList();
/** Numbers of all PK fields. */
List pkFields = new ArrayList();
/** Numbers of all reachable fields (with no datastore column). */
List reachableFields = new ArrayList();
/** Numbers of all relations fields (bidir that may already be attached when persisting). */
List relationFields = new ArrayList();
StringBuffer columnNames = new StringBuffer();
StringBuffer columnValues = new StringBuffer();
Map assignedColumns = new HashMap();
List mc = new ArrayList();
boolean initialized = false;
int paramIndex;
/** one StatementExpressionIndex for each field **/
private StatementExpressionIndex[] statementExpressionIndex;
/** statement indexes for fields to be "retrieved". */
private StatementExpressionIndex[] retrievedStatementExpressionIndex;
/** StatementExpressionIndex for version **/
private StatementExpressionIndex versionStatementExpressionIndex;
/** StatementExpressionIndex for discriminator **/
private StatementExpressionIndex discriminatorStatementExpressionIndex;
private StatementExpressionIndex[] externalFKStmtExprIndex;
private StatementExpressionIndex[] externalFKDiscrimStmtExprIndex;
private StatementExpressionIndex[] externalOrderStmtExprIndex;
private final ClassLoaderResolver clr;
private final AbstractClassMetaData cmd;
/**
* Constructor
* @param clr the ClassLoaderResolver
* @param cmd ClassMetaData
* @param initialParamIndex the initial index to use for the JDBC Parameter
*/
public InsertMappingConsumer(ClassLoaderResolver clr, AbstractClassMetaData cmd, int initialParamIndex)
{
super();
this.clr = clr;
this.cmd = cmd;
this.paramIndex = initialParamIndex;
}
public void preConsumeMapping(int highestFieldNumber)
{
if (!initialized)
{
statementExpressionIndex = new StatementExpressionIndex[highestFieldNumber];
retrievedStatementExpressionIndex = new StatementExpressionIndex[highestFieldNumber];
initialized = true;
}
}
/**
* Consumes a mapping for a field.
* @param m The mapping.
* @param fmd MetaData for the field
*/
public void consumeMapping(JavaTypeMapping m, AbstractMemberMetaData fmd)
{
if (!fmd.getAbstractClassMetaData().isSameOrAncestorOf(cmd))
{
// Make sure we only accept mappings from the correct part of any inheritance tree
return;
}
if (m.includeInInsertStatement())
{
if (m.getNumberOfDatastoreFields() == 0 &&
(m instanceof PersistenceCapableMapping || m instanceof ReferenceMapping))
{
// Reachable Fields (that relate to this object but have no column in the table)
retrievedStatementExpressionIndex[fmd.getAbsoluteFieldNumber()] = new StatementExpressionIndex();
retrievedStatementExpressionIndex[fmd.getAbsoluteFieldNumber()].setMapping(m);
int relationType = fmd.getRelationType(clr);
if (relationType == Relation.ONE_TO_ONE_BI)
{
if (fmd.getMappedBy() != null)
{
// 1-1 Non-owner bidirectional field (no datastore columns)
reachableFields.add(new Integer(fmd.getAbsoluteFieldNumber()));
}
}
else if (relationType == Relation.MANY_TO_ONE_BI)
{
AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
if (fmd.getJoinMetaData() != null || relatedMmds[0].getJoinMetaData() != null)
{
// N-1 bidirectional field using join (no datastore columns)
reachableFields.add(new Integer(fmd.getAbsoluteFieldNumber()));
}
}
// TODO What about 1-N non-owner
}
else
{
// Fields to be "inserted" (that have a datastore column)
// Check if the field is "insertable" (either using JPA column, or JDO extension)
if (fmd.hasExtension("insertable") && fmd.getValueForExtension("insertable").equalsIgnoreCase("false"))
{
return;
}
ColumnMetaData[] colmds = fmd.getColumnMetaData();
if (colmds != null && colmds.length > 0)
{
for (int i=0;i<colmds.length;i++)
{
if (!colmds[i].getInsertable())
{
// Not to be inserted
return;
}
}
}
int relationType = fmd.getRelationType(clr);
if (relationType == Relation.ONE_TO_ONE_BI)
{
if (fmd.getMappedBy() == null)
{
// 1-1 Owner bidirectional field using FK (in this table)
}
}
else if (relationType == Relation.MANY_TO_ONE_BI)
{
AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
if (fmd.getJoinMetaData() == null && relatedMmds[0].getJoinMetaData() == null)
{
// N-1 bidirectional field using FK (in this table)
relationFields.add(new Integer(fmd.getAbsoluteFieldNumber()));
}
}
statementExpressionIndex[fmd.getAbsoluteFieldNumber()] = new StatementExpressionIndex();
statementExpressionIndex[fmd.getAbsoluteFieldNumber()].setMapping(m);
// create the expressions index (columns index)
int parametersIndex[] = new int[m.getNumberOfDatastoreFields()];
for (int j = 0; j < parametersIndex.length; j++)
{
// check if the column was not already assigned
Column c = (Column)m.getDataStoreMapping(j).getDatastoreField();
DatastoreIdentifier columnId = c.getIdentifier();
if (!assignedColumns.containsKey(columnId.toString()))
{
// Either we are a field in a secondary table.
// Or we are a subclass table.
// Or we are not datastore attributed.
if (table instanceof SecondaryTable ||
(!table.getStoreManager().isStrategyDatastoreAttributed(fmd.getValueStrategy(), false)
&& !c.isAutoIncrement()) ||
!table.isBaseDatastoreClass())
{
if (columnNames.length() > 0)
{
columnNames.append(',');
columnValues.append(',');
}
columnNames.append(columnId);
columnValues.append(((RDBMSMapping)m.getDataStoreMapping(j)).getInsertionInputParameter());
if (((RDBMSMapping)m.getDataStoreMapping(j)).insertValuesOnInsert())
{
// only add fields to be replaced by the real values only if the param value has ?
Integer abs_field_num = new Integer(fmd.getAbsoluteFieldNumber());
if (fmd.isPrimaryKey())
{
if (!pkFields.contains(abs_field_num))
{
pkFields.add(abs_field_num);
}
}
else
{
if (!insertFields.contains(abs_field_num))
{
insertFields.add(abs_field_num);
}
}
parametersIndex[j] = paramIndex++;
}
assignedColumns.put(c.getIdentifier().toString(),new Integer(fmd.getAbsoluteFieldNumber()));
}
else
{
hasAutoIncrementColumn = true;
}
}
else
{
parametersIndex[j] = ((Integer)assignedColumns.get(c.getIdentifier().toString())).intValue();
}
statementExpressionIndex[fmd.getAbsoluteFieldNumber()].setParameterIndex(parametersIndex);
}
}
}
if (m instanceof MappingCallbacks)
{
mc.add(m);
}
}
/**
* Consumes a mapping not associated to a field.
* @param m The mapping.
* @param mappingType the Mapping type
*/
public void consumeMapping(JavaTypeMapping m, int mappingType)
{
if (mappingType == MappingConsumer.MAPPING_TYPE_VERSION)
{
// Surrogate version column
if (table.getVersionMapping(false) != null)
{
String val = ((RDBMSMapping)table.getVersionMapping(false).getDataStoreMapping(0)).getUpdateInputParameter();
if (columnNames.length() > 0)
{
columnNames.append(',');
columnValues.append(',');
}
columnNames.append(((Column)table.getVersionMapping(false).getDataStoreMapping(0).getDatastoreField()).getIdentifier());
columnValues.append(val);
versionStatementExpressionIndex = new StatementExpressionIndex();
versionStatementExpressionIndex.setMapping(table.getVersionMapping(false));
int[] param = { paramIndex++ };
versionStatementExpressionIndex.setParameterIndex(param);
}
else
{
versionStatementExpressionIndex = null;
}
}
else if (mappingType == MappingConsumer.MAPPING_TYPE_DISCRIMINATOR)
{
// Surrogate discriminator column
if (table.getDiscriminatorMapping(false) != null)
{
String val = ((RDBMSMapping)table.getDiscriminatorMapping(false).getDataStoreMapping(0)).getUpdateInputParameter();
if (columnNames.length() > 0)
{
columnNames.append(',');
columnValues.append(',');
}
columnNames.append(((Column)table.getDiscriminatorMapping(false).getDataStoreMapping(0).getDatastoreField()).getIdentifier());
columnValues.append(val);
discriminatorStatementExpressionIndex = new StatementExpressionIndex();
discriminatorStatementExpressionIndex.setMapping(table.getDiscriminatorMapping(false));
int[] param = { paramIndex++ };
discriminatorStatementExpressionIndex.setParameterIndex(param);
}
else
{
discriminatorStatementExpressionIndex = null;
}
}
else if (mappingType == MappingConsumer.MAPPING_TYPE_DATASTORE_ID)
{
// Surrogate datastore id column
if (table.getIdentityType() == IdentityType.DATASTORE)
{
if (!table.isObjectIDDatastoreAttributed() || !table.isBaseDatastoreClass())
{
Iterator iterator = key.getColumns().iterator();
if (columnNames.length() > 0)
{
columnNames.append(',');
columnValues.append(',');
}
columnNames.append(((Column) iterator.next()).getIdentifier().toString());
columnValues.append("?");
paramIndex++;
}
else
{
hasAutoIncrementColumn = true;
}
}
}
else if (mappingType == MappingConsumer.MAPPING_TYPE_EXTERNAL_FK)
{
// External FK mapping (1-N uni)
externalFKStmtExprIndex = processExternalMapping(m, statementExpressionIndex, externalFKStmtExprIndex);
}
else if (mappingType == MappingConsumer.MAPPING_TYPE_EXTERNAL_FK_DISCRIM)
{
// External FK discriminator mapping (1-N uni with shared FK)
externalFKDiscrimStmtExprIndex = processExternalMapping(m, statementExpressionIndex, externalFKDiscrimStmtExprIndex);
}
else if (mappingType == MappingConsumer.MAPPING_TYPE_EXTERNAL_INDEX)
{
// External FK order mapping (1-N uni List)
externalOrderStmtExprIndex = processExternalMapping(m, statementExpressionIndex, externalOrderStmtExprIndex);
}
}
/**
* Consumer a datastore field without mapping.
* @param fld The datastore field
*/
public void consumeUnmappedDatastoreField(DatastoreField fld)
{
// TODO Add this field to the INSERT statement with its insert value
if (columnNames.length() > 0)
{
columnNames.append(',');
columnValues.append(',');
}
Column col = (Column)fld;
columnNames.append(fld.getIdentifier());
ColumnMetaData colmd = (ColumnMetaData)col.getMetaData();
String value = colmd.getInsertValue();
if (colmd.getJdbcType().equals("VARCHAR") || colmd.getJdbcType().equals("CHAR"))
{
// String-based so add quoting
value = "\"" + value + "\"";
}
columnValues.append(value);
}
/**
* Convenience method to process an external mapping.
* @param mapping The external mapping
* @param fieldStmtExprIndex The indices for the fields
* @param stmtExprIndex The current external mapping indices
* @return The updated external mapping indices
*/
private StatementExpressionIndex[] processExternalMapping(JavaTypeMapping mapping,
StatementExpressionIndex[] fieldStmtExprIndex, StatementExpressionIndex[] stmtExprIndex)
{
// Check that we dont already have this as a field
for (int i=0;i<fieldStmtExprIndex.length;i++)
{
if (fieldStmtExprIndex[i] != null && fieldStmtExprIndex[i].getMapping() == mapping)
{
// Already present so ignore it
return stmtExprIndex;
}
}
int pos = 0;
if (stmtExprIndex == null)
{
stmtExprIndex = new StatementExpressionIndex[1];
pos = 0;
}
else
{
// Check that we dont already have this external order mapping (shared?)
for (int i=0;i<stmtExprIndex.length;i++)
{
if (stmtExprIndex[i].getMapping() == mapping)
{
// Shared order mapping so ignore it
return stmtExprIndex;
}
}
StatementExpressionIndex[] tmpStmtExprIndex = stmtExprIndex;
stmtExprIndex = new StatementExpressionIndex[tmpStmtExprIndex.length+1];
for (int i=0;i<tmpStmtExprIndex.length;i++)
{
stmtExprIndex[i] = tmpStmtExprIndex[i];
}
pos = tmpStmtExprIndex.length;
}
stmtExprIndex[pos] = new StatementExpressionIndex();
stmtExprIndex[pos].setMapping(mapping);
int[] param = new int[mapping.getNumberOfDatastoreFields()];
for (int i=0;i<mapping.getNumberOfDatastoreFields();i++)
{
if (columnNames.length() > 0)
{
columnNames.append(',');
columnValues.append(',');
}
columnNames.append(((Column)mapping.getDataStoreMapping(i).getDatastoreField()).getIdentifier());
columnValues.append(((RDBMSMapping)mapping.getDataStoreMapping(i)).getUpdateInputParameter());
param[i] = paramIndex++;
}
stmtExprIndex[pos].setParameterIndex(param);
return stmtExprIndex;
}
/**
* @return Returns the mappingCallbacks.
*/
public List getMappingCallbacks()
{
return mc;
}
/**
* Accessor for the numbers of the fields to be inserted.
* @return the array of field numbers
*/
public int[] getInsertFieldNumbers()
{
int[] fieldNumbers = new int[insertFields.size()];
for (int i = 0; i < insertFields.size(); ++i)
{
fieldNumbers[i] = ((Integer) insertFields.get(i)).intValue();
}
return fieldNumbers;
}
/**
* Accessor for the numbers of the PK fields.
* @return the array of primary key field numbers
*/
public int[] getPrimaryKeyFieldNumbers()
{
int[] fieldNumbers = new int[pkFields.size()];
for (int i = 0; i < pkFields.size(); i++)
{
fieldNumbers[i] = ((Integer) pkFields.get(i)).intValue();
}
return fieldNumbers;
}
/**
* Accessor for the numbers of the reachable fields (not inserted).
* @return the array of field numbers
*/
public int[] getReachableFieldNumbers()
{
int[] fieldNumbers = new int[reachableFields.size()];
for (int i = 0; i < reachableFields.size(); ++i)
{
fieldNumbers[i] = ((Integer) reachableFields.get(i)).intValue();
}
return fieldNumbers;
}
/**
* Accessor for the numbers of the relation fields (not inserted).
* @return the array of field numbers
*/
public int[] getRelationFieldNumbers()
{
int[] fieldNumbers = new int[relationFields.size()];
for (int i = 0; i < relationFields.size(); ++i)
{
fieldNumbers[i] = ((Integer) relationFields.get(i)).intValue();
}
return fieldNumbers;
}
/**
* Obtain the StatementExpressionIndex
* @return the array of StatementExpressionIndex indexed by absolute field numbers
*/
public StatementExpressionIndex[] getStatementExpressionIndex()
{
return statementExpressionIndex;
}
/**
* Obtain the StatementExpressionIndex for the "reachable" fields.
* @return the array of StatementExpressionIndex indexed by absolute field numbers
*/
public StatementExpressionIndex[] getReachableStatementIndex()
{
return retrievedStatementExpressionIndex;
}
/**
* Obtain the StatementExpressionIndex for the version
* @return the StatementExpressionIndex
*/
public StatementExpressionIndex getVersionStatementExpressionIndex()
{
return versionStatementExpressionIndex;
}
/**
* Obtain the StatementExpressionIndex for the discriminator
* @return the StatementExpressionIndex
*/
public StatementExpressionIndex getDiscriminatorStatementExpressionIndex()
{
return discriminatorStatementExpressionIndex;
}
/**
* Obtain the StatementExpressionIndex for any external FKs
* @return the StatementExpressionIndex
*/
public StatementExpressionIndex[] getExternalFKStatementExpressionIndex()
{
return externalFKStmtExprIndex;
}
/**
* Obtain the StatementExpressionIndex for any external FK disciminators
* @return the StatementExpressionIndex
*/
public StatementExpressionIndex[] getExternalFKDiscrimStatementExpressionIndex()
{
return externalFKDiscrimStmtExprIndex;
}
/**
* Obtain the StatementExpressionIndex for any external indexes
* @return the StatementExpressionIndex
*/
public StatementExpressionIndex[] getExternalOrderStatementExpressionIndex()
{
return externalOrderStmtExprIndex;
}
/**
* Obtain the insert statement
* @return the SQL statement
*/
public String getInsertStmt()
{
// Construct the statement for the INSERT
if (columnNames.length() > 0 && columnValues.length() > 0)
{
return "INSERT INTO " + table.toString() + " (" + columnNames + ") VALUES (" + columnValues + ")";
}
else
{
// No columns in the INSERT statement
return ((RDBMSAdapter)table.getStoreManager().getDatastoreAdapter()).getInsertStatementForNoColumns((Table)table);
}
}
}
}