org.eclipse.jubula.client.core.persistence.NodePM.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jubula.client.core.persistence.NodePM.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2010 BREDEX GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     BREDEX GmbH - initial API and implementation and/or initial documentation
 *******************************************************************************/
package org.eclipse.jubula.client.core.persistence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang.Validate;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jubula.client.core.i18n.Messages;
import org.eclipse.jubula.client.core.model.ICategoryPO;
import org.eclipse.jubula.client.core.model.IEventExecTestCasePO;
import org.eclipse.jubula.client.core.model.IExecObjContPO;
import org.eclipse.jubula.client.core.model.IExecTestCasePO;
import org.eclipse.jubula.client.core.model.INodePO;
import org.eclipse.jubula.client.core.model.IPersistentObject;
import org.eclipse.jubula.client.core.model.IProjectPO;
import org.eclipse.jubula.client.core.model.IRefTestSuitePO;
import org.eclipse.jubula.client.core.model.IReusedProjectPO;
import org.eclipse.jubula.client.core.model.ISpecObjContPO;
import org.eclipse.jubula.client.core.model.ISpecTestCasePO;
import org.eclipse.jubula.client.core.model.ITestSuitePO;
import org.eclipse.jubula.client.core.model.NodeMaker;
import org.eclipse.jubula.tools.constants.StringConstants;
import org.eclipse.jubula.tools.exception.InvalidDataException;
import org.eclipse.jubula.tools.exception.JBException;
import org.eclipse.jubula.tools.exception.JBFatalException;
import org.eclipse.jubula.tools.exception.ProjectDeletedException;
import org.eclipse.jubula.tools.messagehandling.MessageIDs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * class to persist und read testcase
 * 
 * @author BREDEX GmbH
 * @created 07.09.2004
 */
public class NodePM extends PersistenceManager {
    /**
     * Command for parent/child adding and removing
     */
    public abstract static class AbstractCmdHandleChild {
        /**
         * Template for this command. If executed add the child to the parent.
         * 
         * @param parent
         *            Node where the child should be added.
         * @param child
         *            Child to be added to parent.
         * @param pos
         *            where to insert the child, value null means insert after
         *            end
         */
        public abstract void add(INodePO parent, INodePO child, Integer pos);

        /**
         * Template for this command. If executed remove the child from the
         * parent. This method assumes that the child is to be deleted.
         * Calls dispose() on child!
         * @param parent
         *            Node where the child should be removed.
         * @param child
         *            Child to be removed from parent.
         */
        public void delete(INodePO parent, INodePO child) {
            remove(parent, child);
        }

        /**
         * Like delete, but the child is not to be deleted.
         * Does <b>not</b> call dispose() on child!
         * {@inheritDoc}
         */
        public abstract void remove(INodePO parent, INodePO child);

        /**
         * Sets the parentProjectId for a node inserted into another node.
         * @param child the child node
         * @param parent the parent node
         */
        public void setParentProjectId(INodePO child, INodePO parent) {
            Long parentProjectId = parent.getParentProjectId();
            if (parentProjectId == null) {
                parentProjectId = GeneralStorage.getInstance().getProject().getId();
            }
            child.setParentProjectId(parentProjectId);
        }
    }

    /**
     * {@inheritDoc}
     */
    public static class CmdHandleChildIntoNodeList extends AbstractCmdHandleChild {

        /**
         * {@inheritDoc}
         *      org.eclipse.jubula.client.core.model.INodePO)
         */
        public void add(INodePO parent, INodePO child, Integer pos) {
            parent.addNode(pos == null ? -1 : pos, child);
            setParentProjectId(child, parent);
        }

        /**
         * {@inheritDoc}
         *      org.eclipse.jubula.client.core.model.INodePO)
         * @param parent
         * @param child
         */
        public void remove(INodePO parent, INodePO child) {
            parent.removeNode(child);
        }
    }

    /**
     * {@inheritDoc}
     */
    public static class CmdHandleChildIntoSpecList extends AbstractCmdHandleChild {

        /**
         * {@inheritDoc}
         *      org.eclipse.jubula.client.core.model.INodePO)
         */
        public void add(INodePO parent, INodePO child, Integer pos) {
            // pos is not used here
            IProjectPO proj = GeneralStorage.getInstance().getProject();
            proj.getSpecObjCont().addSpecObject((ISpecPersistable) child);
            setParentProjectId(child, parent);
        }

        /**
         * {@inheritDoc}
         *      org.eclipse.jubula.client.core.model.INodePO)
         */
        public void remove(INodePO parent, INodePO child) {
            IProjectPO proj = GeneralStorage.getInstance().getProject();
            proj.getSpecObjCont().removeSpecObject((ISpecPersistable) child);
        }

    }

    /**
     * {@inheritDoc}
     */
    public static class CmdHandleChildIntoExecList extends AbstractCmdHandleChild {

        /**
         * {@inheritDoc}
         *      org.eclipse.jubula.client.core.model.INodePO)
         */
        public void add(INodePO parent, INodePO child, Integer pos) {
            // pos is not used here
            IProjectPO proj = GeneralStorage.getInstance().getProject();
            proj.getExecObjCont().addExecObject((IExecPersistable) child);
            setParentProjectId(child, parent);
        }

