/*******************************************************************************
 * Copyright (c) 1998, 2009 Oracle. 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.Iterator;
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.sessions.Session;
import org.eclipse.persistence.queries.DeleteAllQuery;
import org.eclipse.persistence.sessions.UnitOfWork;
 
public class DeleteAllQueryTestHelper {
            
    public static String execute(Session mainSession, Class referenceClass, Expression selectionExpression) {
        return execute(mainSession, referenceClass, selectionExpression, true);
    }

    public static String execute(Session mainSession, Class referenceClass, Expression selectionExpression, boolean shouldDeferExecutionInUOW) {
        return execute(mainSession, referenceClass, selectionExpression, shouldDeferExecutionInUOW, true);
    }

    // mainSession - the ServerSession used.
    // referenceClass - the reference class of DeleteAllQuery to be tested
    // selectionExpression - selection expression of DeleteAllQuery to be tested
    // shouldDeferExecutionInUOW==true causes deferring query execution until uow.commit;
    // shouldDeferExecutionInUOW==false causes immediate query execution;
    // shouldHandleChildren==true means the test will be executed not only with the specified class,
    // but also with all its subclasses.
    // Each test will test DeleteAllQuery with the specified reference class
    // and all its subclasses
    // Example: for Vehicle.class  9 DeleteAllQueries will be tested.
    // shouldHandleChildren==false means the test will be executed with the specified class only.
    //
    // For each DeleteAllQuery its results compared with one-by-one deletion:
    //  selectionCriteria is used to select objects and then delete them one-by-one (using uow.deleteObject);
    //  the results are saved and after compared with DeleteAllQuery results:
    //    both inCache and inDb comparison performed;
    //    both deleted and remained objects should be the same;
    public static String execute(Session mainSession, Class referenceClass, Expression selectionExpression, boolean shouldDeferExecutionInUOW, boolean handleChildren) {
        Class rootClass = referenceClass;
        ClassDescriptor descriptor = mainSession.getClassDescriptor(referenceClass);
        if(descriptor.hasInheritance()) {
            ClassDescriptor parentDescriptor = descriptor;
            while(!parentDescriptor.getInheritancePolicy().isRootParentDescriptor()) {
                parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor();
            }
            rootClass = parentDescriptor.getJavaClass();
        }
        String errorMsg = execute(mainSession, referenceClass, selectionExpression, shouldDeferExecutionInUOW, handleChildren, rootClass);

        if(errorMsg.length() == 0) {
            return null;
        } else {
            return errorMsg;
        }
    }
    
    protected static String execute(Session mainSession, Class referenceClass, Expression selectionExpression, boolean shouldDeferExecutionInUOW, boolean handleChildren,
                                    Class rootClass) {
        String errorMsg = "";        
        clearCache(mainSession);
        
        // first delete using the original TopLink approach - one by one.
        UnitOfWork uow = mainSession.acquireUnitOfWork();
        // mainSession could be a ServerSession
        Session session = uow.getParent();
        
        // Will need to bring the db back to its original state
        // so that comparison of the deletion result would be possible.
        ((AbstractSession)session).beginTransaction();
        
        Vector objectsToDelete = uow.readAllObjects(referenceClass, selectionExpression);
        
        ClassDescriptor descriptor = mainSession.getClassDescriptor(referenceClass);
        
        uow.deleteAllObjects(objectsToDelete);
        mainSession.logMessage("***delete one by one");
        uow.commit();
        
        Vector objectsLeftAfterOriginalDeletion = session.readAllObjects(rootClass);

        ((AbstractSession)session).rollbackTransaction();

        // now delete using DeleteAllQuery.
        clearCache(mainSession);
        
        // bring all objects into cache
        session.readAllObjects(rootClass);
        
        uow = mainSession.acquireUnitOfWork();
        // mainSession could be a ServerSession
        session = uow.getParent();
        
        // Will need to bring the db back to its original state
        // so that the in case thre are children descriptors
        // they would still have objects to work with.
        ((AbstractSession)session).beginTransaction();
        
        DeleteAllQuery query = new DeleteAllQuery(referenceClass, selectionExpression);
        query.setShouldDeferExecutionInUOW(shouldDeferExecutionInUOW);
        uow.executeQuery(query);
        mainSession.logMessage("***DeleteAllQuery for class " + referenceClass.getName());
        uow.commit();
        
        // verify that cache invalidation worked correctly:
        // deleted objects should've disappeared, others remain
        String classErrorMsg = "";
        for(int i=0; i < objectsToDelete.size(); i++) {
            Object deletedObject = session.readObject(objectsToDelete.elementAt(i));
            if(deletedObject != null) {
                classErrorMsg = classErrorMsg + "Deleted object "+ deletedObject +" is stil in cache; ";
                break;
            }
        }
        for(int i=0; i < objectsLeftAfterOriginalDeletion.size(); i++) {
            Object remainingObject = objectsLeftAfterOriginalDeletion.elementAt(i);
            Object remainingObjectRead = session.readObject(remainingObject);
            if(remainingObjectRead == null) {
                classErrorMsg = classErrorMsg + "Remaining object " + remainingObject +" is not in cache; ";
                break;
            }
        }
        
        // now let's verify that the objects were correctly deleted from the db
        clearCache(mainSession);
        // deleted objects should've disappeared, others remain
        for(int i=0; i < objectsToDelete.size(); i++) {
            Object deletedObject = session.readObject(objectsToDelete.elementAt(i));
            if(deletedObject != null) {
                classErrorMsg = classErrorMsg + "Deleted object "+ deletedObject + " is stil in db; ";
                break;
            }
        }
        for(int i=0; i < objectsLeftAfterOriginalDeletion.size(); i++) {
            Object remainingObject = objectsLeftAfterOriginalDeletion.elementAt(i);
            Object remainingObjectRead = session.readObject(remainingObject);
            if(remainingObjectRead == null) {
                classErrorMsg = classErrorMsg + "Remaining object " + remainingObject +" is not in db; ";
                break;
            }
        }

        ((AbstractSession)session).rollbackTransaction();
        
        if(classErrorMsg.length() > 0) {
            String className = referenceClass.getName();
            String shortClassName = className.substring(className.lastIndexOf('.') + 1);
            errorMsg = errorMsg + " " + shortClassName + ": " + 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();
                    errorMsg += execute(mainSession, childReferenceClass, selectionExpression, shouldDeferExecutionInUOW, handleChildren, rootClass);
                }
            }
        }
        return errorMsg;
    }
    
    protected static void clearCache(Session mainSession) {
        mainSession.getIdentityMapAccessor().initializeAllIdentityMaps();
    }
}
