/******************************************************************************* * 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.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; import org.eclipse.persistence.expressions.Expression; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.mappings.OneToOneMapping; import org.eclipse.persistence.queries.UpdateAllQuery; import org.eclipse.persistence.sessions.Session; import org.eclipse.persistence.queries.ReportQuery; import org.eclipse.persistence.sessions.UnitOfWork; import org.eclipse.persistence.queries.ReportQueryResult; import org.eclipse.persistence.sessions.DatabaseRecord; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.expressions.DataExpression; public class UpdateAllQueryTestHelper { public static String execute(Session mainSession, Class referenceClass, HashMap updateClauses, Expression selectionExpression) { return execute(mainSession, referenceClass, updateClauses, selectionExpression, true); } public static String execute(Session mainSession, Class referenceClass, HashMap updateClauses, Expression selectionExpression, boolean handleChildren) { return execute(mainSession, createUpdateAllQuery(referenceClass, updateClauses, selectionExpression), true); } public static String execute(Session mainSession, UpdateAllQuery uq) { return execute(mainSession, uq, true); } // Compares results of updating multiple objects yusing traditionalTopLink update one-by-one approach // with updating using UpdateAllQuery. If results differ retuns a non-null error message. // To use this method: // 1. Populate db with the objects on which update should be performed; // 2. In case handleChildren == true the similar query will also be tested with all subclasses: // example: for uq.referenceClass()==Project, the test will also run for SmallProject and LargeProject. // 3. After the test is completed it leaves the db in the original state (uses rollbacks). public static String execute(Session mainSession, UpdateAllQuery uq, boolean handleChildren) { // Find the inheritance root class - the test will compare all instances of this class // after traditional TopLink update (one-by-one) with all instances of this class // after UpdateAllQuery. The test succeeds if the two collections are equal. Class rootClass = uq.getReferenceClass(); ClassDescriptor descriptor = mainSession.getClassDescriptor(uq.getReferenceClass()); if(descriptor.hasInheritance()) { ClassDescriptor parentDescriptor = descriptor; while(!parentDescriptor.getInheritancePolicy().isRootParentDescriptor()) { parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor(); } rootClass = parentDescriptor.getJavaClass(); } String errorMsg = execute(mainSession, uq, handleChildren, rootClass); if(errorMsg.length() == 0) { return null; } else { return errorMsg; } } protected static String execute(Session mainSession, UpdateAllQuery uq, boolean handleChildren, Class rootClass) { String errorMsg = ""; ClassDescriptor descriptor = mainSession.getDescriptor(uq.getReferenceClass()); clearCache(mainSession); // original objects Vector objects = mainSession.readAllObjects(rootClass); // first update using the original TopLink approach - one by one. // That will be done using report query - it will use the same selection criteria // as UpdateAllQuery and each attribute will correspond to an update item. ReportQuery rq = new ReportQuery(uq.getReferenceClass(), uq.getExpressionBuilder()); rq.setSelectionCriteria(uq.getSelectionCriteria()); rq.setShouldRetrievePrimaryKeys(true); // some db platforms don't allow nulls in select clause - so add the fields with null values to the query result. Vector fieldsWithNullValues = new Vector(); Iterator itEntrySets = uq.getUpdateClauses().entrySet().iterator(); while(itEntrySets.hasNext()) { Map.Entry entry = (Map.Entry)itEntrySets.next(); Expression valueExpression; String keyString = getQualifiedFieldNameFromKey(entry.getKey(), rq.getReferenceClass(), descriptor, mainSession); Object value = entry.getValue(); DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForField(new DatabaseField(keyString)); if(mapping != null && mapping.isOneToOneMapping() && value != null) { // Note that this only works in case the reference PK is not compound if(((OneToOneMapping)mapping).getSourceToTargetKeyFields().size() > 1) { errorMsg = "Attribute "+ mapping.getAttributeName() + " mapped with 1to1 mapping that has more than one targetKeyField. UpdateAllQueryTestHelper currently doesn't support that."; } DatabaseField targetField = ((OneToOneMapping)mapping).getSourceToTargetKeyFields().get(new DatabaseField(keyString)); if(value instanceof Expression) { valueExpression = ((Expression)(((Expression)value).clone())).getField(targetField); } else { ClassDescriptor targetDescriptor = ((OneToOneMapping)mapping).getReferenceDescriptor(); Object fieldValue = targetDescriptor.getObjectBuilder().extractValueFromObjectForField(value, targetField, (org.eclipse.persistence.internal.sessions.AbstractSession)mainSession); valueExpression = rq.getExpressionBuilder().value(fieldValue); } } else { if(value instanceof Expression) { valueExpression = (Expression)value; } else { valueExpression = rq.getExpressionBuilder().value(value); } } if(value == null) { fieldsWithNullValues.add(keyString); } else { rq.addAttribute(keyString, valueExpression); } } UnitOfWork uow = mainSession.acquireUnitOfWork(); // mainSession could be a ServerSession Session session = uow.getParent(); // report query results contain the values to be assigned for each object to be updated. Vector result = (Vector)session.executeQuery(rq); Vector objectsAfterOneByOneUpdate = new Vector(objects.size()); ((org.eclipse.persistence.internal.sessions.AbstractSession)session).beginTransaction(); try { for (int i = 0; i < result.size(); i++) { // read through uow the object(clone) to be updated ReportQueryResult reportResult = (ReportQueryResult)result.elementAt(i); // hammer into the object the updated values Object obj = reportResult.readObject(rq.getReferenceClass(), uow); DatabaseRecord row = new DatabaseRecord(); for (int j = 0; j < reportResult.getNames().size(); j++) { String name = reportResult.getNames().get(j); DatabaseField field = new DatabaseField(name); Object value = reportResult.getResults().get(j); row.add(field, value); } // some db platforms don't allow nulls in select clause - so add the fields with null values to the query result for (int j = 0; j < fieldsWithNullValues.size(); j++) { String name = (String)fieldsWithNullValues.elementAt(j); DatabaseField field = new DatabaseField(name); row.add(field, null); } rq.getDescriptor().getObjectBuilder().assignReturnRow(obj, (AbstractSession)uow, row, null); } // uow committed - objects updated. uow.commit(); // Because the transaction will be rolled back (to return to the original state to execute UpdateAllQuery) // objects are copied into another vector - later it will be compared with UpdateAllQuery result. for(int i=0; i < objects.size(); i++) { Object original = objects.elementAt(i); Object copy = buildCopy(descriptor, original, uow); objectsAfterOneByOneUpdate.add(copy); } } finally { // transaction rolled back - objects back to the original state in the db. ((org.eclipse.persistence.internal.sessions.AbstractSession)session).rollbackTransaction(); } clearCache(mainSession); // now use UpdateAllQuery uow = mainSession.acquireUnitOfWork(); // mainSession could be a ServerSession session = uow.getParent(); Vector objectsAfterUpdateAll = new Vector(objects.size()); ((org.eclipse.persistence.internal.sessions.AbstractSession)session).beginTransaction(); try { uow.executeQuery(uq); // uow committed - objects updated. uow.commit(); // Because the transaction will be rolled back (to return to the original state) // objects are copied into another vector - it will be compared with update one-by-one result. for(int i=0; i < objects.size(); i++) { Object original = objects.elementAt(i); Object copy = buildCopy(descriptor, original, uow); objectsAfterUpdateAll.add(copy); } } finally { // transaction rolled back - objects back to the original state in the db. ((org.eclipse.persistence.internal.sessions.AbstractSession)session).rollbackTransaction(); } clearCache(mainSession); // verify String classErrorMsg = ""; for(int i=0; i < objects.size(); i++) { Object obj = objects.elementAt(i); Object obj1 = objectsAfterOneByOneUpdate.elementAt(i); Object obj2 = objectsAfterUpdateAll.elementAt(i); boolean equal = rq.getDescriptor().getObjectBuilder().compareObjects(obj, obj2, (org.eclipse.persistence.internal.sessions.AbstractSession)session); if(!equal) { classErrorMsg = classErrorMsg + "Difference: original = " + obj.toString() + "; afterOneByOneUpdate = " + obj1.toString() +"; afterUpdateAll = " + obj2.toString() + ";"; } } if(classErrorMsg.length() > 0) { errorMsg = errorMsg + classErrorMsg; } if(handleChildren) { if(descriptor.hasInheritance() && descriptor.getInheritancePolicy().hasChildren()) { Iterator it = descriptor.getInheritancePolicy().getChildDescriptors().iterator(); while(it.hasNext()) { ClassDescriptor childDescriptor = (ClassDescriptor)it.next(); Class childReferenceClass = childDescriptor.getJavaClass(); UpdateAllQuery childUq = (UpdateAllQuery)uq.clone(); childUq.setReferenceClass(childReferenceClass); childUq.setIsPrepared(false); errorMsg += execute(mainSession, childUq, handleChildren, rootClass); } } } return errorMsg; } protected static void clearCache(Session mainSession) { mainSession.getIdentityMapAccessor().initializeAllIdentityMaps(); } public static UpdateAllQuery createUpdateAllQuery(Class referenceClass, HashMap updateClauses, Expression selectionExpression) { // Construct UpdateAllQuery UpdateAllQuery uq = new UpdateAllQuery(referenceClass, selectionExpression); Iterator itEntrySets = updateClauses.entrySet().iterator(); while(itEntrySets.hasNext()) { Map.Entry entry = (Map.Entry)itEntrySets.next(); uq.addUpdate((String)entry.getKey(), entry.getValue()); } return uq; } static protected Object buildCopy(ClassDescriptor descriptor, Object original, UnitOfWork uow) { Object copy = descriptor.getCopyPolicy().buildClone(original, uow); descriptor.getObjectBuilder().copyInto(original, copy, true); return copy; } static protected String getQualifiedFieldNameFromKey(Object key, Class referenceClass, ClassDescriptor descriptor, Session session) { DatabaseField field = null; if(key instanceof String) { // attribute name String name = (String)key; DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForAttributeName(name); if(mapping != null) { field = mapping.getFields().firstElement(); } } else if(key instanceof DataExpression) { DataExpression fieldExpression = (DataExpression)key; field = descriptor.getObjectBuilder().getFieldForQueryKeyName(fieldExpression.getName()); if(field == null) { DataExpression fieldExpressionClone = (DataExpression)fieldExpression.clone(); fieldExpressionClone.getBuilder().setQueryClass(referenceClass); fieldExpressionClone.getBuilder().setSession((org.eclipse.persistence.internal.sessions.AbstractSession)session); field = fieldExpressionClone.getField(); } } if(field != null) { return field.getQualifiedName(); } // should never happen return null; } }