        /**
         * {@inheritDoc}
         *      org.eclipse.jubula.client.core.model.INodePO)
         */
        public void remove(INodePO parent, INodePO child) {
            IProjectPO proj = GeneralStorage.getInstance().getProject();
            proj.getExecObjCont().removeExecObject((IExecPersistable) child);
        }

    }

    /**
     * {@inheritDoc}
     */
    public static class CmdHandleEventHandlerIntoMap extends AbstractCmdHandleChild {
        /**
         * {@inheritDoc}
         * @param assocNode specTc which will use the evHandler
         * @param evHandler evHandler to add to assocNode
         * @param pos not required (use null)
         */
        public void add(INodePO assocNode, INodePO evHandler, Integer pos) {
            if (assocNode instanceof ISpecTestCasePO && evHandler instanceof IEventExecTestCasePO) {
                ISpecTestCasePO usingSpecTc = (ISpecTestCasePO) assocNode;
                try {
                    usingSpecTc.addEventTestCase((IEventExecTestCasePO) evHandler);
                    setParentProjectId(usingSpecTc, evHandler);
                } catch (InvalidDataException e) {
                    log.error(Messages.AttemptToAddAnEventhandlerTwice, e);
                }
            } else {
                throw new JBFatalException(Messages.WrongTypeForAdditionOfEventhandler,
                        MessageIDs.E_UNEXPECTED_EXCEPTION);
            }

        }

        /**
         * {@inheritDoc}
         * @param assocNode specTc which contains the evHandler
         * @param evHandler evHandler to remove from assocNode
         */
        public void remove(INodePO assocNode, INodePO evHandler) {
            if (assocNode instanceof ISpecTestCasePO && evHandler instanceof IEventExecTestCasePO) {
                ISpecTestCasePO usingSpecTc = (ISpecTestCasePO) assocNode;
                usingSpecTc.removeNode(evHandler);
            } else {
                throw new JBFatalException(Messages.WrongTypeForRemovalOfEventhandler,
                        MessageIDs.E_UNEXPECTED_EXCEPTION);
            }

        }
    }

    /**
     * class variable for Singleton
     */
    private static NodePM nodePersManager = null;

    /** the logger */
    private static Logger log = LoggerFactory.getLogger(NodePM.class);

    /** cache for project IDs */
    private Map<String, Long> m_projectIDCache = null;

    /** cache for SpecTCs */
    private Map<String, Object> m_specTCCache = null;

    /** Session used in last request */
    private EntityManager m_lastSession = null;

    /** is cache usage enabled */
    private boolean m_useCache = false;

    /**
     * getter for Singleton
     * 
     * @return single instance of CapPM problem of database
     */
    public static NodePM getInstance() {
        if (nodePersManager == null) {
            nodePersManager = new NodePM();
        }
        return nodePersManager;
    }

    /**
     * Factory for Commands
     * 
     * @param parent
     *            p
     * @param child
     *            c
     * @return C
     */
    public static AbstractCmdHandleChild getCmdHandleChild(INodePO parent, INodePO child) {
        Class parentNodePoClass = Persistor.getClass(parent);
        Class childNodePoClass = Persistor.getClass(child);
        if (parent == ISpecObjContPO.TCB_ROOT_NODE) {
            return new CmdHandleChildIntoSpecList();
        } else if (parent == IExecObjContPO.TSB_ROOT_NODE) {
            return new CmdHandleChildIntoExecList();
        } else if (Persistor.isPoClassSubclass(parentNodePoClass, ICategoryPO.class)) {
            // category/specTc in category
            return new CmdHandleChildIntoNodeList();
        } else if (Persistor.isPoClassSubclass(parentNodePoClass, ITestSuitePO.class)) {
            // execTc in testsuite
            return new CmdHandleChildIntoNodeList();
        } else if (Persistor.isPoClassSubclass(parentNodePoClass, ISpecTestCasePO.class)) {
            if (Persistor.isPoClassSubclass(childNodePoClass, IEventExecTestCasePO.class)) {
                // eventhandler in using specTc
                return new CmdHandleEventHandlerIntoMap();
            }
            // execTc or Cap in SpecTestCase
            return new CmdHandleChildIntoNodeList();
        }
        final String msg = Messages.UnsupportedINodePOSubclass;
        log.error(msg);
        throw new JBFatalException(msg, MessageIDs.E_UNSUPPORTED_NODE);
    }

    /**
     * Insert a child and persist to DB
     * 
     * @param parent
     *            parent of child to insert
     * @param child
     *            child to insert
     * @param pos
     *            where to insert the child. if null insert after end
     * @param handler
     *            Command for adding and removing a child
     * @throws PMSaveException
     *             in case of DB problem or refresh errors
     * @throws PMAlreadyLockedException in case of locked parent
     * @throws PMException in case of rollback failed
     * @throws ProjectDeletedException if the project was deleted in another
     * instance
     */
    public static void addAndPersistChildNode(INodePO parent, INodePO child, Integer pos,
            AbstractCmdHandleChild handler)
            throws PMSaveException, PMAlreadyLockedException, PMException, ProjectDeletedException {
        processAndPersistChildNode(parent, child, pos, handler, true);
    }

