/*****************************************************************
* 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
*
* 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.
****************************************************************/
package org.apache.cayenne.access;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.Validating;
import org.apache.cayenne.graph.CompoundDiff;
import org.apache.cayenne.graph.GraphChangeHandler;
import org.apache.cayenne.graph.GraphDiff;
import org.apache.cayenne.graph.NodeDiff;
import org.apache.cayenne.validation.ValidationException;
import org.apache.cayenne.validation.ValidationResult;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* A GraphDiff facade for the ObjectStore changes. Provides a way for the lower
* layers of the access stack to speed up processing of presorted ObjectStore
* diffs.
*
* @since 1.2
*/
class ObjectStoreGraphDiff implements GraphDiff {
private ObjectStore objectStore;
private GraphDiff resolvedDiff;
private int lastSeenDiffId;
ObjectStoreGraphDiff(ObjectStore objectStore) {
this.objectStore = objectStore;
preprocess(objectStore);
}
Map<Object, ObjectDiff> getChangesByObjectId() {
return objectStore.getChangesByObjectId();
}
/**
* Requires external synchronization on ObjectStore.
*/
boolean validateAndCheckNoop() {
if (getChangesByObjectId().isEmpty()) {
return true;
}
boolean noop = true;
// build a new collection for validation as validation methods may
// result in
// ObjectStore modifications
Collection<Validating> objectsToValidate = null;
for (final ObjectDiff diff : getChangesByObjectId().values()) {
if (!diff.isNoop()) {
noop = false;
if (diff.getObject() instanceof Validating) {
if (objectsToValidate == null) {
objectsToValidate = new ArrayList<>();
}
objectsToValidate.add((Validating) diff.getObject());
}
}
}
if (objectsToValidate != null) {
ValidationResult result = new ValidationResult();
for (Validating object : objectsToValidate) {
switch (((Persistent) object).getPersistenceState()) {
case PersistenceState.NEW:
object.validateForInsert(result);
break;
case PersistenceState.MODIFIED:
object.validateForUpdate(result);
break;
case PersistenceState.DELETED:
object.validateForDelete(result);
break;
}
}
if (result.hasFailures()) {
throw new ValidationException(result);
}
}
return noop;
}
@Override
public boolean isNoop() {
if (getChangesByObjectId().isEmpty()) {
return true;
}
for (ObjectDiff diff : getChangesByObjectId().values()) {
if (!diff.isNoop()) {
return false;
}
}
return true;
}
@Override
public void apply(GraphChangeHandler handler) {
resolveDiff();
resolvedDiff.apply(handler);
}
@Override
public void undo(GraphChangeHandler handler) {
resolveDiff();
resolvedDiff.undo(handler);
}
/**
* Converts diffs organized by ObjectId in a collection of diffs sorted by
* diffId (same as creation order).
*/
private void resolveDiff() {
// refresh the diff on first access or if the underlying ObjectStore has
// changed the the last time we cached the changes.
if (resolvedDiff == null || lastSeenDiffId < objectStore.currentDiffId) {
CompoundDiff diff = new CompoundDiff();
Map<Object, ObjectDiff> changes = getChangesByObjectId();
if (!changes.isEmpty()) {
List<NodeDiff> allChanges = new ArrayList<>(changes.size() * 2);
for (final ObjectDiff objectDiff : changes.values()) {
objectDiff.appendDiffs(allChanges);
}
Collections.sort(allChanges);
diff.addAll(allChanges);
}
this.lastSeenDiffId = objectStore.currentDiffId;
this.resolvedDiff = diff;
}
}
private void preprocess(ObjectStore objectStore) {
Map<Object, ObjectDiff> changes = getChangesByObjectId();
if (!changes.isEmpty()) {
for (Entry<Object, ObjectDiff> entry : changes.entrySet()) {
ObjectId id = (ObjectId) entry.getKey();
Persistent object = (Persistent) objectStore.getNode(id);
// address manual id override.
ObjectId objectId = object.getObjectId();
if (!id.equals(objectId)) {
if (objectId != null) {
Map<String, Object> replacement = id.getReplacementIdMap();
replacement.clear();
replacement.putAll(objectId.getIdSnapshot());
}
object.setObjectId(id);
}
}
}
}
}