blob: 7dc7fe9416b59fab5d29cec36c08101bc9451e0e [file] [log] [blame]
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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.
****************************************************************/
package org.apache.cayenne.access.flush;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.access.flush.operation.DbRowOp;
import org.apache.cayenne.access.flush.operation.DbRowOpVisitor;
import org.apache.cayenne.access.flush.operation.DeleteDbRowOp;
import org.apache.cayenne.access.flush.operation.InsertDbRowOp;
import org.apache.cayenne.access.flush.operation.UpdateDbRowOp;
import org.apache.cayenne.exp.parser.ASTDbPath;
import org.apache.cayenne.reflect.ArcProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
import org.apache.cayenne.reflect.ToManyMapProperty;
/**
* @since 4.2
*/
class PostprocessVisitor implements DbRowOpVisitor<Void> {
private final DataContext context;
private Map<ObjectId, DataRow> updatedSnapshots;
private Collection<ObjectId> deletedIds;
PostprocessVisitor(DataContext context) {
this.context = context;
}
@Override
public Void visitInsert(InsertDbRowOp dbRow) {
processObjectChange(dbRow);
return null;
}
@Override
public Void visitUpdate(UpdateDbRowOp dbRow) {
processObjectChange(dbRow);
return null;
}
private void processObjectChange(DbRowOp dbRow) {
if (dbRow.getChangeId().getEntityName().startsWith(ASTDbPath.DB_PREFIX)) {
return;
}
DataRow dataRow = context.currentSnapshot(dbRow.getObject());
if (dbRow.getObject() instanceof DataObject) {
DataObject dataObject = (DataObject) dbRow.getObject();
dataRow.setReplacesVersion(dataObject.getSnapshotVersion());
dataObject.setSnapshotVersion(dataRow.getVersion());
}
if (updatedSnapshots == null) {
updatedSnapshots = new HashMap<>();
}
updatedSnapshots.put(dbRow.getObject().getObjectId(), dataRow);
// update Map reverse relationships
ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(dbRow.getChangeId().getEntityName());
for (ArcProperty arc : descriptor.getMapArcProperties()) {
ToManyMapProperty reverseArc = (ToManyMapProperty) arc.getComplimentaryReverseArc();
// must resolve faults... hopefully for to-one this will not cause extra fetches...
Object source = arc.readProperty(dbRow.getObject());
if (source != null && !reverseArc.isFault(source)) {
remapTarget(reverseArc, source, dbRow.getObject());
}
}
}
@Override
public Void visitDelete(DeleteDbRowOp dbRow) {
if (dbRow.getChangeId().getEntityName().startsWith(ASTDbPath.DB_PREFIX)) {
return null;
}
if (deletedIds == null) {
deletedIds = new HashSet<>();
}
deletedIds.add(dbRow.getChangeId());
return null;
}
Collection<ObjectId> getDeletedIds() {
return deletedIds == null ? Collections.emptyList() : deletedIds;
}
Map<ObjectId, DataRow> getUpdatedSnapshots() {
return updatedSnapshots == null ? Collections.emptyMap() : updatedSnapshots;
}
private void remapTarget(ToManyMapProperty property, Object source, Object target) {
@SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>) property.readProperty(source);
Object newKey = property.getMapKey(target);
Object currentValue = map.get(newKey);
if (currentValue == target) {
// nothing to do
return;
}
// else - do not check for conflicts here (i.e. another object mapped for the same key), as we have no control
// of the order in which this method is called, so another object may be remapped later by the caller
// must do a slow map scan to ensure the object is not mapped under a different key...
Iterator<Map.Entry<Object, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Object, Object> e = it.next();
if (e.getValue() == target) {
it.remove();
break;
}
}
map.put(newKey, target);
}
}