    /**
     * Insert a child and persist to DB
     * 
     * @param parent
     *            parent of child to insert
     * @param child
     *            child to insert
     * @param pos
     *            where to insert the child. if null insert after end
     * @param handler
     *            Command for adding and removing a child
     * @param doAdd
     *            specifies if an add operation should be performed. If false,
     *            the child node is removed.
     * @throws PMException in case of rollback failed
     * @throws ProjectDeletedException if the project was deleted in another
     * instance
     */
    private static void processAndPersistChildNode(INodePO parent, INodePO child, Integer pos,
            AbstractCmdHandleChild handler, boolean doAdd) throws PMException, ProjectDeletedException {
        EntityTransaction tx = null;
        IPersistentObject lockedObj = null;
        GeneralStorage gs = GeneralStorage.getInstance();
        final EntityManager sess = gs.getMasterSession();
        IProjectPO currentProject = gs.getProject();
        final Persistor persistor = Persistor.instance();
        if (parent == ISpecObjContPO.TCB_ROOT_NODE) {
            lockedObj = currentProject.getSpecObjCont();
        } else if (parent == IExecObjContPO.TSB_ROOT_NODE) {
            lockedObj = currentProject.getExecObjCont();
        } else {
            lockedObj = parent;
        }
        try {
            tx = persistor.getTransaction(sess);
            persistor.lockPO(sess, lockedObj);
            if (!doAdd) { // don't lock newly created POs 
                lockedObj = child;
                persistor.lockPO(sess, lockedObj);
            }
        } catch (PersistenceException e) {
            PersistenceManager.handleDBExceptionForMasterSession(lockedObj, e);
        }
        if (doAdd) {

            handler.add(parent, child, pos);
        } else {
            handler.delete(parent, child);
        }
        try {
            if (!doAdd) {
                sess.remove(child);
            }
            persistor.commitTransaction(sess, tx);
        } catch (PersistenceException e) {
            PersistenceManager.handleDBExceptionForMasterSession(null, e);
        }
    }

    /**
     * @param node
     *            the node to be renamed
     * @param newName
     *            the new name
     * @throws PMDirtyVersionException
     *             in case of dirty version
     * @throws PMAlreadyLockedException
     *             in case of locked node
     * @throws PMSaveException
     *             in case of DB save error
     * @throws PMException in case of general db error
     * @throws ProjectDeletedException if the project was deleted in another
     * instance            
     */
    public static void renameNode(INodePO node, String newName) throws PMDirtyVersionException,
            PMAlreadyLockedException, PMSaveException, PMException, ProjectDeletedException {

        EntityManager sess = GeneralStorage.getInstance().getMasterSession();
        EntityTransaction tx = null;
        try {
            final Persistor persistor = Persistor.instance();
            tx = persistor.getTransaction(sess);
            persistor.lockPO(sess, node);
            node.setName(newName);
            persistor.commitTransaction(sess, tx);
        } catch (PersistenceException e) {
            PersistenceManager.handleDBExceptionForMasterSession(node, e);
        }
    }

    /**
     * @param node
     *            the node to be renamed
     * @param newComment
     *            the new comment
     * @throws PMDirtyVersionException
     *             in case of dirty version
     * @throws PMAlreadyLockedException
     *             in case of locked node
     * @throws PMSaveException
     *             in case of DB save error
     * @throws PMException in case of general db error
     * @throws ProjectDeletedException if the project was deleted in another
     * instance            
     */
    public static void setComment(INodePO node, String newComment) throws PMDirtyVersionException,
            PMAlreadyLockedException, PMSaveException, PMException, ProjectDeletedException {

        EntityManager sess = GeneralStorage.getInstance().getMasterSession();
        EntityTransaction tx = null;
        try {
            final Persistor persistor = Persistor.instance();
            tx = persistor.getTransaction(sess);
            persistor.lockPO(sess, node);
            node.setComment(newComment);
            persistor.commitTransaction(sess, tx);
        } catch (PersistenceException e) {
            PersistenceManager.handleDBExceptionForMasterSession(node, e);
        }
    }

    /**
     * @param cat category, which is the parent of the testcases to import
     * @param specObjList list with testcases to import
     * @throws PMException in case of a problem while import
     * @throws ProjectDeletedException if the project was deleted in another
     * instance
     */
    public static void addImportedTestCases(ICategoryPO cat, List<? extends INodePO> specObjList)
            throws PMException, ProjectDeletedException {

        final GeneralStorage genStorage = GeneralStorage.getInstance();
        IProjectPO currentProject = genStorage.getProject();
        for (INodePO specObj : specObjList) {
            cat.addNode(specObj);
        }
        AbstractCmdHandleChild handler = getCmdHandleChild(currentProject, cat);
        addAndPersistChildNode(currentProject, cat, null, handler);
    }

