| /* |
| * 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==" + col1.size() + "!= size2==" + 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==" + map1.size() + "!= size2==" + 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; |
| } |
| } |