in grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/engine/NativeEntryEntityPersister.java [797:1135]
protected Serializable persistEntity(final PersistentEntity persistentEntity, Object obj, boolean isInsert) {
T tmp = null;
ProxyFactory proxyFactory = getProxyFactory();
// if called internally, obj can potentially be a proxy, which won't work.
obj = proxyFactory.unwrap(obj);
final NativeEntryModifyingEntityAccess entityAccess = (NativeEntryModifyingEntityAccess) createEntityAccess(persistentEntity, obj, tmp);
K k = readObjectIdentifier(entityAccess, persistentEntity.getMapping());
boolean isUpdate = k != null && !isInsert;
if (isUpdate && !getSession().isDirty(obj)) {
return (Serializable) k;
}
PendingOperation<T, K> pendingOperation;
SessionImplementor<Object> si = (SessionImplementor<Object>) session;
if(si.isPendingAlready(obj)) {
return (Serializable) k;
}
else {
si.registerPending(obj);
}
boolean assignedId = isAssignedId(persistentEntity);
if (isNotUpdateForAssignedId(persistentEntity, obj, isUpdate, assignedId, si)) {
isUpdate = false;
}
String family = getEntityFamily();
if (!isUpdate) {
tmp = createNewEntry(family, obj);
if (!assignedId) {
k = generateIdentifier(persistentEntity, tmp);
}
cacheNativeEntry(persistentEntity, (Serializable) k, tmp);
pendingOperation = new PendingInsertAdapter<T, K>(persistentEntity, k, tmp, entityAccess) {
public void run() {
K insertResult = executeInsert(persistentEntity, entityAccess, getNativeKey(), getNativeEntry());
if(insertResult == null) {
setVetoed(true);
}
}
};
entityAccess.setProperty(entityAccess.getIdentifierName(), k);
}
else {
tmp = (T) si.getCachedEntry(persistentEntity, (Serializable) k);
if (tmp == null) {
tmp = getFromTPCache(persistentEntity, (Serializable) k);
if (tmp == null) {
tmp = retrieveEntry(persistentEntity, family, (Serializable) k);
}
}
if (tmp == null) {
tmp = createNewEntry(family);
}
final T finalTmp = tmp;
final K finalK = k;
pendingOperation = new PendingUpdateAdapter<T, K>(persistentEntity, finalK, finalTmp, entityAccess) {
public void run() {
if (cancelUpdate(persistentEntity, entityAccess)) {
setVetoed(true);
return;
}
updateEntry(persistentEntity, entityAccess, getNativeKey(), getNativeEntry());
updateTPCache(persistentEntity, finalTmp, (Serializable) finalK);
firePostUpdateEvent(persistentEntity, entityAccess);
}
};
}
final T e = tmp;
entityAccess.setNativeEntry(e);
final List<PersistentProperty> props = persistentEntity.getPersistentProperties();
final Map<Association, List<Serializable>> toManyKeys = new HashMap<Association, List<Serializable>>();
final Map<OneToMany, Serializable> inverseCollectionUpdates = new HashMap<OneToMany, Serializable>();
final Map<PersistentProperty, Object> toIndex = new HashMap<PersistentProperty, Object>();
final Map<PersistentProperty, Object> toUnindex = new HashMap<PersistentProperty, Object>();
entityAccess.setToIndex(toIndex);
for (PersistentProperty prop : props) {
PropertyMapping<Property> pm = prop.getMapping();
final Property mappedProperty = pm.getMappedForm();
String key = null;
if (mappedProperty != null) {
key = mappedProperty.getTargetName();
}
if (key == null) key = prop.getName();
final boolean indexed = isPropertyIndexed(mappedProperty);
if ((prop instanceof Simple) ) {
Object propValue = entityAccess.getProperty(prop.getName());
if(propValue == null && !isUpdate) {
continue;
}
handleIndexing(isUpdate, e, toIndex, toUnindex, prop, key, indexed, propValue);
setEntryValue(e, key, propValue);
}
else if((prop instanceof Basic)) {
Basic basic = (Basic) prop;
CustomTypeMarshaller customTypeMarshaller = basic.getCustomTypeMarshaller();
if (customTypeMarshaller != null && customTypeMarshaller.supports(getMappingContext())) {
Object propValue = entityAccess.getProperty(prop.getName());
Object customValue = customTypeMarshaller.write(prop, propValue, e);
handleIndexing(isUpdate, e, toIndex, toUnindex, prop, key, indexed, customValue);
}
else {
Object propValue = entityAccess.getProperty(prop.getName());
if(propValue == null && !isUpdate) {
continue;
}
handleIndexing(isUpdate, e, toIndex, toUnindex, prop, key, indexed, propValue);
setEntryValue(e, key, propValue);
}
}
else if ((prop instanceof Custom)) {
CustomTypeMarshaller customTypeMarshaller = ((Custom) prop).getCustomTypeMarshaller();
if (customTypeMarshaller.supports(getMappingContext())) {
Object propValue = entityAccess.getProperty(prop.getName());
if(propValue == null && !isUpdate) {
continue;
}
Object customValue = customTypeMarshaller.write(prop, propValue, e);
handleIndexing(isUpdate, e, toIndex, toUnindex, prop, key, indexed, customValue);
if(customValue == null) {
setEntryValue(e, key, null);
}
}
}
else if (prop instanceof OneToMany) {
final OneToMany oneToMany = (OneToMany) prop;
final Object propValue = entityAccess.getProperty(oneToMany.getName());
if (propValue instanceof Collection) {
Collection associatedObjects = (Collection) propValue;
if (isInitializedCollection(associatedObjects)) {
PersistentEntity associatedEntity = oneToMany.getAssociatedEntity();
if(associatedEntity != null) {
EntityPersister associationPersister = (EntityPersister) session.getPersister(associatedEntity);
if (associationPersister != null) {
PersistentCollection persistentCollection;
boolean newCollection = false;
if (associatedObjects instanceof PersistentCollection) {
persistentCollection = (PersistentCollection) associatedObjects;
}
else {
Class associationType = associatedEntity.getJavaClass();
persistentCollection = getPersistentCollection(associatedObjects, associationType);
entityAccess.setPropertyNoConversion(oneToMany.getName(), persistentCollection);
persistentCollection.markDirty();
newCollection = true;
}
if (persistentCollection.isDirty()) {
persistentCollection.resetDirty();
List<Serializable> keys = associationPersister.persist(associatedObjects);
toManyKeys.put(oneToMany, keys);
if (newCollection ) {
entityAccess.setProperty(oneToMany.getName(), associatedObjects);
}
}
}
}
}
}
}
else if (prop instanceof ManyToMany) {
final ManyToMany manyToMany = (ManyToMany) prop;
final Object propValue = entityAccess.getProperty(manyToMany.getName());
if (propValue instanceof Collection) {
Collection associatedObjects = (Collection) propValue;
if (isInitializedCollection(associatedObjects)) {
setManyToMany(persistentEntity, obj, e, manyToMany, associatedObjects, toManyKeys);
}
}
}
else if (prop instanceof ToOne) {
ToOne association = (ToOne) prop;
if (prop instanceof Embedded) {
// For embedded properties simply set the entry value, the underlying implementation
// will have to store the embedded entity in an appropriate way (as a sub-document in a document store for example)
handleEmbeddedToOne(association, key, entityAccess, e);
}
else if (association.getAssociatedEntity() != null) {
final Object associatedObject = entityAccess.getProperty(prop.getName());
if (associatedObject != null) {
Serializable associationId;
NativeEntryEntityPersister associationPersister = (NativeEntryEntityPersister) session.getPersister(associatedObject);
if (proxyFactory.isInitialized(associatedObject) && !session.contains(associatedObject) ) {
Serializable tempId = associationPersister.getObjectIdentifier(associatedObject);
if (tempId == null) {
if (association.doesCascade(CascadeType.PERSIST)) {
tempId = session.persist(associatedObject);
}
}
associationId = tempId;
} else {
associationId = associationPersister.getObjectIdentifier(associatedObject);
}
// handling of hasOne inverse key
if (association.isForeignKeyInChild()) {
T cachedAssociationEntry = (T) si.getCachedEntry(association.getAssociatedEntity(), associationId);
if (cachedAssociationEntry != null) {
if (association.isBidirectional()) {
Association inverseSide = association.getInverseSide();
if (inverseSide != null) {
setEntryValue(cachedAssociationEntry, inverseSide.getName(), formulateDatabaseReference(association.getAssociatedEntity(), inverseSide, (Serializable) k));
} else {
setEntryValue(cachedAssociationEntry, key, formulateDatabaseReference(association.getAssociatedEntity(), inverseSide, (Serializable) k));
}
}
}
if (association.isBidirectional()) {
Association inverseSide = association.getInverseSide();
if (inverseSide != null) {
EntityAccess inverseAccess = createEntityAccess(inverseSide.getOwner(), associatedObject);
inverseAccess.setProperty(inverseSide.getName(), obj);
}
}
if( association.doesCascade(CascadeType.PERSIST) ) {
associationPersister.persist(associatedObject);
}
}
// handle of standard many-to-one
else {
if (associationId != null) {
if (indexed && doesRequirePropertyIndexing()) {
toIndex.put(prop, associationId);
if (isUpdate) {
Object oldValue = getEntryValue(e, key);
oldValue = oldValue != null ? convertToNativeKey((Serializable) oldValue) : oldValue;
if (oldValue != null && !oldValue.equals(associationId)) {
toUnindex.put(prop, oldValue);
}
}
}
setEntryValue(e, key, formulateDatabaseReference(persistentEntity, association, associationId));
if (association.isBidirectional()) {
Association inverse = association.getInverseSide();
if (inverse instanceof OneToMany) {
inverseCollectionUpdates.put((OneToMany) inverse, associationId);
}
// unwrap the entity in case it is a proxy, since we may need to update the reverse link.
Object inverseEntity = proxyFactory.unwrap(entityAccess.getProperty(association.getName()));
if (inverseEntity != null) {
EntityAccess inverseAccess = createEntityAccess(association.getAssociatedEntity(), inverseEntity);
Object entity = entityAccess.getEntity();
if (inverse instanceof OneToMany) {
Collection existingValues = (Collection) inverseAccess.getProperty(inverse.getName());
if (existingValues == null) {
existingValues = MappingUtils.createConcreteCollection(inverse.getType());
inverseAccess.setProperty(inverse.getName(), existingValues);
}
if (!existingValues.contains(entity))
existingValues.add(entity);
} else if (inverse instanceof ToOne) {
inverseAccess.setProperty(inverse.getName(), entity);
}
}
}
}
}
} else {
setEntryValue(e, getPropertyKey(prop), null);
}
}
}
else if (prop instanceof EmbeddedCollection) {
handleEmbeddedToMany(entityAccess, e, prop, key);
}
}
// perform pre-indexing (updating the native entry, if supported by this persister).
updateToManyIndices(e, k, toManyKeys, true);
if (!isUpdate) {
// if the identifier is null at this point that means that datastore could not generated an identifer
// and the identifer is generated only upon insert of the entity
final K updateId = k;
PendingOperation postOperation = new PendingOperationAdapter<T, K>(persistentEntity, k, e) {
public void run() {
updateToManyIndices(e, updateId, toManyKeys, false);
if (doesRequirePropertyIndexing()) {
toIndex.put(persistentEntity.getIdentity(), updateId);
updatePropertyIndices(updateId, toIndex, toUnindex);
}
for (OneToMany inverseCollection : inverseCollectionUpdates.keySet()) {
final Serializable primaryKey = inverseCollectionUpdates.get(inverseCollection);
final NativeEntryEntityPersister inversePersister = (NativeEntryEntityPersister) session.getPersister(inverseCollection.getOwner());
final AssociationIndexer associationIndexer = inversePersister.getAssociationIndexer(e, inverseCollection);
associationIndexer.index(primaryKey, updateId);
}
}
};
pendingOperation.addCascadeOperation(postOperation);
// If the key is still null at this point we have to execute the pending operation now to get the key
if (k == null) {
PendingOperationExecution.executePendingOperation(pendingOperation);
}
else {
si.addPendingInsert((PendingInsert) pendingOperation);
}
}
else {
final K updateId = k;
PendingOperation postOperation = new PendingOperationAdapter<T, K>(persistentEntity, k, e) {
public void run() {
updateToManyIndices(e, updateId, toManyKeys, false);
if (doesRequirePropertyIndexing()) {
updatePropertyIndices(updateId, toIndex, toUnindex);
}
}
};
pendingOperation.addCascadeOperation(postOperation);
si.addPendingUpdate((PendingUpdate) pendingOperation);
}
return (Serializable) k;
}