if (!api.isPersistable(value))
{
throw new NucleusException(LOCALISER.msg("041016", value.getClass(), value)).setFatal();
}
ObjectProvider valueSM = ec.findObjectProvider(value);
try
{
ClassLoaderResolver clr = ec.getClassLoaderResolver();
// Check if the field is attributed in the datastore
boolean hasDatastoreAttributedPrimaryKeyValues = hasDatastoreAttributedPrimaryKeyValues(
ec.getMetaDataManager(), storeMgr, clr);
boolean inserted = false;
if (ownerFieldNumber >= 0)
{
// Field mapping : is this field of the related object present in the datastore?
inserted = storeMgr.isObjectInserted(valueSM, ownerFieldNumber);
}
else if (mmd == null)
{
// Identity mapping : is the object inserted far enough to be considered of this mapping type?
inserted = storeMgr.isObjectInserted(valueSM, type);
}
if (valueSM != null)
{
if (ec.getApiAdapter().isDetached(value) && valueSM.getReferencedPC() != null && ownerSM != null && mmd != null)
{
// Still detached but started attaching so replace the field with what will be the attached
// Note that we have "fmd != null" here hence omitting any M-N relations where this is a join table
// mapping
ownerSM.replaceFieldMakeDirty(ownerFieldNumber, valueSM.getReferencedPC());
}
if (valueSM.isWaitingToBeFlushedToDatastore())
{
// Related object is not yet flushed to the datastore so flush it so we can set the FK
valueSM.flush();
}
}
else
{
if (ec.getApiAdapter().isDetached(value))
{
// Field value is detached and not yet started attaching, so attach
Object attachedValue = ec.persistObjectInternal(value, null, -1, ObjectProvider.PC);
if (attachedValue != value && ownerSM != null)
{
// Replace the field value if using copy-on-attach
ownerSM.replaceFieldMakeDirty(ownerFieldNumber, attachedValue);
}
}
}
// we can execute this block when
// 1) the pc has been inserted; OR
// 2) is not in process of being inserted; OR
// 3) is being inserted yet is inserted enough to use this mapping; OR
// 4) the PC PK values are not attributed by the database and this mapping is for a PK field (compound identity)
// 5) the value is the same object as we are inserting anyway and has its identity set
if (inserted || !ec.isInserting(value) ||
(!hasDatastoreAttributedPrimaryKeyValues && (this.mmd != null && this.mmd.isPrimaryKey())) ||
(!hasDatastoreAttributedPrimaryKeyValues && ownerSM == valueSM && api.getIdForObject(value) != null))
{
// The PC is either already inserted, or inserted down to the level we need, or not inserted at all,
// or the field is a PK and identity not attributed by the datastore
// Object either already exists, or is not yet being inserted.
id = api.getIdForObject(value);
// Check if the PersistenceCapable exists in this datastore
boolean requiresPersisting = false;
if (ec.getApiAdapter().isDetached(value) && ownerSM != null)
{
// Detached object so needs attaching
if (ownerSM.isInserting())
{
// Inserting other object, and this object is detached but if detached from this datastore
// we can just return the value now and attach later (in InsertRequest)
if (!ec.getNucleusContext().getPersistenceConfiguration().getBooleanProperty("datanucleus.attachSameDatastore"))
{
if (ec.getObjectFromCache(api.getIdForObject(value)) != null)
{
// Object is in cache so exists for this datastore, so no point checking
}
else
{
try
{
Object obj = ec.findObject(api.getIdForObject(value), true, false,
value.getClass().getName());
if (obj != null)
{
// Make sure this object is not retained in cache etc
ObjectProvider objSM = ec.findObjectProvider(obj);
if (objSM != null)
{
ec.evictFromTransaction(objSM);
}
ec.removeObjectFromLevel1Cache(api.getIdForObject(value));
}
}
catch (NucleusObjectNotFoundException onfe)
{
// Object doesnt yet exist
requiresPersisting = true;
}
}
}
}
else
{
requiresPersisting = true;
}
}
else if (id == null)
{
// Transient object, so we need to persist it
requiresPersisting = true;
}
else
{
ExecutionContext pcEC = ec.getApiAdapter().getExecutionContext(value);
if (pcEC != null && ec != pcEC)
{
throw new NucleusUserException(LOCALISER.msg("041015"), id);
}
}
if (requiresPersisting)
{
// PERSISTENCE-BY-REACHABILITY
// This PC object needs persisting (new or detached) to do the "set"
if (mmd != null && !mmd.isCascadePersist() && !ec.getApiAdapter().isDetached(value))
{
// Related PC object not persistent, but cant do cascade-persist so throw exception
if (NucleusLogger.PERSISTENCE.isDebugEnabled())
{
NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("007006",
mmd.getFullFieldName()));
}
throw new ReachableObjectNotCascadedException(mmd.getFullFieldName(), value);
}
if (NucleusLogger.PERSISTENCE.isDebugEnabled())
{
NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("007007",
mmd != null ? mmd.getFullFieldName() : null));
}
try
{
Object pcNew = ec.persistObjectInternal(value, null, -1, ObjectProvider.PC);
if (hasDatastoreAttributedPrimaryKeyValues)
{
ec.flushInternal(false);
}
id = api.getIdForObject(pcNew);
if (ec.getApiAdapter().isDetached(value) && ownerSM != null)
{
// Update any detached reference to refer to the attached variant
ownerSM.replaceFieldMakeDirty(ownerFieldNumber, pcNew);
RelationType relationType = mmd.getRelationType(clr);
if (relationType == RelationType.MANY_TO_ONE_BI)
{
// TODO Update the container to refer to the attached object
if (NucleusLogger.PERSISTENCE.isInfoEnabled())
{
NucleusLogger.PERSISTENCE.info("PCMapping.setObject : object " + ownerSM.getInternalObjectId() +
" has field " + ownerFieldNumber + " that is 1-N bidirectional." +
" Have just attached the N side so should really update the reference in the 1 side collection" +
" to refer to this attached object. Not yet implemented");
}
}
else if (relationType == RelationType.ONE_TO_ONE_BI)
{
AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
// TODO Cater for more than 1 related field
ObjectProvider relatedSM = ec.findObjectProvider(pcNew);
relatedSM.replaceFieldMakeDirty(relatedMmds[0].getAbsoluteFieldNumber(), ownerSM.getObject());
}
}
}
catch (NotYetFlushedException e)
{
setObjectAsNull(ec, ps, param);
throw new NotYetFlushedException(value);
}
}
if (valueSM != null)
{
valueSM.setStoringPC();
}
// If the field doesn't map to any datastore fields (e.g remote FK), omit the set process
if (getNumberOfDatastoreMappings() > 0)
{
if (id instanceof OID)
{
OID oid = (OID)id;
try
{
// Try as a Long
getDatastoreMapping(0).setObject(ps, param[0], oid.getKeyValue());
}
catch (Exception e)
{
// Must be a String
getDatastoreMapping(0).setObject(ps, param[0], oid.getKeyValue().toString());
}
}
else
{
boolean fieldsSet = false;
if (api.isSingleFieldIdentity(id) && javaTypeMappings.length > 1)
{
Object key = api.getTargetKeyForSingleFieldIdentity(id);
AbstractClassMetaData keyCmd = ec.getMetaDataManager().getMetaDataForClass(key.getClass(), clr);
if (keyCmd != null && keyCmd.getIdentityType() == IdentityType.NONDURABLE)
{
// Embedded ID - Make sure these are called starting at lowest first, in order
// We cannot just call OP.provideFields with all fields since that does last first
ObjectProvider keyOP = ec.findObjectProvider(key);
int[] fieldNums = keyCmd.getAllMemberPositions();
FieldManager fm = new AppIDObjectIdFieldManager(param, ec, ps, javaTypeMappings);
for (int i=0;i<fieldNums.length;i++)
{
keyOP.provideFields(new int[] {fieldNums[i]}, fm);
}
fieldsSet = true;
}
}