    /**
     * @param type
     *            the type of elements to find
     * @param parentProjectId
     *            ID of the parent project to search in
     * @param s
     *            The session into which the INodePOs will be loaded.
     * @return list of param all INodePOs
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static List<? extends INodePO> computeListOfNodes(Class type, Long parentProjectId, EntityManager s) {
        Assert.isNotNull(type);
        Assert.isNotNull(s);
        CriteriaQuery query = s.getCriteriaBuilder().createQuery();
        Root from = query.from(type);
        query.select(from).where(s.getCriteriaBuilder().equal(from.get("hbmParentProjectId"), parentProjectId)); //$NON-NLS-1$

        List<INodePO> queryResult = s.createQuery(query).getResultList();
        return queryResult;
    }

    /**
     * Returns test cases that reference the test case given information. 
     * Only returns test cases that are in the same project as the given test 
     * case. These test cases are loaded in the Master Session.
     * Warning: the fetched ExecTestCases have no parent, because the database
     * doesn't know the parent.
     * 
     * @param specTcGuid GUID of the test case being reused.
     * @param parentProjectId ID of the parent project of the test case being
     *                        reused.
     * @return all test cases that reference the test case with the given
     *         information, provided that the cases are also in the same 
     *         project.
     * @see getAllExecTestCases
     * @see getExternalExecTestCases
     */
    public static List<IExecTestCasePO> getInternalExecTestCases(String specTcGuid, long parentProjectId) {

        // a SpecTC with guid == null can't be reused
        if (specTcGuid == null) {
            return new ArrayList<IExecTestCasePO>(0);
        }

        List<Long> parentProjectIds = new ArrayList<Long>();
        parentProjectIds.add(parentProjectId);

        return getExecTestCasesFor(specTcGuid, parentProjectIds, GeneralStorage.getInstance().getMasterSession());
    }

    /**
     * Returns ref test suites that reference the test suites given information.
     * Only returns ref test cases that are in the same project as the given
     * test suite. These ref test suites are loaded in the Master Session.
     * Warning: the fetched ref test suites have no parent, because the database
     * doesn't know the parent.
     * 
     * @param tsGuid
     *            GUID of the test case being reused.
     * @param parentProjectId
     *            ID of the parent project of the test case being reused.
     * @return all ref test suites that reference the test suite with the given
     *         information, provided that the cases are also in the same
     *         project.
     */
    public static List<IRefTestSuitePO> getInternalRefTestSuites(String tsGuid, long parentProjectId) {
        // a test suite with guid == null can't be reused
        if (tsGuid == null) {
            return new ArrayList<IRefTestSuitePO>(0);
        }

        List<Long> parentProjectIds = new ArrayList<Long>();
        parentProjectIds.add(parentProjectId);

        return getRefTestSuitesFor(tsGuid, parentProjectIds, GeneralStorage.getInstance().getMasterSession());
    }

    /**
     * 
     * @param tsGuid The GUID of the reused test suite.
     * @param parentProjectIds All returned test cases will have one of these as
     *                         their project parent ID.
     * @param s The session into which the test cases will be loaded.
     * @return list of test suites.
     */
    @SuppressWarnings("unchecked")
    private static synchronized List<IRefTestSuitePO> getRefTestSuitesFor(String tsGuid,
            List<Long> parentProjectIds, EntityManager s) {

        StringBuffer queryBuffer = new StringBuffer(
                "select ref from RefTestSuitePO as ref where ref.testSuiteGuid = :tsGuid and ("); //$NON-NLS-1$

        for (long id : parentProjectIds) {
            queryBuffer.append("ref.hbmParentProjectId = " + id + " or "); //$NON-NLS-1$ //$NON-NLS-2$
        }
        // Remove the last " or ", and close the statement
        queryBuffer.delete(queryBuffer.length() - 4, queryBuffer.length());
        queryBuffer.append(")"); //$NON-NLS-1$
        Query q = s.createQuery(queryBuffer.toString());
        q.setParameter("tsGuid", tsGuid); //$NON-NLS-1$
        List<IRefTestSuitePO> execTcList = q.getResultList();
        return execTcList;
    }

    /**
     * Returns test cases that reference the test case given information. Only
     * returns test cases that are in the same project as the given test case
     * including test cases from reused projects. These test cases are loaded in
     * the Master Session. Warning: the fetched ExecTestCases have no parent,
     * because the database doesn't know the parent.
     * 
     * @param specTcGuid
     *            GUID of the test case being reused.
     * @param parentProjectIds
     *            IDs of the parent projects of the test case being reused.
     * @return all test cases that reference the test case with the given
     *         information, provided that the cases are also in the same
     *         project or reused projects.
     * @see getAllExecTestCases
     * @see getExternalExecTestCases
     * @see getInternalExecTestCases
     */
    public static List<IExecTestCasePO> getExecTestCases(String specTcGuid, List<Long> parentProjectIds) {

        // a SpecTC with guid == null can't be reused
        if (specTcGuid == null) {
            return new ArrayList<IExecTestCasePO>(0);
        }

        return getExecTestCasesFor(specTcGuid, parentProjectIds, GeneralStorage.getInstance().getMasterSession());
    }

