org.squashtest.tm.service.internal.batchimport.Model.java Source code

Java tutorial

Introduction

Here is the source code for org.squashtest.tm.service.internal.batchimport.Model.java

Source

/**
 *     This file is part of the Squashtest platform.
 *     Copyright (C) 2010 - 2016 Henix, henix.fr
 *
 *     See the NOTICE file distributed with this work for additional
 *     information regarding copyright ownership.
 *
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     this software is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.squashtest.tm.service.internal.batchimport;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.MultiValueMap;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.type.LongType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.squashtest.tm.core.foundation.lang.PathUtils;
import org.squashtest.tm.domain.NamedReference;
import org.squashtest.tm.domain.customfield.BindableEntity;
import org.squashtest.tm.domain.customfield.CustomField;
import org.squashtest.tm.domain.library.structures.LibraryGraph;
import org.squashtest.tm.domain.library.structures.LibraryGraph.SimpleNode;
import org.squashtest.tm.domain.milestone.Milestone;
import org.squashtest.tm.domain.milestone.MilestoneStatus;
import org.squashtest.tm.domain.project.Project;
import org.squashtest.tm.domain.requirement.Requirement;
import org.squashtest.tm.domain.requirement.RequirementVersion;
import org.squashtest.tm.domain.testcase.Dataset;
import org.squashtest.tm.domain.testcase.Parameter;
import org.squashtest.tm.domain.testcase.ParameterAssignationMode;
import org.squashtest.tm.domain.testcase.TestCase;
import org.squashtest.tm.domain.testcase.TestStep;
import org.squashtest.tm.service.importer.Target;
import org.squashtest.tm.service.internal.batchimport.TestCaseCallGraph.Node;
import org.squashtest.tm.service.internal.repository.CustomFieldDao;
import org.squashtest.tm.service.internal.repository.DatasetDao;
import org.squashtest.tm.service.internal.testcase.TestCaseCallTreeFinder;
import org.squashtest.tm.service.milestone.MilestoneMembershipFinder;
import org.squashtest.tm.service.requirement.RequirementLibraryFinderService;
import org.squashtest.tm.service.requirement.RequirementVersionManagerService;
import org.squashtest.tm.service.testcase.ParameterFinder;
import org.squashtest.tm.service.testcase.TestCaseFinder;
import org.squashtest.tm.service.testcase.TestCaseLibraryFinderService;

@Component
@Scope("prototype")
public class Model {

    @PersistenceContext
    private EntityManager em;

    @Inject
    private CustomFieldDao cufDao;

    @Inject
    private TestCaseLibraryFinderService finderService;

    @Inject
    private RequirementLibraryFinderService reqFinderService;

    @Inject
    private TestCaseCallTreeFinder calltreeFinder;

    @Inject
    private TestCaseFinder testCaseFinder;

    @Inject
    private ParameterFinder paramFinder;

    @Inject
    private MilestoneMembershipFinder milestoneMemberFinder;

    @Inject
    private DatasetDao dsDao;

    @Inject
    private RequirementVersionManagerService requirementVersionManagerService;

    /* **********************************************************************************************************************************
     *
     * The following properties are initialized all together during
     * init(List<TestCaseTarget>) :
     *
     * - testCaseStatusByTarget - testCaseStepsByTarget - projectStatusByName -
     * tcCufsPerProjectname - stepCufsPerProjectname
     *
     * **************************************************************************
     * **************************************** *****************
     */

    /**
     * Maps a reference to a TestCase (namely a TestCaseTarget). It keeps track
     * of its status (see TargetStatus) and possibly its id (when there is a
     * concrete instance of it in the database).<br/>
     * <br/>
     * Because a test case might be referenced multiple time, once a test case
     * is loaded in that map it'll stay there.
     */
    private Map<TestCaseTarget, TargetStatus> testCaseStatusByTarget = new HashMap<>();

    /**
     * Maps a test case (given its target) to a list of step models. We only
     * care of their position (because they are identified by position), their
     * type (because we want to detect potential attempts of modifications of an
     * action step whereas the target is actually a call step and conversely),
     * and possibly a called test case (if the step is a call step, and we want
     * to keep track of possible cycles).
     */
    private Map<TestCaseTarget, List<InternalStepModel>> testCaseStepsByTarget = new HashMap<>();

    /*
     * Introduced with issue 4973
     */
    /**
     * <p>
     * This maps says whether a given test case is locked because of its
     * milestones or not The answer is yes if :
     * </p>
     * <p/>
     * <ul>
     * <li>The entity exists in the database (ie it's not new : status is
     * EXISTS)</li>
     * <li>The entity indeed belongs to a milestone which status forbids any
     * modification</li>
     * </ul>
     * <p/>
     * <p>
     * Note that when the entity doesn't exist yet the answer is always no,
     * because one can never add or remove an entity a locked milestone. Same
     * goes for entities that are deleted or plain inexistant (status NOT_EXIST
     * : neither in DB nor in the import file)
     * </p>
     * <p/>
     * <p>
     * Also note that for now we make no distinction between milestones that
     * prevent deletion and milestones that prevent edition because they are
     * exactly the same at the moment
     * </p>
     */
    private Map<Target, Boolean> isTargetMilestoneLocked = new HashMap<>();

    /**
     * nothing special, plain wysiwyg
     */
    private Map<String, ProjectTargetStatus> projectStatusByName = new HashMap<>();

    /**
     * caches the custom fields defined for the test cases in a given project.
     * This is a multimap that matches a project name against a collection of
     * CustomField
     */
    private MultiValueMap tcCufsPerProjectname = new MultiValueMap();

    /**
     * same as tcCufsPerProjectname, but regarding the test steps
     */
    private MultiValueMap stepCufsPerProjectname = new MultiValueMap();

    /**
     * same as tcCufsPerProjectname, but regarding requirement
     */
    private MultiValueMap reqCufsPerProjectname = new MultiValueMap();

    /**
     * keeps track of which parameters are defined for which test cases
     */
    private Map<TestCaseTarget, Collection<ParameterTarget>> parametersByTestCase = new HashMap<>();

    /**
     * keeps track of which datasets are defined for which test cases
     */
    private Map<TestCaseTarget, Set<DatasetTarget>> datasetsByTestCase = new HashMap<>();

    /**
     * This property keeps track of the test case call graph. It is not
     * initialized like the rest, it's rather initialized on demand (see
     * isCalled for instance )
     */
    private TestCaseCallGraph callGraph = new TestCaseCallGraph();

    /**
     * This property tracks a hierarchy of requirement folders, requirements and
     * their versions
     */
    private ImportedRequirementTree requirementTree = new ImportedRequirementTree();

    private static final Logger LOGGER = LoggerFactory.getLogger(Model.class);

    // ===============================================================================================
    // ===============================================================================================

    // ************************** project access
    // *****************************************************

    public ProjectTargetStatus getProjectStatus(String projectName) {
        projectName = PathUtils.unescapePathPartSlashes(projectName);
        if (!projectStatusByName.containsKey(projectName)) {
            initProject(projectName);
        }
        return projectStatusByName.get(projectName);
    }

    // ************************** Test Case status management
    // ****************************************

    public TargetStatus getStatus(TestCaseTarget target) {

        if (!testCaseStatusByTarget.containsKey(target)) {
            mainInitTestCase(target);
        }

        return testCaseStatusByTarget.get(target);

    }

    public void setExists(TestCaseTarget target, Long id) {
        testCaseStatusByTarget.put(target, new TargetStatus(Existence.EXISTS, id));
    }

    public void setToBeCreated(TestCaseTarget target) {
        testCaseStatusByTarget.put(target, new TargetStatus(Existence.TO_BE_CREATED));
        clearSteps(target);
    }

    public void setToBeDeleted(TestCaseTarget target) {
        TargetStatus oldStatus = testCaseStatusByTarget.get(target);
        testCaseStatusByTarget.put(target, new TargetStatus(Existence.TO_BE_DELETED, oldStatus.id));
        clearSteps(target);
        callGraph.removeNode(target);
    }

    public void setDeleted(TestCaseTarget target) {
        testCaseStatusByTarget.put(target, new TargetStatus(Existence.NOT_EXISTS, null));
        clearSteps(target);
        callGraph.removeNode(target);
    }

    /**
     * virtually an alias of setDeleted
     */
    public void setNotExists(TestCaseTarget target) {
        testCaseStatusByTarget.put(target, new TargetStatus(Existence.NOT_EXISTS, null));
        clearSteps(target);
    }

    private void clearSteps(TestCaseTarget target) {
        if (testCaseStepsByTarget.containsKey(target)) {
            testCaseStepsByTarget.get(target).clear();
        }
    }

    // ************************** Test Case accessors
    // *****************************************

    /**
     * may return null
     */
    public Long getId(TestCaseTarget target) {
        return getStatus(target).id;
    }

    public TestCase get(TestCaseTarget target) {
        Long id = getId(target);
        if (id == null) {
            return null;
        } else {
            return testCaseFinder.findById(id);
        }
    }

    public boolean isTestCaseLockedByMilestones(TestCaseTarget target) {
        if (!testCaseStatusByTarget.containsKey(target)) {
            mainInitTestCase(target);
        }

        return isTargetMilestoneLocked.get(target);
    }

    // ************************ test case calls code
    // ***********************************

    /**
     * returns true if the test case is being called by another test case or
     * else false.
     * <p/>
     * Note : the problem arises only if the test case already exists in the
     * database (test cases instructions are all processed before
     * step-instructions are thus newly imported test cases aren't bound to any
     * test step yet).
     */
    public boolean isCalled(TestCaseTarget target) {

        if (!callGraph.knowsNode(target)) {
            initCallGraph(target);
        }

        return callGraph.isCalled(target);
    }

    public boolean isCalledBy(TestCaseTarget called, TestCaseTarget caller) {
        return wouldCreateCycle(called, caller);
    }

    public boolean wouldCreateCycle(TestCaseTarget srcTestCase, TestCaseTarget destTestCase) {
        if (!callGraph.knowsNode(srcTestCase)) {
            initCallGraph(srcTestCase);
        }

        if (!callGraph.knowsNode(destTestCase)) {
            initCallGraph(destTestCase);
        }

        return callGraph.wouldCreateCycle(srcTestCase, destTestCase);
    }

    public boolean wouldCreateCycle(TestStepTarget step, TestCaseTarget destTestCase) {
        return wouldCreateCycle(step.getTestCase(), destTestCase);
    }

    /**
     * initialize the call graph from the database. The whole graph will be
     * pulled so be carefull to load it only when necessary.
     */
    private void initCallGraph(TestCaseTarget target) {

        Long id = finderService.findNodeIdByPath(target.getPath());
        if (id == null) {
            // this is probably a new node
            callGraph.addNode(target);
            return;
        }

        LibraryGraph<NamedReference, SimpleNode<NamedReference>> targetCallers = calltreeFinder
                .getExtendedGraph(singletonList(id));

        // some data transform now
        Collection<SimpleNode<NamedReference>> refs = targetCallers.getNodes();
        swapNameForPath(refs);

        // now create the graph
        callGraph.addGraph(targetCallers);
    }

    private void addCallGraphEdge(TestCaseTarget src, TestCaseTarget dest) {

        if (!callGraph.knowsNode(src)) {
            initCallGraph(src);
        }

        if (!callGraph.knowsNode(dest)) {
            initCallGraph(dest);
        }

        callGraph.addEdge(src, dest);
    }

    // ************************ Test Step management
    // ********************************

    /**
     * Adds a step of the specified type to the model. Not to the database.
     *
     * @return the index at which the step was created
     */
    public Integer addActionStep(TestStepTarget target) {
        return addStep(target, StepType.ACTION, null);
    }

    public Integer addCallStep(TestStepTarget target, TestCaseTarget calledTestCase, CallStepParamsInfo paramInfo) {

        Boolean delegates = paramInfo.getParamMode() == ParameterAssignationMode.DELEGATE;

        // set the call graph
        addCallGraphEdge(target.getTestCase(), calledTestCase);

        // set the model
        return addStep(target, StepType.CALL, calledTestCase, delegates);

    }

    private Integer addStep(TestStepTarget target, StepType type, TestCaseTarget calledTestCase,
            Boolean delegates) {

        List<InternalStepModel> steps = findInternalStepModels(target);

        Integer index = target.getIndex();

        if (index == null || index >= steps.size() || index < 0) {
            index = steps.size();
        }

        steps.add(index, new InternalStepModel(type, calledTestCase, delegates));

        return index;

    }

    private Integer addStep(TestStepTarget target, StepType type, TestCaseTarget calledTestCase) {

        List<InternalStepModel> steps = findInternalStepModels(target);

        Integer index = target.getIndex();

        if (index == null || index >= steps.size() || index < 0) {
            index = steps.size();
        }

        steps.add(index, new InternalStepModel(type, calledTestCase));

        return index;

    }

    /**
     * warning : won't check that the operation will not create a cycle. Such
     * check needs to be done beforehand.
     */
    public void updateCallStepTarget(TestStepTarget step, TestCaseTarget newTarget, CallStepParamsInfo paramInfo) {

        if (!stepExists(step)) {
            throw new IllegalArgumentException("cannot update non existant step '" + step + "'");
        }

        if (getType(step) != StepType.CALL) {
            throw new IllegalArgumentException("cannot update the called test case for step '" + step
                    + "' because that step is not a call step");
        }

        InternalStepModel model = findInternalStepModel(step);
        Boolean delegates = paramInfo.getParamMode() == ParameterAssignationMode.DELEGATE;
        model.setDelegates(delegates);
        TestCaseTarget src = step.getTestCase();
        TestCaseTarget oldDest = model.getCalledTC();

        // update the model
        model.setCalledTC(newTarget);

        // update the call graph
        callGraph.removeEdge(src, oldDest);
        callGraph.addEdge(src, newTarget);

    }

    public void remove(TestStepTarget target) {

        if (!stepExists(target)) {
            throw new IllegalArgumentException("cannot remove non existant step '" + target + "'");
        }

        List<InternalStepModel> steps = findInternalStepModels(target);
        Integer index = target.getIndex();

        // remove from the model
        // .intValue() desambiguate with the other method - remove(Object) -
        InternalStepModel step = steps.remove(index.intValue());

        // remove from the callgraph
        if (step.type == StepType.CALL) {
            callGraph.removeEdge(target.getTestCase(), step.getCalledTC());
        }

    }

    // ************************ Test Step accessors
    // *********************************

    public boolean stepExists(TestStepTarget target) {

        InternalStepModel model = findInternalStepModel(target);

        return model != null;

    }

    public boolean indexIsFirstAvailable(TestStepTarget target) {
        Integer index = target.getIndex();
        TestCaseTarget tc = target.getTestCase();
        if (!testCaseStatusByTarget.containsKey(tc)) {
            mainInitTestCase(tc);
        }
        List<InternalStepModel> steps = testCaseStepsByTarget.get(tc);
        if (index == null || steps == null) {
            return false;
        } else {
            return index == steps.size();
        }
    }

    /**
     * @return null if the step is unknown
     */
    public StepType getType(TestStepTarget target) {

        InternalStepModel model = findInternalStepModel(target);

        if (model != null) {
            return model.getType();
        } else {
            return null;
        }
    }

    /**
     * may return null
     */
    public Long getStepId(TestStepTarget target) {

        Long tcId = getStatus(target.getTestCase()).id;
        Integer index = target.getIndex();

        // this condition is heavily defensive and the caller code should check
        // those beforehand
        if (!stepExists(target) || tcId == null || index == null) {
            return null;
        }

        Query q = getCurrentSession().getNamedQuery("testStep.findIdByTestCaseAndPosition");
        q.setParameter(":tcId", tcId);
        q.setParameter("position", index);

        return (Long) q.uniqueResult();

    }

    /**
     * may return null
     */
    public TestStep getStep(TestStepTarget target) {

        Long tcId = getStatus(target.getTestCase()).id;
        Integer index = target.getIndex();

        // this condition is heavily defensive and the caller code should check
        // those beforehand
        if (!stepExists(target) || tcId == null || index == null) {
            return null;
        }

        Query q = getCurrentSession().getNamedQuery("testStep.findByTestCaseAndPosition");
        q.setParameter("tcId", tcId);
        q.setParameter("position", index);

        return (TestStep) q.uniqueResult();

    }

    private List<InternalStepModel> findInternalStepModels(TestStepTarget step) {
        TestCaseTarget tc = step.getTestCase();

        if (!testCaseStatusByTarget.containsKey(tc)) {
            mainInitTestCase(tc);
        }

        return testCaseStepsByTarget.get(tc);

    }

    private InternalStepModel findInternalStepModel(TestStepTarget step) {
        Integer index = step.getIndex();
        List<InternalStepModel> steps = findInternalStepModels(step);

        if (index != null && steps.size() > index && index >= 0) {
            return steps.get(index);
        } else {
            return null;
        }
    }

    // ************************** parameters
    // ****************************************

    public boolean doesParameterExists(ParameterTarget target) {
        TestCaseTarget tc = target.getOwner();
        if (!parametersByTestCase.containsKey(tc)) {
            initParameters(singletonList(tc));
        }

        return parametersByTestCase.get(tc).contains(target);
    }

    public void addParameter(ParameterTarget target) {
        TestCaseTarget tc = target.getOwner();
        if (!parametersByTestCase.containsKey(tc)) {
            initParameters(singletonList(tc));
        }

        parametersByTestCase.get(tc).add(target);
    }

    public void removeParameter(ParameterTarget target) {
        TestCaseTarget tc = target.getOwner();
        if (!parametersByTestCase.containsKey(tc)) {
            initParameters(singletonList(tc));
        }

        parametersByTestCase.get(tc).remove(target);
    }

    /**
     * returns the parameters owned by this test case. It doesn't include all
     * the parameters from the test case call tree of this test case.
     */
    public Collection<ParameterTarget> getOwnParameters(TestCaseTarget testCase) {
        if (!parametersByTestCase.containsKey(testCase)) {
            initParameters(singletonList(testCase));
        }

        return parametersByTestCase.get(testCase);
    }

    /**
     * returns all parameters available to a test case. This includes every
     * ParameterTarget from the test cases being called directly or indirectly
     * by this test case, not just the one owner by the test case (unlike
     * getOwnParameters). Parameters from downstream test cases will be included
     * iif they are inherited in some ways.
     */
    public Collection<ParameterTarget> getAllParameters(TestCaseTarget testCase) {

        if (!callGraph.knowsNode(testCase)) {
            initCallGraph(testCase);
        }

        Collection<ParameterTarget> result = new HashSet<>();
        LinkedList<Node> processing = new LinkedList<>();
        Set<Node> processed = new HashSet<>();

        processing.add(callGraph.getNode(testCase));

        while (!processing.isEmpty()) {
            Node current = processing.pop();
            result.addAll(getOwnParameters(current.getKey()));

            // modification patron
            for (Node child : current.getOutbounds()) {

                List<InternalStepModel> steps = testCaseStepsByTarget.get(current.getKey());
                extractParametersFromSteps(processing, processed, child, steps);
                processed.add(current);
            }

        }

        return result;

    }

    private void extractParametersFromSteps(Collection<Node> processing, Set<Node> processed, Node child,
            List<InternalStepModel> steps) {
        if (steps != null) {
            for (InternalStepModel step : steps) {
                if (step.type == StepType.CALL && step.calledTC.equals(child.getKey()) && step.getDeleguates()
                // FIXME calledTC is not a Node so there's probably a bug here
                        && !processed.contains(step.calledTC)) {
                    processing.add(child);
                }
            }
        }
    }

    /**
     * @return true if the parameter legitimately belongs to the dataset, false
     * otherwise
     */
    public boolean isParamInDataset(ParameterTarget param, DatasetTarget ds) {
        Collection<ParameterTarget> allparams = getAllParameters(ds.getTestCase());
        return allparams.contains(param);
    }

    // **************************** datasets
    // ****************************************

    public boolean doesDatasetExists(DatasetTarget target) {
        TestCaseTarget tc = target.getTestCase();
        if (!datasetsByTestCase.containsKey(tc)) {
            initDatasets(singletonList(tc));
        }

        return datasetsByTestCase.get(tc).contains(target);
    }

    /**
     * This operation is imdepotent
     */
    public void addDataset(DatasetTarget target) {
        TestCaseTarget tc = target.getTestCase();
        if (!datasetsByTestCase.containsKey(tc)) {
            initDatasets(singletonList(tc));
        }

        datasetsByTestCase.get(tc).add(target);
    }

    public void removeDataset(DatasetTarget target) {
        TestCaseTarget tc = target.getTestCase();
        if (!datasetsByTestCase.containsKey(tc)) {
            initDatasets(singletonList(tc));
        }

        datasetsByTestCase.get(tc).remove(target);
    }

    /**
     * returns the parameters owned by this test case. It doesn't include all
     * the parameters from the test case call tree of this test case.
     */
    public Collection<DatasetTarget> getDatasets(TestCaseTarget testCase) {
        if (!datasetsByTestCase.containsKey(testCase)) {
            initDatasets(singletonList(testCase));
        }

        return datasetsByTestCase.get(testCase);
    }

    // ************************* CUFS accessors
    // *************************************

    @SuppressWarnings("unchecked")
    public Collection<CustomField> getTestCaseCufs(TestCaseTarget target) {
        if (!testCaseStatusByTarget.containsKey(target)) {
            mainInitTestCase(target);
        }

        String projectName = PathUtils.extractProjectName(target.getPath());
        Collection<CustomField> cufs = tcCufsPerProjectname.getCollection(projectName);

        if (cufs != null) {
            return cufs;
        } else {
            return emptyList();
        }
    }

    @SuppressWarnings("unchecked")
    public Collection<CustomField> getRequirementVersionCufs(RequirementVersionTarget target) {
        // if requirement version is unknown in model (ie in req tree),
        // init the requirement version
        if (requirementTree.targetAlreadyLoaded(target)) {
            mainInitRequirements(target);
        }

        String projectName = PathUtils.extractProjectName(target.getPath());
        Collection<CustomField> cufs = reqCufsPerProjectname.getCollection(projectName);

        if (cufs != null) {
            return cufs;
        } else {
            return emptyList();
        }
    }

    @SuppressWarnings("unchecked")
    public Collection<CustomField> getTestStepCufs(TestStepTarget target) {
        TestCaseTarget tc = target.getTestCase();

        if (!testCaseStatusByTarget.containsKey(tc)) {
            mainInitTestCase(tc);
        }

        String projectName = PathUtils.extractProjectName(tc.getPath());
        Collection<CustomField> cufs = stepCufsPerProjectname.getCollection(projectName);
        if (cufs != null) {
            return cufs;
        } else {
            return emptyList();
        }
    }

    // *********************** Requirement management
    // *******************************

    public TargetStatus getStatus(RequirementTarget target) {
        if (!requirementTree.targetAlreadyLoaded(target)) {
            loadRequirement(target);
        }
        return requirementTree.getStatus(target);
    }

    private void loadRequirement(RequirementTarget target) {
        Long reqId;
        if (target.isSynchronized()) {
            LOGGER.debug("ReqImport - looking for synchronized requirement key : '{}'", target.getRemoteKey());
            reqId = reqFinderService.findNodeIdByRemoteKey(target.getRemoteKey(), target.getProject());
        } else {
            LOGGER.debug("ReqImport - looking for native requirement by path : '{}", target.getPath());
            reqId = reqFinderService.findNodeIdByPath(target.getPath());
        }
        LOGGER.debug("ReqImport - result find by node : {}" + reqId);
        //only add existing requirement in tree.
        //New requirement will be created with good status by adding the requirement version
        if (reqId != null) {
            requirementTree.addOrUpdateNode(target, new TargetStatus(Existence.EXISTS, reqId));
            target.setId(reqId);
        } else {
            requirementTree.addOrUpdateNode(target, new TargetStatus(Existence.NOT_EXISTS));
        }
    }

    public TargetStatus getStatus(RequirementVersionTarget target) {
        //init the requirement version in tree if unknown
        if (!requirementTree.targetAlreadyLoaded(target)) {
            mainInitRequirements(target);
        }

        return requirementTree.getStatus(target);
    }

    // ************************** loading code
    // **************************************

    public void mainInitTestCase(TestCaseTarget target) {
        mainInitTestCase(Arrays.asList(new TestCaseTarget[] { target }));
    }

    public void mainInitTestCase(List<TestCaseTarget> targets) {

        // ensures unicity
        List<TestCaseTarget> uniqueTargets = uniqueList(targets);

        // init the test cases
        initTestCases(uniqueTargets);

        // init the steps
        initTestSteps(uniqueTargets);

        // init the projects
        initProjects(uniqueTargets);

    }

    private void initTestCases(List<TestCaseTarget> initialTargets) {

        // filter out the test cases we already know of
        List<TestCaseTarget> targets = new LinkedList<>();
        for (TestCaseTarget target : initialTargets) {
            if (!testCaseStatusByTarget.containsKey(target)) {
                targets.add(target);
            }
        }

        // exit if they are all known
        if (targets.isEmpty()) {
            return;
        }

        // collect their paths
        List<String> paths = collectPaths(targets);

        // find their ids
        List<Long> ids = finderService.findNodeIdsByPath(paths);

        // now store them
        for (int i = 0; i < paths.size(); i++) {

            TestCaseTarget t = targets.get(i);
            Long id = ids.get(i);

            Existence existence = id == null ? Existence.NOT_EXISTS : Existence.EXISTS;
            TargetStatus status = new TargetStatus(existence, id);

            testCaseStatusByTarget.put(t, status);

            // Issue 4973
            // see comment on the attribute "isTargetMilestoneLocked"
            Boolean milestoneLocked = Boolean.FALSE;
            if (existence == Existence.EXISTS) {
                milestoneLocked = milestoneMemberFinder.isTestCaseMilestoneDeletable(id);
            }

            isTargetMilestoneLocked.put(t, milestoneLocked);
        }
    }

    private void initParameters(List<TestCaseTarget> initialTargets) {

        for (TestCaseTarget t : initialTargets) {

            if (parametersByTestCase.containsKey(t)) {
                continue;
            }

            TargetStatus status = getStatus(t);

            if (status.id != null && status.status != Existence.TO_BE_DELETED) {
                Collection<Parameter> params = paramFinder.findOwnParameters(status.id);
                Collection<ParameterTarget> parameters = new HashSet<>(params.size());
                for (Parameter p : params) {
                    parameters.add(new ParameterTarget(t, p.getName()));
                }
                parametersByTestCase.put(t, parameters);
            } else {
                parametersByTestCase.put(t, new HashSet<ParameterTarget>());
            }
        }
    }

    private void initDatasets(List<TestCaseTarget> testCases) {

        for (TestCaseTarget t : testCases) {

            if (datasetsByTestCase.containsKey(t)) {
                continue;
            }

            TargetStatus status = getStatus(t);

            if (status.id != null && status.status != Existence.TO_BE_DELETED) {
                Collection<Dataset> datasets = dsDao.findOwnDatasetsByTestCase(status.id);
                Set<DatasetTarget> dstargets = new HashSet<>(datasets.size());
                for (Dataset ds : datasets) {
                    dstargets.add(new DatasetTarget(t, ds.getName()));
                }
                datasetsByTestCase.put(t, dstargets);
            } else {
                datasetsByTestCase.put(t, new HashSet<DatasetTarget>());
            }
        }
    }

    /**
     * this method assumes that the targets were all processed through
     * initTestCases(targets) beforehand.
     */
    private void initTestSteps(List<TestCaseTarget> targets) {

        for (TestCaseTarget target : targets) {

            // do not double process the steps
            if (testCaseStepsByTarget.containsKey(target)) {
                continue;
            }

            TargetStatus status = testCaseStatusByTarget.get(target);

            List<InternalStepModel> steps;
            if (status.id != null && status.status != Existence.TO_BE_DELETED) {
                steps = loadStepsModel(status.id);
            } else {
                steps = new ArrayList<>();
            }

            testCaseStepsByTarget.put(target, steps);

        }

    }

    private void initProject(String projectName) {
        initProjectsByName(singletonList(projectName));
    }

    private void initProjects(List<TestCaseTarget> targets) {
        initProjectsByName(collectProjects(targets));
    }

    private void initProjectsByName(List<String> allNames) {
        //[Issue 6032] unescaping the projects names
        List<String> allUnescapedNames = PathUtils.unescapePathPartSlashes(allNames);

        // filter out projects we already know of
        List<String> projectNames = new LinkedList<>();
        for (String name : allUnescapedNames) {
            if (!projectStatusByName.containsKey(name)) {
                projectNames.add(name);
            }
        }

        // exit if they are all known
        if (projectNames.isEmpty()) {
            return;
        }

        // now begin
        List<Project> projects = loadProjects(projectNames);

        // add the projects that were found
        for (Project p : projects) {
            LOGGER.debug("ReqImport - Trying to import project in model " + p.getId());
            ProjectTargetStatus status = new ProjectTargetStatus(Existence.EXISTS, p.getId(),
                    p.getTestCaseLibrary().getId(), p.getRequirementLibrary().getId());
            projectStatusByName.put(p.getName(), status);
            initCufs(p.getName());
        }

        // add the projects that weren't found
        Set<String> knownProjects = projectStatusByName.keySet();
        for (String name : projectNames) {
            if (!knownProjects.contains(name)) {
                // FIXME I'm pretty sure this ProjectTargetStatus ctor throws an IllegalSomethingEx
                projectStatusByName.put(name, new ProjectTargetStatus(Existence.NOT_EXISTS));
            }
        }

    }

    /**
     * assumes that the project exists and that we have its ID
     */
    private void initCufs(String projectName) {

        Long projectId = projectStatusByName.get(projectName).id;

        List<CustomField> tccufs = cufDao.findAllBoundCustomFields(projectId, BindableEntity.TEST_CASE);
        tcCufsPerProjectname.putAll(projectName, tccufs);

        List<CustomField> stcufs = cufDao.findAllBoundCustomFields(projectId, BindableEntity.TEST_STEP);
        stepCufsPerProjectname.putAll(projectName, stcufs);

        List<CustomField> reqcufs = cufDao.findAllBoundCustomFields(projectId, BindableEntity.REQUIREMENT_VERSION);
        reqCufsPerProjectname.putAll(projectName, reqcufs);

    }

    public void mainInitRequirements(RequirementVersionTarget target) {
        mainInitRequirements(Arrays.asList(target));
    }

    public void mainInitRequirements(List<RequirementVersionTarget> targets) {

        // ensures unicity
        // FIXME    ^^^ change signature for Set<RVT> so that vvvvv dont create shitload of useless collections !
        List<RequirementVersionTarget> uniqueTargets = uniqueList(targets);

        // init the requirements
        initRequirementVersions(uniqueTargets);

        // init the projects TODO
        initRequirementProjects(uniqueTargets);

    }

    private void initRequirementProjects(List<RequirementVersionTarget> uniqueTargets) {
        LOGGER.debug("ReqImport - Looking for project " + collectRequirementProjects(uniqueTargets));
        initProjectsByName(collectRequirementProjects(uniqueTargets));
    }

    public void initRequirementVersions(List<RequirementVersionTarget> initialTargets) {
        LOGGER.debug("ReqImport - Initialize targets");

        // filter out the requirement version we already know of
        List<RequirementVersionTarget> targets = new ArrayList<>(initialTargets.size());
        for (RequirementVersionTarget target : initialTargets) {
            if (!requirementTree.targetAlreadyLoaded(target)) {
                targets.add(target);
            }
        }

        // exit if they are all known
        if (targets.isEmpty()) {
            return;
        }

        for (RequirementVersionTarget target : targets) {
            //Now we look in database for the requirement version
            LOGGER.debug("ReqImport - Initialize target " + target.getPath());
            Existence reqExistence = getStatus(target.getRequirement()).getStatus();
            if (reqExistence != Existence.EXISTS) {
                //requirement not exist so requirement version can't exist in db
                LOGGER.debug("ReqImport - requirement doesn't exist so we don't need to check version");
                requirementTree.addOrUpdateNode(target, TargetStatus.NOT_EXISTS);
            } else {
                Long reqId = requirementTree.getNodeId(target.getRequirement());
                Integer versionNumber = target.getVersion();
                Long reqVersionId = requirementVersionManagerService
                        .findReqVersionIdByRequirementAndVersionNumber(reqId, versionNumber);
                if (reqVersionId != null) {
                    // FIXME we should probably check for READ right here but i dont know what else could i write when i dont have the right
                    initExistingRequirementVersion(target, reqVersionId);
                } else {
                    requirementTree.addOrUpdateNode(target, new TargetStatus(Existence.NOT_EXISTS));
                }
                //now we init all existing requirement version in the same requirement we are trying to update or add,
                // as we need it to make some check (milestone already used by another version...)
                // FIXME if we dont have READ rights, this breaks ! Model probably has to be revamped becaiuse it dont seem to care for access rights.
                Requirement req = requirementVersionManagerService.findRequirementById(reqId);
                List<RequirementVersion> reqVersions = req.getRequirementVersions();
                for (RequirementVersion requirementVersion : reqVersions) {
                    //we init the RequirementVersionTarget with the same RequirementTarget as the imported one as they have the same Requirement in db
                    RequirementVersionTarget existingRequirementversion = new RequirementVersionTarget(
                            target.getRequirement(), requirementVersion.getVersionNumber());
                    initExistingRequirementVersion(existingRequirementversion, requirementVersion.getId());
                }
            }
        }
    }

    private void initExistingRequirementVersion(RequirementVersionTarget target, Long reqVersionId) {
        requirementTree.addOrUpdateNode(target, new TargetStatus(Existence.EXISTS, reqVersionId));
        //here get milestone and milestoneLocked
        // FIXME if we dont have READ rights, this breaks ! Model probably has to be revamped becaiuse it dont seem to care for access rights.
        Collection<Milestone> milestones = milestoneMemberFinder.findMilestonesForRequirementVersion(reqVersionId);
        for (Milestone milestone : milestones) {
            requirementTree.bindMilestone(target, milestone.getLabel());
            if (milestone.getStatus() == MilestoneStatus.LOCKED
                    || milestone.getStatus() == MilestoneStatus.PLANNED) {
                requirementTree.milestoneLock(target);
            }
        }
    }

    /**
     * Add a requirement version to model, not to database.
     */
    public void addRequirementVersion(RequirementVersionTarget target, TargetStatus targetStatus) {
        requirementTree.addOrUpdateNode(target, targetStatus);
    }

    public void addRequirementVersion(RequirementVersionTarget target, TargetStatus targetStatus,
            List<String> milestones) {
        requirementTree.addOrUpdateNode(target, targetStatus);
        requirementTree.bindMilestone(target, milestones);
    }

    public void addRequirement(RequirementTarget target, TargetStatus status) {
        requirementTree.addOrUpdateNode(target, status);
    }

    public Long getRequirementId(RequirementVersionTarget target) {
        return requirementTree.getNodeId(target.getRequirement());
    }

    // ********************* REQUIREMENT STATUS METHOD *****************
    public void setNotExists(RequirementVersionTarget target) {
        requirementTree.setNotExists(target);
    }

    public boolean checkMilestonesAlreadyUsedInRequirement(String milestone, RequirementVersionTarget target) {
        return requirementTree.isMilestoneUsedByOneVersion(target, milestone);
    }

    public boolean isRequirementFolder(RequirementVersionTarget target) {
        return requirementTree.isRequirementFolder(target);
    }

    public void bindMilestonesToRequirementVersion(RequirementVersionTarget target, List<String> milestones) {
        requirementTree.bindMilestone(target, milestones);
    }

    // *************************** private methods
    // *************************************

    private <OBJ> List<OBJ> uniqueList(Collection<OBJ> orig) {
        // FIXME if orig is a Set or has a size of 1 maybe dont create useless collections.
        // FIXME we always pass a list anyways so change method sgnature
        Set<OBJ> filtered = new LinkedHashSet<>(orig);
        return new ArrayList<>(filtered);
    }

    /**
     * note (GRF) I dont know what this is supposed to do but it does not preserve the order of the input list !
     */
    private <TARGET extends Target> List<String> collectProjects(List<TARGET> targets) {
        List<String> paths = collectPaths(targets);
        return PathUtils.extractProjectNames(paths);
    }

    private List<String> collectRequirementProjects(List<RequirementVersionTarget> targets) {
        List<String> paths = collectRequirementPaths(targets);
        return PathUtils.extractProjectNames(paths);
    }

    @SuppressWarnings("unchecked")
    private <TARGET extends Target> List<String> collectPaths(List<TARGET> targets) {
        return (List<String>) CollectionUtils.collect(targets, PathCollector.INSTANCE,
                new ArrayList<String>(targets.size()));
    }

    @SuppressWarnings("unchecked")
    private List<String> collectRequirementPaths(List<RequirementVersionTarget> targets) {
        return (List<String>) CollectionUtils.collect(targets, RequirementPathCollector.INSTANCE,
                new ArrayList<String>(targets.size()));
    }

    @SuppressWarnings("unchecked")
    private List<Project> loadProjects(List<String> names) {
        List<String> unescapedNames = PathUtils.unescapePathPartSlashes(names);
        Query q = getCurrentSession().getNamedQuery("Project.findAllByName");
        q.setParameterList("names", unescapedNames);
        return q.list();
    }

    @SuppressWarnings("unchecked")
    private List<InternalStepModel> loadStepsModel(Long tcId) {
        Query query = getCurrentSession().getNamedQuery("testStep.findBasicInfosByTcId");
        query.setParameter("tcId", tcId, LongType.INSTANCE);

        List<Object[]> stepdata = query.list();

        List<InternalStepModel> steps = new ArrayList<>(stepdata.size());
        for (Object[] tuple : stepdata) {
            StepType type = StepType.valueOf((String) tuple[0]);
            TestCaseTarget calledTC = null;
            boolean delegates = false;
            if (type == StepType.CALL) {
                String path = finderService.getPathAsString((Long) tuple[1]);
                calledTC = new TestCaseTarget(path);
                delegates = (Boolean) tuple[2];
            }
            steps.add(new InternalStepModel(type, calledTC, delegates));
        }

        return steps;
    }

    /**
     * substitutes the value of the name attribute of NamedReference so that it
     * becomes a path instead.<br/>
     * All references are supposed to exist in the database that's foul play but
     * saves more bloat
     */
    @SuppressWarnings("unchecked")
    private void swapNameForPath(Collection<SimpleNode<NamedReference>> references) {

        // first ensures that the references will be iterated in a constant
        // order
        List<SimpleNode<NamedReference>> listedRefs = new ArrayList<>(references);

        // now collect the ids. Node : the javadoc claims that the result is a
        // new list.
        List<Long> ids = (List<Long>) CollectionUtils.collect(listedRefs, NamedReferenceIdCollector.INSTANCE);

        List<String> paths = finderService.getPathsAsString(ids);

        for (int i = 0; i < paths.size(); i++) {
            SimpleNode<NamedReference> currentNode = listedRefs.get(i);
            Long id = ids.get(i);
            String path = paths.get(i);
            currentNode.setKey(new NamedReference(id, path));
        }

    }

    private Session getCurrentSession() {
        return em.unwrap(Session.class);
    }

    // ************************ internal types for TestCase Management
    // **********************************

    private static final class PathCollector implements Transformer {

        private static final PathCollector INSTANCE = new PathCollector();

        private PathCollector() {
            super();
        }

        @Override
        public Object transform(Object value) {
            return ((TestCaseTarget) value).getPath();
        }
    }

    private static final class RequirementPathCollector implements Transformer {

        private static final RequirementPathCollector INSTANCE = new RequirementPathCollector();

        private RequirementPathCollector() {
            super();
        }

        @Override
        public Object transform(Object value) {
            return ((RequirementVersionTarget) value).getRequirement().getPath();
        }
    }

    private static final class NamedReferenceIdCollector implements Transformer {
        private static final NamedReferenceIdCollector INSTANCE = new NamedReferenceIdCollector();

        private NamedReferenceIdCollector() {
            super();
        }

        @SuppressWarnings("unchecked")
        @Override
        public Long transform(Object input) {
            return ((SimpleNode<NamedReference>) input).getKey().getId();
        }
    }

    // ********************************** Internal types for Test Step
    // management *************************

    private static final class InternalStepModel {
        private StepType type;
        private TestCaseTarget calledTC;
        private Boolean delegates = null;

        public InternalStepModel(StepType type, TestCaseTarget calledTC) {
            this.type = type;
            this.calledTC = calledTC;
        }

        public InternalStepModel(StepType type, TestCaseTarget calledTC, boolean delegates) {
            this.type = type;
            this.calledTC = calledTC;
            this.delegates = delegates;
        }

        public void setDelegates(boolean delegates) {
            this.delegates = delegates;
        }

        public boolean getDeleguates() {
            return delegates;
        }

        public TestCaseTarget getCalledTC() {
            return calledTC;
        }

        public void setCalledTC(TestCaseTarget calledTC) {
            this.calledTC = calledTC;
        }

        public StepType getType() {
            return type;
        }

    }

}