OPENJPA-1736: Mappings with foreign keys as identity fields sometimes not resolved correctly

back-out changes in 2.0.x branch.
must be pre-approved before committing changes to 2.0.x.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/branches/2.0.x@988277 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Catalina Wei 2010-08-23 19:45:14 +00:00
parent 82c9f910b5
commit f0c38d38a5
10 changed files with 306 additions and 225 deletions

View File

@ -64,6 +64,7 @@ public class Compatibility {
private boolean _superclassDiscriminatorStrategyByDefault = true; private boolean _superclassDiscriminatorStrategyByDefault = true;
private boolean _isAbstractMappingUniDirectional = false; private boolean _isAbstractMappingUniDirectional = false;
private boolean _isNonDefaultMappingAllowed = false; private boolean _isNonDefaultMappingAllowed = false;
private boolean _reorderMetaDataResolution = true;
private boolean _reloadOnDetach = false; private boolean _reloadOnDetach = false;
private boolean _ignoreDetachedStateFieldForProxySerialization = false; private boolean _ignoreDetachedStateFieldForProxySerialization = false;
@ -507,6 +508,26 @@ public class Compatibility {
public boolean isNonDefaultMappingAllowed() { public boolean isNonDefaultMappingAllowed() {
return _isNonDefaultMappingAllowed; return _isNonDefaultMappingAllowed;
} }
/**
* Whether OpenJPA should reorder entities in MetaDataRepository.processBuffer() to ensure that the metadata for
* entities with foreign keys in their identity are processed after the entities it depends on.
*
* @return true if the reordering should be performed, false if not.
*/
public boolean getReorderMetaDataResolution() {
return _reorderMetaDataResolution;
}
/**
* Whether OpenJPA should reorder entities in MetaDataRepository.processBuffer() to ensure that the metadata for
* entities with foreign keys in their identity are processed after the entities it depends on.
*
* @param reorderProcessBuffer true if the reordering should be performed, false if not.
*/
public void setReorderMetaDataResolution(boolean reorderProcessBuffer) {
_reorderMetaDataResolution = reorderProcessBuffer;
}
/** /**
* Whether OpenJPA should attempt to load fields when the DetachState * Whether OpenJPA should attempt to load fields when the DetachState

View File

@ -0,0 +1,74 @@
/*
* 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.meta;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.io.Serializable;
public class InheritanceOrderedMetaDataList
implements Serializable {
private MetaDataInheritanceComparator _comp
= new MetaDataInheritanceComparator();
private LinkedList<ClassMetaData> buffer = new LinkedList<ClassMetaData>();
public boolean add(ClassMetaData meta) {
if (meta == null || buffer.contains(meta))
return false;
for (ListIterator<ClassMetaData> itr = buffer.listIterator();
itr.hasNext();) {
int ord = _comp.compare(meta, itr.next());
if (ord > 0)
continue;
if (ord == 0)
return false;
itr.previous();
itr.add(meta);
return true;
}
buffer.add(meta);
return true;
}
public boolean remove(ClassMetaData meta) {
return buffer.remove(meta);
}
public ClassMetaData peek() {
return buffer.peek();
}
public int size() {
return buffer.size();
}
public Iterator<ClassMetaData> iterator() {
return buffer.iterator();
}
public boolean isEmpty() {
return buffer.isEmpty();
}
public void clear() {
buffer.clear();
}
}

View File

@ -141,8 +141,8 @@ public class MetaDataRepository implements PCRegistry.RegisterClassListener, Con
private final Collection<Class<?>> _registered = new HashSet<Class<?>>(); private final Collection<Class<?>> _registered = new HashSet<Class<?>>();
// set of metadatas we're in the process of resolving // set of metadatas we're in the process of resolving
private final List<ClassMetaData> _resolving = new ArrayList<ClassMetaData>(); private final InheritanceOrderedMetaDataList _resolving = new InheritanceOrderedMetaDataList();
private final List<ClassMetaData> _mapping = new ArrayList<ClassMetaData>(); private final InheritanceOrderedMetaDataList _mapping = new InheritanceOrderedMetaDataList();
private final List<RuntimeException> _errs = new LinkedList<RuntimeException>(); private final List<RuntimeException> _errs = new LinkedList<RuntimeException>();
// system listeners // system listeners
@ -152,6 +152,8 @@ public class MetaDataRepository implements PCRegistry.RegisterClassListener, Con
protected boolean _preloadComplete = false; protected boolean _preloadComplete = false;
protected boolean _locking = true; protected boolean _locking = true;
private static final String PRELOAD_STR = "Preload"; private static final String PRELOAD_STR = "Preload";
private boolean _reorderMetaDataResolution = false;
/** /**
* Default constructor. Configure via {@link Configurable}. * Default constructor. Configure via {@link Configurable}.
@ -768,28 +770,22 @@ public class MetaDataRepository implements PCRegistry.RegisterClassListener, Con
/** /**
* Process the given metadata and the associated buffer. * Process the given metadata and the associated buffer.
*/ */
private List<ClassMetaData> processBuffer(ClassMetaData meta, List<ClassMetaData> buffer, int mode) { private List<ClassMetaData> processBuffer(ClassMetaData meta, InheritanceOrderedMetaDataList buffer, int mode) {
// add the metadata to the buffer unless an instance for the same entity
// is already there
for (ClassMetaData cmd : buffer)
if (cmd.getDescribedType().equals(meta.getDescribedType()))
return null;
// if we're already processing a metadata, just buffer this one; when // if we're already processing a metadata, just buffer this one; when
// the initial metadata finishes processing, we traverse the buffer // the initial metadata finishes processing, we traverse the buffer
// and process all the others that were introduced during reentrant // and process all the others that were introduced during reentrant
// calls // calls
buffer.add(meta); if (!buffer.add(meta) || buffer.size() != 1)
if (buffer.size() != 1)
return null; return null;
// continually pop a metadata and process it until we run out; note // continually pop a metadata and process it until we run out; note
// that each processing call might place more metas in the buffer as // that each processing call might place more metas in the buffer as
// one class tries to access metadata for another // one class tries to access metadata for another; also note that the
// buffer orders itself from least to most derived
ClassMetaData buffered; ClassMetaData buffered;
List<ClassMetaData> processed = new ArrayList<ClassMetaData>(5); List<ClassMetaData> processed = new ArrayList<ClassMetaData>(5);
while (!buffer.isEmpty()) { while (!buffer.isEmpty()) {
buffered = buffer.get(0); buffered = buffer.peek();
try { try {
buffered.resolve(mode); buffered.resolve(mode);
processed.add(buffered); processed.add(buffered);
@ -812,6 +808,11 @@ public class MetaDataRepository implements PCRegistry.RegisterClassListener, Con
} }
} }
// Check if process buffer reordering for PCTypes that have relationships to other PCTypes in their identity
// should be performed.
if (_reorderMetaDataResolution) {
processed = resolveFKInPKDependenciesOrdering(processed);
}
return processed; return processed;
} }
@ -1843,6 +1844,7 @@ public class MetaDataRepository implements PCRegistry.RegisterClassListener, Con
public void setConfiguration(Configuration conf) { public void setConfiguration(Configuration conf) {
_conf = (OpenJPAConfiguration) conf; _conf = (OpenJPAConfiguration) conf;
_log = _conf.getLog(OpenJPAConfiguration.LOG_METADATA); _log = _conf.getLog(OpenJPAConfiguration.LOG_METADATA);
_reorderMetaDataResolution = _conf.getCompatibilityInstance().getReorderMetaDataResolution();
} }
public void startConfiguration() { public void startConfiguration() {
@ -2411,6 +2413,196 @@ public class MetaDataRepository implements PCRegistry.RegisterClassListener, Con
public XMLFieldMetaData newXMLFieldMetaData(Class<?> type, String name) { public XMLFieldMetaData newXMLFieldMetaData(Class<?> type, String name) {
return new XMLFieldMetaData(type, name); return new XMLFieldMetaData(type, name);
} }
/**
* Analyzes the list of ClassMetaData in the supplied list for any which has foreign keys to other ClassMetaData
* instances in its identity (in other words, PCTypes which have primary keys that are foreign keys to other
* tables), and returns a list arranged so that a ClassMetaData that depends on another ClassMetaData appears
* after it in the list.
*
* @param cmdList - List of ClassMetaData to examine
* @return - List of ClassMetaData, with ClassMetaData dependees moved after the last identified dependent
* ClassMetaData, if any move is necessary.
*/
private List<ClassMetaData> resolveFKInPKDependenciesOrdering(List<ClassMetaData> cmdList) {
HashMap<ClassMetaData, CMDDependencyNode> nodeMap = new HashMap<ClassMetaData, CMDDependencyNode>();
HashSet<CMDDependencyNode> nodesWithDependenciesSet = new HashSet<CMDDependencyNode>();
ArrayList<CMDDependencyNode> nodeList = new ArrayList<CMDDependencyNode>(cmdList.size());
// Initial analysis of ClassMetaData objects -- Populate the linked list with objects in the same order of
// appearance in the original list. Identify CMDs whose identities have a FK to another CMD, and catalog that
// dependency.
for (ClassMetaData cmd : cmdList) {
// Add this node to the list
CMDDependencyNode node = nodeMap.get(cmd);
if (node == null) {
node = new CMDDependencyNode(cmd);
nodeMap.put(cmd, node);
}
nodeList.add(node);
// Examine its primary key fields, flag any references to another PCType that is defined in cmdList as a
// dependency
FieldMetaData[] fmdArr = cmd.getPrimaryKeyFields();
for (FieldMetaData fmd : fmdArr) {
ValueMetaData vmd = fmd.getValue();
if (vmd.isTypePC()) {
ClassMetaData targetCMD = vmd.getDeclaredTypeMetaData();
// Only process entries which are in the cmdList, as we don't want to be adding anything new.
if (!cmdList.contains(targetCMD)) {
continue;
}
// Register the dependency
CMDDependencyNode targetNode = null;
if ((targetNode = nodeMap.get(targetCMD)) == null) {
targetNode = new CMDDependencyNode(targetCMD);
nodeMap.put(targetCMD, targetNode);
}
node.registerDependentNode(targetNode);
nodesWithDependenciesSet.add(node);
}
}
}
// Analysis is complete. For each CMD that has an identity foreign key dependency on another CMD, ensure that it
// appears later in the list then the CMD it is dependent on. If it appears earlier, move it immediately after
// the CMD. If there are multiple CMDs the identity is dependent on, move it after the last dependency in
// the linked list.
for (CMDDependencyNode node : nodesWithDependenciesSet) {
// Check if there is a cycle (dependencies or subdependencies that create a cycle in the graph. If one is
// detected, then this algorithm cannot be used to reorder the CMD list. Emit a warning, and return the
// original list.
if (node.checkForCycle()) {
if (_log.isWarnEnabled()) {
_log.warn(_loc.get("cmd-discover-cycle", node.getCmd().getResourceName()));
}
return cmdList;
}
int nodeIndex = nodeList.indexOf(node);
Set<CMDDependencyNode> dependencies = node.getDependsOnSet();
// If the current node has a dependency that appears later in the list, then this node needs
// to be moved to the point immediately after that dependency.
CMDDependencyNode moveAfter = null;
int moveAfterIndex = -1;
for (CMDDependencyNode depNode : dependencies) {
int dependencyIndex = nodeList.indexOf(depNode);
if ((nodeIndex < dependencyIndex) && (moveAfterIndex < dependencyIndex)) {
moveAfter = depNode;
moveAfterIndex = dependencyIndex;
}
}
if (moveAfter != null) {
nodeList.remove(nodeIndex);
nodeList.add(nodeList.indexOf(moveAfter) + 1, node);
}
}
// Sorting is complete, build the return list. Clear the dependsOnSet for the GC.
ArrayList<ClassMetaData> returnList = new ArrayList<ClassMetaData>();
for (CMDDependencyNode current : nodeList) {
returnList.add(current.getCmd());
current.getDependsOnSet().clear();
}
return returnList;
}
/**
* Linked list node class for managing any foreign keys in the identity of a ClassMetaData instance.
*
*/
private class CMDDependencyNode {
private ClassMetaData cmd;
// Marker for quick determination if this node has dependencies
private boolean hasDependencies = false;
// List of ClassMetaData objects this ClassMetaData depends on
private HashSet<CMDDependencyNode> dependsOnSet = new HashSet<CMDDependencyNode>();
/**
* Inner class constructor
*/
CMDDependencyNode(ClassMetaData cmd) {
this.cmd = cmd;
}
/**
* Returns the ClassMetaData instance referenced by this node.
*/
public ClassMetaData getCmd() {
return cmd;
}
/**
*
* @return true if this node's ClassMetaData has a FK in its identity that refers to another ClassMetaData;
* false if it does not.
*/
public boolean getHasDependencies() {
return hasDependencies;
}
/**
* Registers a ClassMetaData modelled by a CMDDependencyNode as a dependency of this ClassMetaData.
*
*/
public void registerDependentNode(CMDDependencyNode node) {
getDependsOnSet().add(node);
hasDependencies = true;
}
/**
* Returns a Set containing all of the CMDDependencyNode instances that this node has a FK in identity
* dependency on.
*
*/
public Set<CMDDependencyNode> getDependsOnSet() {
return dependsOnSet;
}
/**
* Checks all dependencies, and sub-dependencies, for any cycles in the dependency graph.
*
* @return true if a cycle was discovered, false if not.
*/
public boolean checkForCycle() {
java.util.Stack<CMDDependencyNode> visitStack = new java.util.Stack<CMDDependencyNode>();
return internalCheckForCycle(visitStack);
}
/**
* Internal implementation of the cycle detection.
*
* @param visitStack
* @return true if a cycle is detected, false if no cycle was detected.
*/
private boolean internalCheckForCycle(java.util.Stack<CMDDependencyNode> visitStack) {
if (visitStack.contains(this)) {
return true;
}
visitStack.push(this);
try {
for (CMDDependencyNode node : dependsOnSet) {
if (node.getHasDependencies()) {
if (node.internalCheckForCycle(visitStack) == true) {
return true;
}
}
}
} finally {
visitStack.pop();
}
return false;
}
}
public static boolean needsPreload(Options o) { public static boolean needsPreload(Options o) {
if (o.getBooleanProperty(PRELOAD_STR) == true) { if (o.getBooleanProperty(PRELOAD_STR) == true) {

View File

@ -348,6 +348,9 @@ repos-preload-none: No persistent metadata found for loading during initializati
repos-preloading: Following metadata are being loaded during initialization by "{0}": {1}. repos-preloading: Following metadata are being loaded during initialization by "{0}": {1}.
repos-preload-error: Unexpected error during early loading during initialization. \ repos-preload-error: Unexpected error during early loading during initialization. \
See nested stacktrace for details. See nested stacktrace for details.
cmd-discover-cycle: A cycle was detected while resolving the identity \
references for type "{0}". The original process buffer ordering \
will be used.
repos-initializeEager-none: No persistent metadata found for loading during initialization. \ repos-initializeEager-none: No persistent metadata found for loading during initialization. \
The persistent classes must be listed in persistence unit configuration to be loaded during initialization. The persistent classes must be listed in persistence unit configuration to be loaded during initialization.
repos-initializeEager-found: The following classes are being preloaded "{0}". repos-initializeEager-found: The following classes are being preloaded "{0}".

View File

@ -28,7 +28,9 @@ import org.apache.openjpa.persistence.test.SingleEMFTestCase;
public class TestEntityAsIdentityFields extends SingleEMFTestCase { public class TestEntityAsIdentityFields extends SingleEMFTestCase {
public void setUp() { public void setUp() {
setUp(Account.class, AccountGroup.class, Person.class); setUp(
Account.class, AccountGroup.class, Person.class,
"openjpa.Compatibility", "reorderMetaDataResolution=true");
} }
/** /**

View File

@ -1,65 +0,0 @@
/*
* 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.persistence.identity.entityasidentity2;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "EAI2Attendance")
@IdClass(Attendance.AttendanceId.class)
public class Attendance {
public static class AttendanceId {
private int student;
private int course;
public AttendanceId() {}
public AttendanceId(int studentId, int courseId) {
this.student = studentId;
this.course = courseId;
}
public String toString() {
return student + ":" + course;
}
public int hashCode() {
return (17 + student) * 37 + course;
}
public boolean equals(Object other) {
return this == other
|| other instanceof AttendanceId
&& student == ((AttendanceId) other).student
&& course == ((AttendanceId) other).course;
}
}
@Id @ManyToOne
Student student;
@Id @ManyToOne
Course course;
}

View File

@ -1,37 +0,0 @@
/*
* 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.persistence.identity.entityasidentity2;
import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "EAI2Course")
public class Course {
@Id
int id;
@OneToMany(mappedBy = "course")
Collection<Attendance> attendances;
}

View File

@ -1,31 +0,0 @@
/*
* 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.persistence.identity.entityasidentity2;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "EAI2Person")
public class Person {
@Id
int id;
}

View File

@ -1,38 +0,0 @@
/*
* 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.persistence.identity.entityasidentity2;
import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name = "EAI2Student")
public class Student {
@Id @OneToOne
Person person;
@OneToMany(mappedBy = "student")
Collection<Attendance> attendances;
}

View File

@ -1,40 +0,0 @@
/*
* 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.persistence.identity.entityasidentity2;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
public class TestEntityAsIdentityFields2 extends SingleEMFTestCase {
public void setUp() {
setUp(Attendance.class, Course.class, Person.class, Student.class);
}
public void testEntityAsIdentityField001() {
EntityManager em = emf.createEntityManager();
Query query = em.createQuery("select p from Person p");
query.getResultList();
em.close();
}
}