    /**
     * Returns test cases that reference the test case given information. 
     * Only returns test cases that are <em>NOT</em> in the same project 
     * as the given test case.
     * This method opens a new session to gather the test cases and then closes
     * the session in order to prevent accidental DB commits for test cases
     * external to the current project.
     * Warning: the fetched ExecTestCases have no parent, because the database
     * doesn't know the parent.
     * 
     * @param specTcGuid GUID of the test case being reused.
     * @param parentProjectId ID of the parent project of the test case being
     *                        reused.
     * @return all test cases that reference the test case with the given
     *         information, provided that the cases are <em>NOT</em> in the 
     *         same project.
     * @see getInternalExecTestCases
     * @see getAllExecTestCases
     */
    public static List<IExecTestCasePO> getExternalExecTestCases(String specTcGuid, long parentProjectId)
            throws JBException {

        // a SpecTC with guid == null can't be reused
        if (specTcGuid == null) {
            return new ArrayList<IExecTestCasePO>(0);
        }

        EntityManager s = Persistor.instance().openSession();

        try {
            EntityTransaction tx = Persistor.instance().getTransaction(s);

            IProjectPO parentProject = s.find(NodeMaker.getProjectPOClass(), parentProjectId);

            if (parentProject == null) {
                String error = Messages.ParentProjectDoesNotExistWithID + StringConstants.COLON
                        + StringConstants.SPACE + parentProjectId;
                log.error(error);
                throw new JBException(error, MessageIDs.E_DATABASE_GENERAL);
            }

            List<Long> projectsThatReuse = ProjectPM.findIdsThatReuse(parentProject.getGuid(),
                    parentProject.getMajorProjectVersion(), parentProject.getMinorProjectVersion());

            List<IExecTestCasePO> tcList = getExecTestCasesFor(specTcGuid, projectsThatReuse, s);

            Persistor.instance().commitTransaction(s, tx);

            return tcList;

        } finally {
            Persistor.instance().dropSessionWithoutLockRelease(s);
        }
    }

    /**
     * 
     * @param specTcGuid The GUID of the reused test case.
     * @param parentProjectIds All returned test cases will have one of these as
     *                         their project parent ID.
     * @param s The session into which the test cases will be loaded.
     * @return list of test cases.
     */
    @SuppressWarnings("unchecked")
    private static synchronized List<IExecTestCasePO> getExecTestCasesFor(String specTcGuid,
            List<Long> parentProjectIds, EntityManager s) {

        StringBuffer queryBuffer = new StringBuffer(
                "select ref from ExecTestCasePO as ref where ref.specTestCaseGuid = :specTcGuid and ("); //$NON-NLS-1$

        for (long id : parentProjectIds) {
            queryBuffer.append("ref.hbmParentProjectId = " + id + " or "); //$NON-NLS-1$ //$NON-NLS-2$
        }
        // Remove the last " or ", and close the statement
        queryBuffer.delete(queryBuffer.length() - 4, queryBuffer.length());
        queryBuffer.append(")"); //$NON-NLS-1$
        Query q = s.createQuery(queryBuffer.toString());
        q.setParameter("specTcGuid", specTcGuid); //$NON-NLS-1$
        List<IExecTestCasePO> execTcList = q.getResultList();

        return execTcList;

    }

    /**
     * Returns all test cases that reference the test case given information. 
     * This method opens a new session to gather the test cases and then closes
     * the session in order to prevent accidental DB commits for test cases
     * external to the current project.
     * Warning: the fetched ExecTestCases have no parent, because the database
     * doesn't know the parent.
     * @param specTcGuid GUID of the test case being reused.
     * @param parentProjectId ID of the parent project of the test case being
     *                        reused.
     * @return <em>all</em> test cases that reference the test case with the 
     *         given information, regardless of which project they are in.
     * @see getInternalExecTestCases
     * @see getExternalExecTestCases
     */
    public static List<IExecTestCasePO> getAllExecTestCases(String specTcGuid, long parentProjectId)
            throws JBException {
        // a SpecTC with guid == null can't be reused
        if (specTcGuid == null) {
            return new ArrayList<IExecTestCasePO>(0);
        }

        EntityManager s = Persistor.instance().openSession();

        try {
            EntityTransaction tx = Persistor.instance().getTransaction(s);

            IProjectPO parentProject = s.find(NodeMaker.getProjectPOClass(), parentProjectId);

            if (parentProject == null) {
                String error = Messages.ParentProjectDoesNotExistWithID + StringConstants.COLON
                        + StringConstants.SPACE + parentProjectId;
                log.error(error);
                throw new JBException(error, MessageIDs.E_DATABASE_GENERAL);
            }

            List<Long> projectsThatReuse = ProjectPM.findIdsThatReuse(parentProject.getGuid(),
                    parentProject.getMajorProjectVersion(), parentProject.getMinorProjectVersion());
            // Project technically "reuses" itself
            projectsThatReuse.add(parentProjectId);

            List<IExecTestCasePO> tcList = getExecTestCasesFor(specTcGuid, projectsThatReuse, s);

            Persistor.instance().commitTransaction(s, tx);

            return tcList;
        } finally {
            Persistor.instance().dropSessionWithoutLockRelease(s);
        }
    }

