/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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.CollectionMapping; 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 joinsIterator = queryWithJoins.getJoinedAttributeManager().getJoinedAttributeExpressions().iterator(); joinsIterator.hasNext(); ) { Expression joinExpression = (Expression)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 joinsIterator = queryWithJoins.getJoinedAttributeManager().getJoinedAttributeExpressions().iterator(); joinsIterator.hasNext(); ) { Expression joinExpression = (Expression)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 mappings = desc.getMappings(); for (int index = 0; index < mappings.size(); index++) { DatabaseMapping mapping = (DatabaseMapping)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 = ((CollectionMapping)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; } }