OPENJPA-870: orphanRemoval support

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@739155 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Fay Wang 2009-01-30 03:15:09 +00:00
parent e47d83b051
commit 6453c64343
11 changed files with 338 additions and 37 deletions

View File

@ -40,6 +40,7 @@ import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.jdbc.sql.SelectExecutor;
import org.apache.openjpa.jdbc.sql.Union;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StateManagerImpl;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.JavaTypes;
@ -345,8 +346,9 @@ public abstract class StoreCollectionFieldStrategy
if (decMeta == null)
return;
if (oid.equals(sm.getObjectId())) {
mappedByValue = sm.getPersistenceCapable();
StateManagerImpl owner = ((StateManagerImpl)sm).getObjectIdOwner();
if (oid.equals(owner.getObjectId())) {
mappedByValue = owner.getPersistenceCapable();
res.setMappedByFieldMapping(mappedByFieldMapping);
res.setMappedByValue(mappedByValue);
} else if (coll instanceof Collection &&

View File

@ -18,16 +18,21 @@
*/
package org.apache.openjpa.kernel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.datacache.DataCache;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.ValueMetaData;
@ -131,7 +136,8 @@ public class InverseManager implements Configurable {
public void correctRelations(OpenJPAStateManager sm, FieldMetaData fmd,
Object value) {
if (fmd.getDeclaredTypeCode() != JavaTypes.PC &&
(fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION ||
((fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION &&
fmd.getDeclaredTypeCode() != JavaTypes.MAP) ||
fmd.getElement().getDeclaredTypeCode() != JavaTypes.PC))
return;
@ -146,23 +152,43 @@ public class InverseManager implements Configurable {
// clear any restorable relations
clearInverseRelations(sm, fmd, inverses, value);
if (value != null) {
StoreContext ctx = sm.getContext();
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.PC:
createInverseRelations(ctx, sm.getManagedInstance(),
value, fmd, inverses);
break;
case JavaTypes.COLLECTION:
for (Iterator itr = ((Collection) value).iterator();
itr.hasNext();)
createInverseRelations(ctx, sm.getManagedInstance(),
itr.next(), fmd, inverses);
break;
}
}
// create inverse relations
createInverseRelations(sm, fmd, inverses, value);
}
protected void createInverseRelations(OpenJPAStateManager sm,
FieldMetaData fmd, FieldMetaData[] inverses, Object value) {
if (value == null)
return;
StoreContext ctx = sm.getContext();
if (isEmbedded(sm))
return;
Object obj = sm.getManagedInstance();
createInverseRelations(ctx, obj, fmd, inverses, value);
}
protected void createInverseRelations(StoreContext ctx, Object obj,
FieldMetaData fmd, FieldMetaData[] inverses, Object value) {
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.PC:
createInverseRelations(ctx, obj, value, fmd, inverses);
break;
case JavaTypes.COLLECTION:
for (Iterator itr = ((Collection) value).iterator();
itr.hasNext();)
createInverseRelations(ctx, obj, itr.next(), fmd, inverses);
break;
}
}
protected boolean isEmbedded(OpenJPAStateManager sm) {
ClassMetaData meta = sm.getMetaData();
ValueMetaData owner = meta.getEmbeddingMetaData();
if (owner != null)
return true;
return false;
}
/**
* Create the inverse relations for all the given inverse fields.
* A relation exists from <code>fromRef</code> to <code>toRef</code>; this
@ -241,13 +267,22 @@ public class InverseManager implements Configurable {
Object initial = sm.fetchInitialField(fmd.getIndex());
clearInverseRelations(sm, initial, fmd, inverses);
} else {
Collection initial = (Collection)
sm.fetchInitialField(fmd.getIndex());
Object obj = sm.fetchInitialField(fmd.getIndex());
Collection initial = null;
if (obj instanceof Collection)
initial = (Collection) obj;
else if (obj instanceof Map)
initial = ((Map)obj).values();
if (initial == null)
return;
// clear all relations not also in the new value
Collection coll = (Collection) newValue;
Collection coll = null;
if (newValue instanceof Collection)
coll = (Collection) newValue;
else if (newValue instanceof Map)
coll = ((Map)newValue).values();
Object elem;
for (Iterator itr = initial.iterator(); itr.hasNext();) {
elem = itr.next();
@ -285,7 +320,10 @@ public class InverseManager implements Configurable {
case JavaTypes.PC:
if (!owned || inverses[i].getCascadeDelete()
== ValueMetaData.CASCADE_AUTO)
storeNull(other, inverses[i], sm.getManagedInstance());
if (fmd.getOrphanRemoval() || fmd.getElement().getOrphanRemoval())
((StateManagerImpl)other).delete();
else
storeNull(other, inverses[i], sm.getManagedInstance());
break;
case JavaTypes.COLLECTION:
if (!owned || inverses[i].getElement().getCascadeDelete()

View File

@ -0,0 +1,139 @@
/*
* 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.openjpa.kernel;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
/**
* Class which manages orphanRemoval before flushing
* to the datastore.
*
* @author Fay Wang
*/
public class OrphanRemovalManager {
/**
* Correct relations from the given dirty field to inverse instances.
* Field <code>fmd</code> of the instance managed by <code>sm</code> has
* value <code>value</code>. Ensure that all inverses relations from
* <code>value</code> are consistent with this.
*/
public static void correctRelations(OpenJPAStateManager sm, FieldMetaData fmd,
Object value) {
if (fmd.getDeclaredTypeCode() != JavaTypes.PC &&
((fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION &&
fmd.getDeclaredTypeCode() != JavaTypes.MAP) ||
fmd.getElement().getDeclaredTypeCode() != JavaTypes.PC))
return;
FieldMetaData[] orphanRemoves = fmd.getOrphanRemovalMetaDatas();
if (orphanRemoves.length == 0)
return;
// clear any restorable relations
clearOrphanRemovalRelations(sm, fmd, orphanRemoves, value);
}
/**
* Remove all relations between the initial value of <code>fmd</code> for
* the instance managed by <code>sm</code> and its inverses. Relations
* shared with <code>newValue</code> can be left intact.
*/
protected static void clearOrphanRemovalRelations(OpenJPAStateManager sm,
FieldMetaData fmd, FieldMetaData[] orphanRemoves, Object newValue) {
// don't bother clearing unflushed new instances
if (sm.isNew() && !sm.getFlushed().get(fmd.getIndex()))
return;
if (fmd.getDeclaredTypeCode() == JavaTypes.PC) {
Object initial = sm.fetchInitialField(fmd.getIndex());
clearInverseRelations(sm, initial, fmd, orphanRemoves);
} else {
Object obj = sm.fetchInitialField(fmd.getIndex());
Collection initial = null;
if (obj instanceof Collection)
initial = (Collection) obj;
else if (obj instanceof Map)
initial = ((Map)obj).values();
if (initial == null)
return;
// clear all relations not also in the new value
Collection coll = null;
if (newValue instanceof Collection)
coll = (Collection) newValue;
else if (newValue instanceof Map)
coll = ((Map)newValue).values();
Object elem;
for (Iterator itr = initial.iterator(); itr.hasNext();) {
elem = itr.next();
if (coll == null || !coll.contains(elem))
clearInverseRelations(sm, elem, fmd, orphanRemoves);
}
}
}
/**
* Clear all inverse the relations from <code>val</code> to the instance
* managed by <code>sm</code>.
*/
protected static void clearInverseRelations(OpenJPAStateManager sm, Object val,
FieldMetaData fmd, FieldMetaData[] orphanRemoves) {
if (val == null)
return;
OpenJPAStateManager other = sm.getContext().getStateManager(val);
if (other == null || other.isDeleted())
return;
for (int i = 0; i < orphanRemoves.length; i++) {
// if this is the owned side of the relation and has not yet been
// loaded, no point in setting it now, cause it'll have the correct
// value the next time it is loaded after the flush
switch (orphanRemoves[i].getDeclaredTypeCode()) {
case JavaTypes.PC:
if (fmd.getOrphanRemoval() || fmd.getElement().getOrphanRemoval())
((StateManagerImpl)other).delete();
break;
case JavaTypes.COLLECTION:
removeFromCollection(other, orphanRemoves[i],
sm.getManagedInstance());
break;
}
}
}
/**
* Remove the given instance from the collection.
*/
protected static void removeFromCollection(OpenJPAStateManager sm,
FieldMetaData fmd, Object val) {
Collection coll = (Collection) sm.fetchObjectField(fmd.getIndex());
if (coll != null)
coll.remove(val);
}
}

View File

@ -113,11 +113,12 @@ public class SaveFieldManager
mutable = true;
}
// if this is not an inverse field and the proper restore flag is
// not set, skip it
// if this is not an inverse field or a field subject to orphanRemoval
// and the proper restore flag is not set, skip it
if (_sm.getBroker().getInverseManager() == null
|| fmd.getInverseMetaDatas().length == 0) {
if ((_sm.getBroker().getInverseManager() == null
|| fmd.getInverseMetaDatas().length == 0) &&
(fmd.getOrphanRemovalMetaDatas().length == 0)) {
// use sm's restore directive, not broker's
int restore = _sm.getBroker().getRestoreState();
if (restore == RestoreState.RESTORE_NONE

View File

@ -481,6 +481,11 @@ class SingleFieldManager
InverseManager manager = _broker.getInverseManager();
if (manager != null)
manager.correctRelations(_sm, fmd, objval);
else {
FieldMetaData[] orphans = fmd.getOrphanRemovalMetaDatas();
if (orphans.length > 0)
OrphanRemovalManager.correctRelations(_sm, fmd, objval);
}
return ret;
}

View File

@ -105,6 +105,7 @@ public class StateManagerImpl
private static final int FLAG_VERSION_CHECK = 2 << 14;
private static final int FLAG_VERSION_UPDATE = 2 << 15;
private static final int FLAG_DETACHING = 2 << 16;
private static final int FLAG_ORPHAN_REMOVAL = 2 << 17;
private static final Localizer _loc = Localizer.forPackage
(StateManagerImpl.class);
@ -306,6 +307,10 @@ public class StateManagerImpl
if (_broker.getInverseManager() != null
&& fmds[i].getInverseMetaDatas().length > 0)
_flags |= FLAG_INVERSES;
// record whether there are any managed fields subject to
// orphan removal
if (fmds[i].getOrphanRemovalMetaDatas().length > 0)
_flags |= FLAG_ORPHAN_REMOVAL;
}
pc.pcSetDetachedState(null);
@ -510,6 +515,12 @@ public class StateManagerImpl
((OpenJPAId) oid).setManagedInstanceType(_meta.getDescribedType());
}
public StateManagerImpl getObjectIdOwner() {
StateManagerImpl sm = this;
while (sm.getOwner() != null)
sm = (StateManagerImpl) sm.getOwner();
return sm;
}
public boolean assignObjectId(boolean flush) {
lock();
try {
@ -853,9 +864,12 @@ public class StateManagerImpl
public Object fetchInitialField(int field) {
FieldMetaData fmd = _meta.getField(field);
boolean hasInverse = (_flags & FLAG_INVERSES) != 0
&& fmd.getInverseMetaDatas().length > 0;
boolean hasOrphanRemoval = (_flags & FLAG_ORPHAN_REMOVAL) != 0
&& fmd.getOrphanRemovalMetaDatas().length > 0;
if (_broker.getRestoreState() == RestoreState.RESTORE_NONE
&& ((_flags & FLAG_INVERSES) == 0
|| fmd.getInverseMetaDatas().length == 0))
&& (!hasInverse && !hasOrphanRemoval))
throw new InvalidStateException(_loc.get("restore-unset"));
switch (fmd.getDeclaredTypeCode()) {
@ -867,8 +881,7 @@ public class StateManagerImpl
case JavaTypes.OBJECT:
// if we're not saving mutable types, throw an exception
if (_broker.getRestoreState() != RestoreState.RESTORE_ALL
&& ((_flags & FLAG_INVERSES) == 0
|| fmd.getInverseMetaDatas().length == 0))
&& (!hasInverse && !hasOrphanRemoval))
throw new InvalidStateException(_loc.get
("mutable-restore-unset"));
}
@ -2666,7 +2679,8 @@ public class StateManagerImpl
*/
void saveFields(boolean immediate) {
if (_broker.getRestoreState() == RestoreState.RESTORE_NONE
&& (_flags & FLAG_INVERSES) == 0)
&& (((_flags & FLAG_INVERSES) == 0)
|| ((_flags & FLAG_ORPHAN_REMOVAL) == 0)))
return;
_flags |= FLAG_SAVE;
@ -2694,8 +2708,12 @@ public class StateManagerImpl
// if this is a managed inverse field, load it so we're sure to have
// the original value
if (!_loaded.get(field) && ((_flags & FLAG_INVERSES) != 0
&& _meta.getField(field).getInverseMetaDatas().length > 0))
FieldMetaData fmd = _meta.getField(field);
boolean hasInverse = (_flags & FLAG_INVERSES) != 0
&& fmd.getInverseMetaDatas().length > 0;
boolean hasOrphanRemoval = (_flags & FLAG_ORPHAN_REMOVAL) != 0
&& fmd.getOrphanRemovalMetaDatas().length > 0;
if (!_loaded.get(field) && (hasInverse || hasOrphanRemoval))
loadField(field, LockLevels.LOCK_NONE, false, false);
// don't bother creating the save field manager if we're not going to

View File

@ -210,6 +210,7 @@ public class FieldMetaData
// indicate if this field is used by other field as "order by" value
private boolean _usedInOrderBy = false;
private boolean _isElementCollection = false;
private FieldMetaData[] _orphanRemoves = null;
/**
* Constructor.
@ -911,6 +912,7 @@ public class FieldMetaData
break;
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
meta = _elem.getTypeMetaData();
break;
}
@ -979,6 +981,72 @@ public class FieldMetaData
return _inverses;
}
/**
* Return all orphanRemoval meta data of this field.
*/
public FieldMetaData[] getOrphanRemovalMetaDatas() {
if (_orphanRemoves == null) {
// get the metadata for the type on the other side of this relation
ClassMetaData meta = null;
switch (getTypeCode()) {
case JavaTypes.PC:
meta = getTypeMetaData();
break;
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
meta = _elem.getTypeMetaData();
break;
}
Collection orphanRemoves = null;
if (meta != null) {
// scan rel type for fields that name this field as an inverse
FieldMetaData[] fields = meta.getFields();
Class type = getOwnerType();
//Class type = getDeclaringMetaData().getDescribedType();
for (int i = 0; i < fields.length; i++) {
// skip fields that aren't compatible with our owning class
switch (fields[i].getTypeCode()) {
case JavaTypes.PC:
if (!type.isAssignableFrom(fields[i].getType()))
continue;
break;
case JavaTypes.COLLECTION:
case JavaTypes.ARRAY:
if (!type.isAssignableFrom(fields[i].
getElement().getType()))
continue;
break;
default:
continue;
}
if (orphanRemoves == null)
orphanRemoves = new ArrayList(3);
if (!orphanRemoves.contains(fields[i]))
orphanRemoves.add(fields[i]);
}
}
MetaDataRepository repos = getRepository();
if (orphanRemoves == null)
_orphanRemoves = repos.EMPTY_FIELDS;
else
_orphanRemoves = (FieldMetaData[]) orphanRemoves.toArray
(repos.newFieldMetaDataArray(orphanRemoves.size()));
}
return _orphanRemoves;
}
public Class getOwnerType() {
ClassMetaData owner = getDefiningMetaData();
ValueMetaData vm = owner.getEmbeddingMetaData();
if (vm == null)
return owner.getDescribedType();
FieldMetaData fmd = vm.getFieldMetaData();
return fmd.getOwnerType();
}
/**
* The strategy to use for insert value generation.
* One of the constants from {@link ValueStrategies}.
@ -1997,6 +2065,14 @@ public class FieldMetaData
public void setCascadeDelete(int delete) {
_val.setCascadeDelete(delete);
}
public boolean getOrphanRemoval() {
return _val.getOrphanRemoval();
}
public void setOrphanRemoval(boolean orphanRemoval) {
_val.setOrphanRemoval(orphanRemoval);
}
public int getCascadePersist() {
return _val.getCascadePersist();

View File

@ -302,4 +302,14 @@ public interface ValueMetaData
* information.
*/
public void copy(ValueMetaData vmd);
/**
* Whether this value is subject to orphanRemoval.
*/
public void setOrphanRemoval(boolean orphanRemoval);
/**
* Whether this value is subject to orphanRemoval.
*/
public boolean getOrphanRemoval();
}

View File

@ -57,6 +57,7 @@ public class ValueMetaDataImpl
private int _resMode = MODE_NONE;
private String _mappedBy = null;
private FieldMetaData _mappedByMeta = null;
private boolean _orphanRemoval = false;
protected ValueMetaDataImpl(FieldMetaData owner) {
_owner = owner;
@ -227,6 +228,14 @@ public class ValueMetaDataImpl
_delete = delete;
}
public boolean getOrphanRemoval() {
return _orphanRemoval;
}
public void setOrphanRemoval(boolean orphanRemoval) {
_orphanRemoval = orphanRemoval;
}
public int getCascadePersist() {
if (_owner.getManagement() != FieldMetaData.MANAGE_PERSISTENT)
return CASCADE_NONE;

View File

@ -1747,10 +1747,6 @@ public class AnnotationPersistenceMappingParser
* Parse @MapKeyColumn.
*/
protected void parseMapKeyColumn(FieldMapping fm, MapKeyColumn anno) {
if (!fm.isElementCollection())
throw new UnsupportedException(_loc.get("unsupported", fm,
anno.toString()));
int unique = 0;
Column col = new Column();
setupMapKeyColumn(fm, col, anno);

View File

@ -1330,6 +1330,7 @@ public class AnnotationPersistenceMetaDataParser
if (anno.targetEntity() != void.class)
fmd.setTypeOverride(anno.targetEntity());
setCascades(fmd, anno.cascade());
setOrphanRemoval(fmd, anno.orphanRemoval());
}
/**
@ -1368,6 +1369,7 @@ public class AnnotationPersistenceMetaDataParser
if (anno.targetEntity() != void.class)
fmd.getElement().setDeclaredType(anno.targetEntity());
setCascades(fmd.getElement(), anno.cascade());
setOrphanRemoval(fmd.getElement(), anno.orphanRemoval());
}
/**
@ -1563,6 +1565,11 @@ public class AnnotationPersistenceMetaDataParser
vmd.setCascadeRefresh(ValueMetaData.CASCADE_IMMEDIATE);
}
}
private void setOrphanRemoval(ValueMetaData vmd, boolean orphanRemoval) {
vmd.setOrphanRemoval(orphanRemoval);
if (orphanRemoval)
setCascades(vmd, new CascadeType[] {CascadeType.REMOVE});
}
/**
* Parse @SequenceGenerator.