    /**
     * 
     * @param project the project
     * @param reused the reused project
     * @return list of exec test cases in the given project that use 
     *         specTestCases from the given reused project
     */
    @SuppressWarnings("unchecked")
    public static synchronized List<IExecTestCasePO> getUsedTestCaseNames(IProjectPO project,
            IReusedProjectPO reused) {

        if (project == null) {
            return new ArrayList<IExecTestCasePO>();
        }

        EntityManager s = GeneralStorage.getInstance().getMasterSession();
        Query q = s
                .createQuery("select ref from ExecTestCasePO as ref where ref.hbmParentProjectId = :parentProjectId" //$NON-NLS-1$
                        + " and ref.projectGuid = :projectGuid"); //$NON-NLS-1$
        q.setParameter("parentProjectId", project.getId()); //$NON-NLS-1$
        q.setParameter("projectGuid", reused.getProjectGuid()); //$NON-NLS-1$

        List<IExecTestCasePO> result = q.getResultList();
        return result;

    }

    /**
     * 
     * @param project proj
     * @return read-only list of ISpecPersistable objects
     */
    public static synchronized List<ISpecPersistable> loadSpecObjList(IProjectPO project) {

        if (project == null) {
            return new ArrayList<ISpecPersistable>();
        }

        EntityManager s = GeneralStorage.getInstance().getMasterSession();
        Query q = s.createQuery(
                "select cont from SpecObjContPO as cont where cont.hbmParentProjectId = :parentProjectId"); //$NON-NLS-1$
        q.setParameter("parentProjectId", project.getId()); //$NON-NLS-1$

        try {
            return ((ISpecObjContPO) q.getSingleResult()).getSpecObjList();
        } catch (NoResultException nre) {
            return new ArrayList<ISpecPersistable>();
        }

    }

    /**
     * Finds a test case within reused projects.
     * @param reusedProjects Set of reused projects that are available.
     * @param projectGuid The GUID of the parent project of the spec testcase
     * @param specTcGuid The GUID of the spec testcase
     * @return the spec testcase with the given guid, or <code>null</code> if 
     *         the testcase cannot be found
     */
    public static synchronized ISpecTestCasePO getSpecTestCase(Set<IReusedProjectPO> reusedProjects,
            String projectGuid, String specTcGuid) {

        Integer majorNumber = null;
        Integer minorNumber = null;
        for (IReusedProjectPO reusedProj : reusedProjects) {
            if (reusedProj.getProjectGuid().equals(projectGuid)) {
                majorNumber = reusedProj.getMajorNumber();
                minorNumber = reusedProj.getMinorNumber();
                break;
            }
        }

        if (majorNumber == null) {
            return null;
        }

        EntityManager s = GeneralStorage.getInstance().getMasterSession();
        Long projectId = NodePM.getInstance().findProjectID(s, projectGuid, majorNumber, minorNumber);
        if (projectId == null) {
            return null;
        }
        Object result = NodePM.getInstance().findSpecTC(s, specTcGuid, projectId);
        if (result instanceof ISpecTestCasePO) {
            return (ISpecTestCasePO) result;
        }

        return null;
    }

    /**
     * find and cache a referenced spec TC
     * @param s Session
     * @param specTcGuid GUID of TC
     * @param projectId ID of project containing TC
     * @return the resulting TC or null if none was found
     */
    private Object findSpecTC(EntityManager s, String specTcGuid, Long projectId) {
        validateSession(s);

        StringBuilder idBuilder = new StringBuilder(50);
        idBuilder.append(specTcGuid);
        idBuilder.append(':');
        idBuilder.append(projectId);
        String key = idBuilder.toString();

        if (m_useCache) {
            Object cached = m_specTCCache.get(key);
            if (cached != null) {
                if (cached instanceof INodePO) { // check for not found
                    return cached;
                }
                return null;
            }
        }
        Query specTcQuery = s.createQuery("select node from SpecTestCasePO as node where node.guid = :guid" //$NON-NLS-1$
                + " and node.hbmParentProjectId = :projectId"); //$NON-NLS-1$
        specTcQuery.setParameter("guid", specTcGuid); //$NON-NLS-1$
        specTcQuery.setParameter("projectId", projectId); //$NON-NLS-1$

        Object result = null;

        try {
            result = specTcQuery.getSingleResult();
        } catch (NoResultException nre) {
            // No result found. The result remains null.
        }

        if (m_useCache) {
            if (result != null) {
                m_specTCCache.put(key, result);
            } else {
                m_specTCCache.put(key, new Object()); // set a not found marker
            }
        }
        return result;
    }

