blob: 0e313f17e06203b2942ca31f22a01463a3062742 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.framework;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.indirection.ValueHolder;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.expressions.QueryKeyExpression;
import org.eclipse.persistence.internal.helper.Helper;
import java.util.*;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.InMemoryQueryIndirectionPolicy;
import org.eclipse.persistence.mappings.DatabaseMapping;
public class JoinedAttributeTestHelper {
/**
* Pass to this method controlQuery and query with joined attributes to be tested.
* The method executes both controlQuery and query with joined attributes and compares results.
* The errorMessage is returned - empty return means the result are equal.
* Note that comparison:
* takes into account all objects returned by queries;
* doesn't instantiate indirection;
* error is reported in case on query has indirection instantiated and the other doesn't.
* joinedAttributes expressions set on the second query used to read the relevant values into
* the result of controlQuery.
*
* A simple way to create control query the will be in sync with queryWithJoins to be tested:
* instantiate queryWithJoins - the one to be tested,
* set it's referenceClass, selectionCriteria,..., everything but joinedAttributes;
* clone queryWithJoins - this clone is controlQuery;
* now add joined attributes to queryWithJoins;
* pass controlQuery and queryWithJoins to this method.
*/
public static String executeQueriesAndCompareResults(ObjectLevelReadQuery controlQuery, ObjectLevelReadQuery queryWithJoins, AbstractSession session) {
session.logMessage("JoinedAttributeTestHelper: executing queryWithJoins:");
session.getIdentityMapAccessor().initializeAllIdentityMaps();
Object result = session.executeQuery(queryWithJoins);
session.logMessage("JoinedAttributeTestHelper: getting control result:");
Object controlResult = getControlResultsFromControlQuery(controlQuery, queryWithJoins, session);
String errorMsg = "";
if (controlResult instanceof Collection) {
errorMsg = compareCollections((Collection)controlResult, (Collection)result, controlQuery.getDescriptor(), session);
} else {
errorMsg = compareObjects(controlResult, result, session);
}
return errorMsg;
}
/**
* Pass to this method controlQuery and query with joined attributes to be tested.
* The method executes controlQuery and returns the results after triggering the relations
* that need to be joined (using the queryWithJoins).
*
* @see executeQueriesAndCompareResults
*/
public static Object getControlResultsFromControlQuery (ObjectLevelReadQuery controlQuery, ObjectLevelReadQuery queryWithJoins, AbstractSession session){
int valueHolderPolicy = InMemoryQueryIndirectionPolicy.SHOULD_TRIGGER_INDIRECTION;
session.getIdentityMapAccessor().initializeAllIdentityMaps();
// Need to execute the control query twice, once to determine objects excluded from outjoins,
// and once to instantiate indirection on only those objects not excluded (otherwise may instantiate indirection differently than join query).
Object controlResult = session.executeQuery(controlQuery);
boolean isCollection = false;
Collection collectionResult = null;
if (controlResult instanceof Collection) {
collectionResult = (Collection)controlResult;
isCollection = true;
} else {
collectionResult = new Vector(1);
collectionResult.add(controlResult);
}
Set excluded = new HashSet();
// Iterate over the result and add removed results to the excluded set.
for (Iterator iterator = collectionResult.iterator(); iterator.hasNext(); ) {
Object object = iterator.next();
boolean remove = false;
for (Iterator<Expression> joinsIterator = queryWithJoins.getJoinedAttributeManager().getJoinedAttributeExpressions().iterator(); joinsIterator.hasNext(); ) {
Expression joinExpression = joinsIterator.next();
joinExpression.getBuilder().setSession(session);
joinExpression.getBuilder().setQueryClass(queryWithJoins.getReferenceClass());
// Instantiate value holders that should be instantiated.
Object value = joinExpression.valueFromObject(object, session, null, valueHolderPolicy, false);
if (joinExpression.isQueryKeyExpression()) {
QueryKeyExpression queryKeyExpression = ((QueryKeyExpression)joinExpression);
// Iin case of NOT an outer join, remove objects with null / empty values.
if (!queryKeyExpression.shouldUseOuterJoin()) {
if (value == null) {
remove = true;
break;
} else if (value instanceof Collection) {
Collection collection = (Collection)value;
if (collection.isEmpty()) {
remove = true;
break;
} else if (!queryKeyExpression.shouldQueryToManyRelationship()) {
Iterator collectionIterator = collection.iterator();
while (collectionIterator.hasNext()) {
if (collectionIterator.next() == null) {
remove = true;
break;
}
}
if (remove == true) {
break;
}
}
}
}
}
}
if (remove) {
excluded.add(new CacheKey(session.getId(object)));
}
}
session.getIdentityMapAccessor().initializeAllIdentityMaps();
// Now execute and only instantiate indirection in non-excluded objects.
controlResult = session.executeQuery(controlQuery);
isCollection = false;
collectionResult = null;
if (controlResult instanceof Collection) {
collectionResult = (Collection)controlResult;
isCollection = true;
} else {
collectionResult = new Vector(1);
collectionResult.add(controlResult);
}
// Iterate over the result and instantiate all joined indirection.
for (Iterator iterator = collectionResult.iterator(); iterator.hasNext(); ) {
Object object = iterator.next();
if (excluded.contains(new CacheKey(session.getId(object)))) {
iterator.remove();
} else {
for (Iterator<Expression> joinsIterator = queryWithJoins.getJoinedAttributeManager().getJoinedAttributeExpressions().iterator(); joinsIterator.hasNext(); ) {
Expression joinExpression = joinsIterator.next();
// Instantiate value holders that should be instantiated.
joinExpression.valueFromObject(object, session, null, valueHolderPolicy, false);
}
}
}
session.getIdentityMapAccessor().initializeAllIdentityMaps();
if (isCollection){
return collectionResult;
} else {
return controlResult;
}
}
// The errorMessage is returned - empty return means the collections are equal.
// Note that comparison:
// takes into account all objects in collections: pks are extracted, objects with the same pks are compared.
public static String compareCollections(Collection col1, Collection col2, ClassDescriptor desc, AbstractSession session) {
Map processed = new IdentityHashMap();
return compareCollections(col1, col2, desc, session, processed);
}
// The errorMessage is returned - empty return means the objects are equal.
// Note that comparison:
// takes into account all objects referenced by the objects compared;
// doesn't instantiate indirection;
// error is reported in case on query has indirection instantiated and the other doesn't.
public static String compareObjects(Object obj1, Object obj2, AbstractSession session) {
Map processed = new IdentityHashMap();
return compareObjects(obj1, obj2, session, processed);
}
protected static String compareCollections(Collection col1, Collection col2, ClassDescriptor desc, AbstractSession session, Map processed) {
if(col1==null && col2==null) {
return "";
}
String errorMsg = "";
if(col1 != null) {
if(processed.containsKey(col1)) {
return "";
}
processed.put(col1, col1);
if(col2==null) {
errorMsg = ": " + col1.toString() + "!= null ; ";
return errorMsg;
}
}
if(col2 != null) {
if(processed.containsKey(col2)) {
return "";
}
processed.put(col2, col2);
if(col1 == null) {
errorMsg = ": null !=" + col2.toString() + "; ";
return errorMsg;
}
}
if(col1.size() != col2.size()) {
errorMsg = ": size1==" + Integer.toString(col1.size()) + "!= size2==" + Integer.toString(col2.size()) + "; ";
return errorMsg;
}
if(desc != null) {
// objects keyed by pks
HashMap map1 = new HashMap(col1.size());
HashMap map2 = new HashMap(col2.size());
ObjectBuilder builder = desc.getObjectBuilder();
Iterator it1 = col1.iterator();
Iterator it2 = col2.iterator();
while(it1.hasNext()) {
Object obj1 = it1.next();
Object obj2 = it2.next();
Object pk1 = builder.extractPrimaryKeyFromObject(obj1, session);
Object pk2 = builder.extractPrimaryKeyFromObject(obj2, session);
map1.put(pk1, obj1);
map2.put(pk2, obj2);
}
Iterator itEntries1 = map1.entrySet().iterator();
while(itEntries1.hasNext()) {
Map.Entry entry = (Map.Entry)itEntries1.next();
Object pk = entry.getKey();
Object obj1 = entry.getValue();
Object obj2 = map2.get(pk);
String objErrorMsg = compareObjects(obj1, obj2, session, processed);
if(objErrorMsg.length() > 0) {
errorMsg += "PK = " + pk.toString() + ": " + Helper.getShortClassName(obj1.getClass()) + objErrorMsg + " ";
}
}
} else {
// there's no target descriptor - compare collections directly
if(!col1.equals(col2)) {
errorMsg += "Collections " + col1.toString() + " and " + col2.toString() + " are not equal; ";
}
}
return errorMsg;
}
protected static String compareMaps(Map map1, Map map2, AbstractSession session, Map processed) {
if(map1==null && map2==null) {
return "";
}
String errorMsg = "";
if(map1 != null) {
if(processed.containsKey(map1)) {
return "";
}
processed.put(map1, map1);
if(map2==null) {
errorMsg = ": " + map1.toString() + "!= null ; ";
return errorMsg;
}
}
if(map2 != null) {
if(processed.containsKey(map2)) {
return "";
}
processed.put(map2, map2);
if(map1 == null) {
errorMsg = ": null !=" + map2.toString() + "; ";
return errorMsg;
}
}
if(map1.size() != map2.size()) {
errorMsg = ": size1==" + Integer.toString(map1.size()) + "!= size2==" + Integer.toString(map2.size()) + "; ";
return errorMsg;
}
Iterator itEntries1 = map1.entrySet().iterator();
while(itEntries1.hasNext()) {
Map.Entry entry = (Map.Entry)itEntries1.next();
Object key = entry.getKey();
Object obj1 = entry.getValue();
Object obj2 = map2.get(key);
String objErrorMsg = compareObjects(obj1, obj2, session, processed);
if(objErrorMsg.length() > 0) {
errorMsg += "Key = " + key.toString() + ": " + Helper.getShortClassName(obj1.getClass()) + objErrorMsg + " ";
}
}
return errorMsg;
}
protected static String compareObjects(Object obj1, Object obj2, AbstractSession session, Map processed) {
if(obj1==null && obj2==null) {
return "";
}
String errorMsg = "";
if(obj1 != null) {
if(processed.containsKey(obj1)) {
return "";
}
processed.put(obj1, obj1);
if(obj2==null) {
errorMsg = ": " + obj1.toString() + "!= null; ";
return errorMsg;
}
}
if(obj2 != null) {
if(processed.containsKey(obj2)) {
return "";
}
processed.put(obj2, obj2);
if(obj1 == null) {
errorMsg = ": null !=" + obj2.toString() + "; ";
return errorMsg;
}
}
if(obj1.getClass() != obj2.getClass()) {
errorMsg = ": " + obj1.getClass().getName() + "!=" + obj2.getClass().getName() + "; ";
return errorMsg;
}
ClassDescriptor desc = session.getDescriptor(obj1);
if(desc == null ) {
if (!obj1.equals(obj2)) {
errorMsg = ": " + obj1.toString() + "!=" +obj2.toString() + "; ";
}
return errorMsg;
}
Vector<DatabaseMapping> mappings = desc.getMappings();
for (int index = 0; index < mappings.size(); index++) {
DatabaseMapping mapping = mappings.get(index);
String mappingErrorMsg = compareAttributes(obj1, obj2, mapping, session, processed);
errorMsg += mappingErrorMsg;
}
return errorMsg;
}
protected static String compareAttributes(Object obj1, Object obj2, DatabaseMapping mapping, AbstractSession session, Map processed) {
String errorMsg = "";
if(mapping.isForeignReferenceMapping()) {
ForeignReferenceMapping frm = (ForeignReferenceMapping)mapping;
Object value1 = frm.getAttributeValueFromObject(obj1);
Object value2 = frm.getAttributeValueFromObject(obj2);
boolean isInstantiated1 = frm.getIndirectionPolicy().objectIsInstantiated(value1);
boolean isInstantiated2 = frm.getIndirectionPolicy().objectIsInstantiated(value2);
if(!isInstantiated1 && !isInstantiated2) {
return "";
} else if(isInstantiated1 && !isInstantiated2) {
if(frm.isOneToOneMapping() && value1 instanceof ValueHolder && ((ValueHolder)(value1)).getValue() == null) {
// In OneToOne case if the foreign key of the read object is null then ValueHolder (which is always instantiated) with value null is created
} else {
errorMsg = ": indirection instantiated != indirection NOT instantiated; ";
}
} else if(!isInstantiated1 && isInstantiated2) {
if(frm.isOneToOneMapping() && value2 instanceof ValueHolder && ((ValueHolder)(value2)).getValue() == null) {
// In OneToOne case if the foreign key of the read object is null then ValueHolder (which is always instantiated) with value null is created
} else {
errorMsg = ": indirection NOT instantiated != indirection instantiated; ";
}
} else {
value1 = frm.getRealAttributeValueFromObject(obj1, session);
value2 = frm.getRealAttributeValueFromObject(obj2, session);
if(frm.isCollectionMapping()) {
Class containerClass = frm.getContainerPolicy().getContainerClass();
if(Collection.class.isAssignableFrom(containerClass)) {
errorMsg += compareCollections((Collection)value1, (Collection)value2, frm.getReferenceDescriptor(), session, processed);
} else if(Map.class.isAssignableFrom(containerClass)) {
errorMsg += compareMaps((Map)value1, (Map)value2, session, processed);
} else {
errorMsg += mapping.toString() + " container class implements neither Collection nor Map - can't processl; ";
}
} else {
errorMsg += compareObjects(value1, value2, session, processed);
}
}
} else if (!mapping.compareObjects(obj1, obj2, session)) {
Object value1 = mapping.getAttributeValueFromObject(obj1);
if(value1 == null) {
value1 = new String("null");
}
Object value2 = mapping.getAttributeValueFromObject(obj2);
if(value2 == null) {
value2 = new String("null");
}
errorMsg = ": " + value1.toString() + "!=" + value2.toString() + "; ";
}
if(errorMsg.length() > 0) {
errorMsg = "." + mapping.getAttributeName() + errorMsg;
}
return errorMsg;
}
}