blob: e017c966019258129b537c1a56e73009c7939b45 [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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.apache.cayenne.graph.ArcId;
import org.apache.cayenne.map.DeleteRule;
import org.apache.cayenne.map.LifecycleEvent;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.reflect.ArcProperty;
import org.apache.cayenne.reflect.AttributeProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
import org.apache.cayenne.reflect.PropertyVisitor;
import org.apache.cayenne.reflect.ToManyProperty;
import org.apache.cayenne.reflect.ToOneProperty;
/**
* A CayenneContext helper that processes object deletion.
*
* @since 1.2
*/
class ObjectContextDeleteAction {
private ObjectContext context;
ObjectContextDeleteAction(ObjectContext context) {
this.context = context;
}
boolean performDelete(Persistent object) throws DeleteDenyException {
int oldState = object.getPersistenceState();
if (oldState == PersistenceState.TRANSIENT
|| oldState == PersistenceState.DELETED) {
// Drop out... especially in case of DELETED we might be about to get
// into a horrible recursive loop due to CASCADE delete rules.
// Assume that everything must have been done correctly already
// and *don't* do it again
return false;
}
if (object.getObjectContext() == null) {
throw new CayenneRuntimeException(
"Attempt to delete unregistered non-TRANSIENT object: %s", object);
}
if (object.getObjectContext() != context) {
throw new CayenneRuntimeException(
"Attempt to delete object regsitered in a different ObjectContext. Object: %s, context: %s"
, object, context);
}
// must resolve HOLLOW objects before delete... needed
// to process relationships and optimistic locking...
context.prepareForAccess(object, null, false);
if (oldState == PersistenceState.NEW) {
deleteNew(object);
}
else {
deletePersistent(object);
}
return true;
}
private void deleteNew(Persistent object) throws DeleteDenyException {
object.setPersistenceState(PersistenceState.TRANSIENT);
processDeleteRules(object, PersistenceState.NEW);
// if an object was NEW, we must throw it out
context.getGraphManager().unregisterNode(object.getObjectId());
}
private void deletePersistent(Persistent object) throws DeleteDenyException {
context.getEntityResolver().getCallbackRegistry().performCallbacks(
LifecycleEvent.PRE_REMOVE,
object);
int oldState = object.getPersistenceState();
object.setPersistenceState(PersistenceState.DELETED);
processDeleteRules(object, oldState);
context.getGraphManager().nodeRemoved(object.getObjectId());
}
@SuppressWarnings("unchecked")
private Collection<Persistent> toCollection(Object object) {
if (object == null) {
return Collections.emptyList();
}
// create copies of collections to avoid iterator exceptions
if (object instanceof Collection) {
return new ArrayList<>((Collection<Persistent>) object);
} else if (object instanceof Map) {
return new ArrayList<>(((Map<?, Persistent>) object).values());
} else {
return Collections.singleton((Persistent)object);
}
}
private void processDeleteRules(final Persistent object, int oldState)
throws DeleteDenyException {
ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
object.getObjectId().getEntityName());
for (final ObjRelationship relationship : descriptor.getEntity().getRelationships()) {
boolean processFlattened = relationship.isFlattened()
&& relationship.isToDependentEntity()
&& !relationship.isReadOnly();
// first check for no action... bail out if no flattened processing is needed
if (relationship.getDeleteRule() == DeleteRule.NO_ACTION && !processFlattened) {
continue;
}
ArcProperty property = (ArcProperty) descriptor.getProperty(relationship.getName());
final Collection<Persistent> relatedObjects = toCollection(property.readProperty(object));
// no related object, bail out
if (relatedObjects.size() == 0) {
continue;
}
// process DENY rule first...
if (relationship.getDeleteRule() == DeleteRule.DENY) {
object.setPersistenceState(oldState);
String message = relatedObjects.size() == 1
? "1 related object"
: relatedObjects.size() + " related objects";
throw new DeleteDenyException(object, relationship.getName(), message);
}
// process flattened with dependent join tables...
// joins must be removed even if they are non-existent or ignored in the
// object graph
if (processFlattened) {
ArcId arcId = new ArcId(property);
for (Persistent relatedObject : relatedObjects) {
context.getGraphManager()
.arcDeleted(object.getObjectId(), relatedObject.getObjectId(), arcId);
}
}
// process remaining rules
switch (relationship.getDeleteRule()) {
case DeleteRule.NO_ACTION:
break;
case DeleteRule.NULLIFY:
ArcProperty reverseArc = property.getComplimentaryReverseArc();
if (reverseArc == null) {
// nothing we can do here
break;
}
reverseArc.visit(new PropertyVisitor() {
public boolean visitAttribute(AttributeProperty property) {
return false;
}
public boolean visitToMany(ToManyProperty property) {
for (Persistent relatedObject : relatedObjects) {
property.removeTarget(relatedObject, object, true);
}
return false;
}
public boolean visitToOne(ToOneProperty property) {
// Inverse is to-one - find all related objects and
// nullify the reverse relationship
for (Persistent relatedObject : relatedObjects) {
property.setTarget(relatedObject, null, true);
}
return false;
}
});
break;
case DeleteRule.CASCADE:
// Delete all related objects
for (Persistent relatedObject : relatedObjects) {
performDelete(relatedObject);
}
break;
default:
object.setPersistenceState(oldState);
throw new CayenneRuntimeException("Invalid delete rule %s", relationship.getDeleteRule());
}
}
}
}