    /**
     * find and cache a reused projects OID
     * @param s Session
     * @param projectGuid GUID of project
     * @param majorNumber version number
     * @param minorNumber version number
     * @return the OID of the project or null if the project cannot be found
     */
    private Long findProjectID(EntityManager s, String projectGuid, Integer majorNumber, Integer minorNumber) {
        validateSession(s);

        String key = buildProjectKey(projectGuid, majorNumber, minorNumber);

        if (m_useCache) {
            Long id = m_projectIDCache.get(key);
            if (id != null) {
                if (id.longValue() != -1) { // means already lookuped but not
                                            // found
                    return id;
                }
                return null;
            }
        }
        Query projIdQuery = s.createQuery("select project from ProjectPO as project" //$NON-NLS-1$
                + " inner join fetch project.properties where project.guid = :guid" //$NON-NLS-1$
                + " and project.properties.majorNumber = :majorNumber and project.properties.minorNumber = :minorNumber"); //$NON-NLS-1$           
        projIdQuery.setParameter("guid", projectGuid); //$NON-NLS-1$
        projIdQuery.setParameter("majorNumber", majorNumber); //$NON-NLS-1$
        projIdQuery.setParameter("minorNumber", minorNumber); //$NON-NLS-1$
        try {
            final Object uniqueResult = projIdQuery.getSingleResult();
            Long projectId = ((IProjectPO) uniqueResult).getId();
            if (m_useCache) {
                m_projectIDCache.put(key, projectId);
            }
            return projectId;
        } catch (NoResultException nre) {
            if (m_useCache) {
                m_projectIDCache.put(key, new Long(-1));
            }
            return null;
        }
    }

    /**
     * checks if the Session was used before and discards caches if not
     * @param s Session
     */
    private void validateSession(EntityManager s) {
        if (m_useCache && m_lastSession != s) {
            resetCaching();
            m_lastSession = s;
        }
    }

    /**
     * clears all caches
     */
    private void resetCaching() {
        if (m_projectIDCache != null) {
            m_projectIDCache.clear();
            m_projectIDCache = null;
        }
        if (m_specTCCache != null) {
            m_specTCCache.clear();
            m_specTCCache = null;
        }
        m_lastSession = null;
        if (m_useCache) {
            m_projectIDCache = new HashMap<String, Long>(20);
            m_specTCCache = new HashMap<String, Object>(500);
        }
    }

    /**
     * build a key for the cache
     * 
     * @param projectGuid
     *            part
     * @param majorNumber
     *            part
     * @param minorNumber
     *            part
     * @return a key combined from the parts
     */
    private static String buildProjectKey(String projectGuid, Integer majorNumber, Integer minorNumber) {
        StringBuilder idBuilder = new StringBuilder(200);
        idBuilder.append(projectGuid);
        idBuilder.append(StringConstants.COLON);
        idBuilder.append(majorNumber);
        idBuilder.append(StringConstants.COLON);
        idBuilder.append(minorNumber);
        return idBuilder.toString();
    }

    /**
     * Finds a test case within the project with the given ID.
     * @param projectId The ID of the parent project of the spec testcase
     * @param specTcGuid The GUID of the spec testcase
     * @return the spec testcase with the given guid, or <code>null</code> if 
     *         the testcase cannot be found
     */
    public static synchronized ISpecTestCasePO getSpecTestCase(Long projectId, String specTcGuid) {

        EntityManager s = GeneralStorage.getInstance().getMasterSession();
        return (ISpecTestCasePO) NodePM.getInstance().findSpecTC(s, specTcGuid, projectId);
    }

    /**
     * Finds a Test Suite within the currently opened project.
     * 
     * @param testSuiteGuid The GUID of the Test Suite.
     * @return the Test Suite with the given GUID, or <code>null</code> if 
     *         no such Test Suite can be found.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static synchronized ITestSuitePO getTestSuite(String testSuiteGuid) {

        GeneralStorage gs = GeneralStorage.getInstance();
        IProjectPO currentProject = gs.getProject();
        if (currentProject != null) {
            EntityManager s = gs.getMasterSession();

            CriteriaBuilder builder = s.getCriteriaBuilder();
            CriteriaQuery query = builder.createQuery();
            Root from = query.from(NodeMaker.getTestSuitePOClass());
            query.select(from).where(builder.like(from.get("guid"), testSuiteGuid), //$NON-NLS-1$
                    builder.equal(from.get("hbmParentProjectId"), currentProject.getId())); //$NON-NLS-1$

            try {
                Object result = s.createQuery(query).getSingleResult();
                if (result instanceof ITestSuitePO) {
                    return (ITestSuitePO) result;
                }
            } catch (NoResultException nre) {
                // No result found. Fall through to return null as per javadoc.
            }
        }

        return null;
    }

    /**
     * Finds a node within the project with the given ID.
     * @param projectId The ID of the parent project of the spec testcase
     * @param nodeGuid The GUID of the node
     * @return the spec testcase with the given guid, or <code>null</code> if 
     *         the testcase cannot be found
     */
    public static synchronized INodePO getNode(Long projectId, String nodeGuid) {
        return getNode(projectId, nodeGuid, GeneralStorage.getInstance().getMasterSession());
    }

    /**
     * Finds a node within the project with the given ID.
     * @param projectId The ID of the parent project of the spec testcase
     * @param nodeGuid The GUID of the node
     * @param session may not be null
     * @return the spec testcase with the given guid, or <code>null</code> if 
     *         the testcase cannot be found
     */
    public static synchronized INodePO getNode(Long projectId, String nodeGuid, EntityManager session) {

        Validate.notNull(session);

        Query specTcQuery = session.createQuery("select node from NodePO node where node.guid = :guid" //$NON-NLS-1$
                + " and node.hbmParentProjectId = :projectId"); //$NON-NLS-1$
        specTcQuery.setParameter("guid", nodeGuid); //$NON-NLS-1$
        specTcQuery.setParameter("projectId", projectId); //$NON-NLS-1$

        try {
            Object result = specTcQuery.getSingleResult();
            if (result instanceof INodePO) {
                return (INodePO) result;
            }
        } catch (NoResultException nre) {
            // No result found. Fall through to return null.
        }
        return null;
    }

    /**
     * Loads a bag of Nodes into the given session and returns the loaded
     * Nodes.
     * 
     * @param projectId The Project in which to search for the Nodes.
     * @param guids The GUIDs for which to load Nodes.
     * @param session The session into which to load the Nodes.
     * @return the loaded Nodes, mapped by GUID. GUIDs for which no node could
     *         be found are mapped to <code>null</code>.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static synchronized Map<String, INodePO> getNodes(Long projectId, Collection<String> guids,
            EntityManager session) {

        CriteriaQuery query = session.getCriteriaBuilder().createQuery();
        Root from = query.from(NodeMaker.getNodePOClass());
        Predicate parentProjectPred = session.getCriteriaBuilder().equal(from.get("hbmParentProjectId"), projectId); //$NON-NLS-1$
        Predicate guidDisjunction = PersistenceUtil.getExpressionDisjunction(guids, from.get("guid"), //$NON-NLS-1$
                session.getCriteriaBuilder());
        query.select(from).where(parentProjectPred, guidDisjunction);

        List<INodePO> nodeList = session.createQuery(query).getResultList();

        Map<String, INodePO> guidToNodeMap = new HashMap<String, INodePO>();
        for (INodePO node : nodeList) {
            String guid = node.getGuid();
            if (!guidToNodeMap.containsKey(guid)) {
                guidToNodeMap.put(guid, node);
            }
        }

        return guidToNodeMap;
    }

    /**
     * @param parentProjectId The ID of the project for which to find the number
     *                        of nodes.
     * @param sess The session in which to perform the query.
     * @return The number of nodes that have the project for the given ID as 
     *         an absolute parent.
     */
    public static long getNumNodes(long parentProjectId, EntityManager sess) {

        Query specTcQuery = sess.createQuery("select count(node) from NodePO as " //$NON-NLS-1$
                + "node where node.hbmParentProjectId = :parentProjectId"); //$NON-NLS-1$
        specTcQuery.setParameter("parentProjectId", parentProjectId); //$NON-NLS-1$

        try {
            return (Long) specTcQuery.getSingleResult();
        } catch (NoResultException nre) {
            return 0;
        }

    }

    /**
     * @param parentProjectId The ID of the project for which to find the number
     *                        of TD managers.
     * @param sess The session in which to perform the query.
     * @return The number of TD managers that have the project for the 
     *         given ID as an absolute parent.
     */
    public static long getNumTestDataManagers(long parentProjectId, EntityManager sess) {

        Query tdManQuery = sess.createQuery("select count(tdMan) from TDManagerPO " //$NON-NLS-1$
                + "tdMan where tdMan.hbmParentProjectId = :parentProjectId"); //$NON-NLS-1$
        tdManQuery.setParameter("parentProjectId", parentProjectId); //$NON-NLS-1$

        try {
            return (Long) tdManQuery.getSingleResult();
        } catch (NoResultException nre) {
            return 0;
        }
    }

    /**
     * @param parentProjectId The ID of the project for which to find the number
     *                        of execTCs.
     * @param sess The session in which to perform the query.
     * @return The number of execTCs that have the project for the 
     *         given ID as an absolute parent.
     */
    public static long getNumExecTestCases(long parentProjectId, EntityManager sess) {

        Query execTcQuery = sess.createQuery("select count(execTc) from ExecTestCasePO as " //$NON-NLS-1$
                + "execTc where execTc.hbmParentProjectId = :parentProjectId"); //$NON-NLS-1$
        execTcQuery.setParameter("parentProjectId", parentProjectId); //$NON-NLS-1$

        try {
            return (Long) execTcQuery.getSingleResult();
        } catch (NoResultException nre) {
            return 0;
        }
    }

    /**
     * @param parentProjectId The ID of the project for which to find the number
     *                        of execTCs.
     * @param sess The session in which to perform the query.
     * @return The number of execTCs that have the project for the 
     *         given ID as an absolute parent.
     */
    public static long getNumExecTestCasesWithRefTd(long parentProjectId, EntityManager sess) {

        Query execTcQuery = sess.createQuery("select count(execTc) from ExecTestCasePO as " //$NON-NLS-1$
                + "execTc where execTc.hbmParentProjectId = :parentProjectId and execTc.hasReferencedTD = :hasReferencedTD"); //$NON-NLS-1$
        execTcQuery.setParameter("parentProjectId", parentProjectId); //$NON-NLS-1$
        execTcQuery.setParameter("hasReferencedTD", true); //$NON-NLS-1$

        try {
            return (Long) execTcQuery.getSingleResult();
        } catch (NoResultException nre) {
            return 0;
        }
    }

    /**
     * @param useCache should the cache be used
     */
    public void setUseCache(boolean useCache) {
        m_useCache = useCache;
        resetCaching();
    }

    /**
     * @return true if the internal cache is in use
     */
    public boolean isUseCache() {
        return m_useCache;
    }
}