org.squashtest.tm.service.internal.requirement.VerifiedRequirementsManagerServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.squashtest.tm.service.internal.requirement.VerifiedRequirementsManagerServiceImpl.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.requirement;

import static org.squashtest.tm.service.security.Authorizations.OR_HAS_ROLE_ADMIN;

import java.util.*;
import java.util.Map.Entry;

import javax.inject.Inject;

import org.apache.commons.collections.MultiMap;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Interval;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.squashtest.tm.core.foundation.collection.PagedCollectionHolder;
import org.squashtest.tm.core.foundation.collection.PagingAndSorting;
import org.squashtest.tm.core.foundation.collection.PagingBackedPagedCollectionHolder;
import org.squashtest.tm.domain.campaign.Iteration;
import org.squashtest.tm.domain.campaign.IterationTestPlanItem;
import org.squashtest.tm.domain.execution.Execution;
import org.squashtest.tm.domain.execution.ExecutionStatus;
import org.squashtest.tm.domain.execution.ExecutionStep;
import org.squashtest.tm.domain.milestone.Milestone;
import org.squashtest.tm.domain.requirement.Requirement;
import org.squashtest.tm.domain.requirement.RequirementCoverageStat;
import org.squashtest.tm.domain.requirement.RequirementCoverageStat.Rate;
import org.squashtest.tm.domain.requirement.RequirementLibraryNode;
import org.squashtest.tm.domain.requirement.RequirementStatus;
import org.squashtest.tm.domain.requirement.RequirementVersion;
import org.squashtest.tm.domain.testcase.ActionTestStep;
import org.squashtest.tm.domain.testcase.RequirementVersionCoverage;
import org.squashtest.tm.domain.testcase.TestCase;
import org.squashtest.tm.domain.testcase.TestCaseExecutionStatus;
import org.squashtest.tm.domain.testcase.TestStep;
import org.squashtest.tm.exception.UnknownEntityException;
import org.squashtest.tm.exception.requirement.RequirementAlreadyVerifiedException;
import org.squashtest.tm.exception.requirement.RequirementVersionNotLinkableException;
import org.squashtest.tm.exception.requirement.VerifiedRequirementException;
import org.squashtest.tm.service.advancedsearch.IndexationService;
import org.squashtest.tm.service.internal.repository.ExecutionStepDao;
import org.squashtest.tm.service.internal.repository.IterationDao;
import org.squashtest.tm.service.internal.repository.LibraryNodeDao;
import org.squashtest.tm.service.internal.repository.RequirementDao;
import org.squashtest.tm.service.internal.repository.RequirementVersionCoverageDao;
import org.squashtest.tm.service.internal.repository.RequirementVersionDao;
import org.squashtest.tm.service.internal.repository.TestCaseDao;
import org.squashtest.tm.service.internal.repository.TestStepDao;
import org.squashtest.tm.service.internal.testcase.TestCaseCallTreeFinder;
import org.squashtest.tm.service.milestone.ActiveMilestoneHolder;
import org.squashtest.tm.service.requirement.VerifiedRequirement;
import org.squashtest.tm.service.requirement.VerifiedRequirementsManagerService;
import org.squashtest.tm.service.security.PermissionEvaluationService;
import org.squashtest.tm.service.security.PermissionsUtils;
import org.squashtest.tm.service.security.SecurityCheckableObject;
import org.squashtest.tm.service.testcase.TestCaseImportanceManagerService;

import com.google.common.base.Optional;

@Service("squashtest.tm.service.VerifiedRequirementsManagerService")
@Transactional
public class VerifiedRequirementsManagerServiceImpl implements VerifiedRequirementsManagerService {

    private static final Logger LOGGER = LoggerFactory.getLogger(VerifiedRequirementsManagerServiceImpl.class);
    private static final String LINK_TC_OR_ROLE_ADMIN = "hasPermission(#testCaseId, 'org.squashtest.tm.domain.testcase.TestCase' , 'LINK')"
            + OR_HAS_ROLE_ADMIN;

    @Inject
    private TestCaseDao testCaseDao;

    @Inject
    private TestStepDao testStepDao;

    @Inject
    private RequirementVersionDao requirementVersionDao;

    @Inject
    private TestCaseCallTreeFinder callTreeFinder;

    @Inject
    private RequirementVersionCoverageDao requirementVersionCoverageDao;

    @Inject
    private TestCaseImportanceManagerService testCaseImportanceManagerService;

    @Inject
    private IndexationService indexationService;

    @Inject
    private RequirementDao requirementDao;

    @Inject
    private IterationDao iterationDao;

    @Inject
    private ExecutionStepDao executionStepDao;

    @Inject
    private ActiveMilestoneHolder activeMilestoneHolder;

    @SuppressWarnings("rawtypes")
    @Inject
    @Qualifier("squashtest.tm.repository.RequirementLibraryNodeDao")
    private LibraryNodeDao<RequirementLibraryNode> requirementLibraryNodeDao;
    @Inject
    private PermissionEvaluationService permissionService;

    @Override
    @PreAuthorize(LINK_TC_OR_ROLE_ADMIN)
    public Collection<VerifiedRequirementException> addVerifiedRequirementsToTestCase(List<Long> requirementsIds,
            long testCaseId) {

        List<RequirementVersion> requirementVersions = findRequirementVersions(requirementsIds);

        TestCase testCase = testCaseDao.findById(testCaseId);
        if (!requirementVersions.isEmpty()) {
            return doAddVerifyingRequirementVersionsToTestCase(requirementVersions, testCase);
        }
        return Collections.emptyList();
    }

    private List<RequirementVersion> extractVersions(List<Requirement> requirements) {

        List<RequirementVersion> rvs = new ArrayList<>(requirements.size());

        Optional<Milestone> activeMilestone = activeMilestoneHolder.getActiveMilestone();

        for (Requirement requirement : requirements) {

            // normal mode
            if (!activeMilestone.isPresent()) {
                rvs.add(requirement.getResource());
            }
            // milestone mode
            else {
                rvs.add(requirement.findByMilestone(activeMilestone.get()));
            }

        }
        return rvs;
    }

    @Override
    @PreAuthorize(LINK_TC_OR_ROLE_ADMIN)
    public void removeVerifiedRequirementVersionsFromTestCase(List<Long> requirementVersionsIds, long testCaseId) {

        if (!requirementVersionsIds.isEmpty()) {

            List<RequirementVersionCoverage> requirementVersionCoverages = requirementVersionCoverageDao
                    .byTestCaseAndRequirementVersions(requirementVersionsIds, testCaseId);

            for (RequirementVersionCoverage coverage : requirementVersionCoverages) {
                requirementVersionCoverageDao.delete(coverage);
            }

            indexationService.reindexTestCase(testCaseId);
            indexationService.reindexRequirementVersionsByIds(requirementVersionsIds);

            testCaseImportanceManagerService.changeImportanceIfRelationsRemovedFromTestCase(requirementVersionsIds,
                    testCaseId);
        }
    }

    @Override
    @PreAuthorize(LINK_TC_OR_ROLE_ADMIN)
    public void removeVerifiedRequirementVersionFromTestCase(long requirementVersionId, long testCaseId) {
        RequirementVersionCoverage coverage = requirementVersionCoverageDao
                .byRequirementVersionAndTestCase(requirementVersionId, testCaseId);

        requirementVersionCoverageDao.delete(coverage);

        indexationService.reindexTestCase(testCaseId);
        indexationService.reindexRequirementVersion(requirementVersionId);
        testCaseImportanceManagerService
                .changeImportanceIfRelationsRemovedFromTestCase(Arrays.asList(requirementVersionId), testCaseId);
    }

    @Override
    @PreAuthorize(LINK_TC_OR_ROLE_ADMIN)
    public int changeVerifiedRequirementVersionOnTestCase(long oldVerifiedRequirementVersionId,
            long newVerifiedRequirementVersionId, long testCaseId) {
        RequirementVersion newReq = requirementVersionDao.findOne(newVerifiedRequirementVersionId);
        RequirementVersionCoverage coverage = requirementVersionCoverageDao
                .byRequirementVersionAndTestCase(oldVerifiedRequirementVersionId, testCaseId);
        coverage.setVerifiedRequirementVersion(newReq);
        indexationService.reindexTestCase(testCaseId);
        indexationService.reindexRequirementVersion(oldVerifiedRequirementVersionId);
        indexationService.reindexRequirementVersion(oldVerifiedRequirementVersionId);
        testCaseImportanceManagerService.changeImportanceIfRelationsRemovedFromTestCase(
                Arrays.asList(newVerifiedRequirementVersionId), testCaseId);

        return newReq.getVersionNumber();
    }

    /*
     * regarding the @PreAuthorize for the verified requirements :
     *
     * I prefer to show all the requirements that the test case refers to even
     * if some of those requirements belongs to a project the current user
     * cannot "read", rather post filtering it.
     *
     * The reason for that is that such policy is impractical for the same
     * problem in the context of Iteration-TestCase associations : filtering the
     * test cases wouldn't make much sense and would lead to partial executions
     * of a campaign.
     *
     * Henceforth the same policy applies to other cases of possible
     * inter-project associations (like TestCase-Requirement associations in the
     * present case), for the sake of coherence.
     *
     * @author bsiri
     *
     * (non-Javadoc)
     */
    @Override
    @PreAuthorize("hasPermission(#testCaseId, 'org.squashtest.tm.domain.testcase.TestCase' , 'READ')"
            + OR_HAS_ROLE_ADMIN)
    public PagedCollectionHolder<List<VerifiedRequirement>> findAllDirectlyVerifiedRequirementsByTestCaseId(
            long testCaseId, PagingAndSorting pagingAndSorting) {
        List<RequirementVersionCoverage> reqVersionCoverages = requirementVersionCoverageDao
                .findAllByTestCaseId(testCaseId, pagingAndSorting);
        long verifiedCount = requirementVersionCoverageDao.numberByTestCase(testCaseId);
        return new PagingBackedPagedCollectionHolder<>(pagingAndSorting, verifiedCount,
                convertInDirectlyVerified(reqVersionCoverages));
    }

    private List<VerifiedRequirement> convertInDirectlyVerified(
            List<RequirementVersionCoverage> reqVersionCoverages) {
        List<VerifiedRequirement> result = new ArrayList<>(reqVersionCoverages.size());
        for (RequirementVersionCoverage rvc : reqVersionCoverages) {
            VerifiedRequirement convertionResult = new VerifiedRequirement(rvc, true)
                    .withVerifyingStepsFrom(rvc.getVerifyingTestCase());
            result.add(convertionResult);
        }
        return result;
    }

    @Override
    public Collection<VerifiedRequirementException> addVerifyingRequirementVersionsToTestCase(
            Map<TestCase, List<RequirementVersion>> requirementVersionsByTestCase) {
        Collection<VerifiedRequirementException> rejections = new ArrayList<>();
        for (Entry<TestCase, List<RequirementVersion>> reqVsByTc : requirementVersionsByTestCase.entrySet()) {
            TestCase testCase = reqVsByTc.getKey();
            List<RequirementVersion> requirementVersions = reqVsByTc.getValue();
            Collection<VerifiedRequirementException> entrtyRejections = doAddVerifyingRequirementVersionsToTestCase(
                    requirementVersions, testCase);
            rejections.addAll(entrtyRejections);
        }
        return rejections;

    }

    private Collection<VerifiedRequirementException> doAddVerifyingRequirementVersionsToTestCase(
            List<RequirementVersion> requirementVersions, TestCase testCase) {
        Collection<VerifiedRequirementException> rejections = new ArrayList<>();
        Iterator<RequirementVersion> iterator = requirementVersions.iterator();
        while (iterator.hasNext()) {
            RequirementVersion requirementVersion = iterator.next();
            try {
                RequirementVersionCoverage coverage = new RequirementVersionCoverage(requirementVersion, testCase);
                requirementVersionCoverageDao.persist(coverage);
                indexationService.reindexTestCase(testCase.getId());
                indexationService.reindexRequirementVersion(requirementVersion.getId());
            } catch (RequirementAlreadyVerifiedException | RequirementVersionNotLinkableException ex) {
                LOGGER.warn(ex.getMessage());
                rejections.add(ex);
                iterator.remove();
            }
        }
        testCaseImportanceManagerService.changeImportanceIfRelationsAddedToTestCase(requirementVersions, testCase);
        return rejections;

    }

    @Override
    @PreAuthorize("hasPermission(#testStepId, 'org.squashtest.tm.domain.testcase.TestStep' , 'LINK')"
            + OR_HAS_ROLE_ADMIN)
    public Collection<VerifiedRequirementException> addVerifiedRequirementsToTestStep(List<Long> requirementsIds,
            long testStepId) {
        List<RequirementVersion> requirementVersions = findRequirementVersions(requirementsIds);
        // init rejections
        Collection<VerifiedRequirementException> rejections = new ArrayList<>();
        // check if list not empty
        if (!requirementVersions.isEmpty()) {
            // collect concerned entities
            ActionTestStep step = testStepDao.findActionTestStepById(testStepId);
            TestCase testCase = step.getTestCase();
            // iterate on requirement versions
            Iterator<RequirementVersion> iterator = requirementVersions.iterator();
            while (iterator.hasNext()) {
                try {
                    RequirementVersion requirementVersion = iterator.next();
                    PermissionsUtils.checkPermission(permissionService,
                            new SecurityCheckableObject(requirementVersion, "LINK"));
                    boolean newReqCoverage = addVerifiedRequirementVersionToTestStep(requirementVersion, step,
                            testCase);
                    if (!newReqCoverage) {
                        iterator.remove();
                    }
                } catch (RequirementAlreadyVerifiedException | RequirementVersionNotLinkableException ex) {
                    LOGGER.warn(ex.getMessage());
                    iterator.remove();
                    rejections.add(ex);
                }
            }
            testCaseImportanceManagerService.changeImportanceIfRelationsAddedToTestCase(requirementVersions,
                    testCase);

        }
        return rejections;
    }

    /**
     * Will find the RequirementVersionCoverage for the given requirement
     * version and test case to add the step to it. If not found, will create a
     * new RequirementVersionCoverage for the test case and add the step to it.<br>
     *
     * @param step
     * @param testCase
     * @return true if a new RequirementVersionCoverage has been created.
     */
    private boolean addVerifiedRequirementVersionToTestStep(RequirementVersion requirementVersion,
            ActionTestStep step, TestCase testCase) {

        RequirementVersionCoverage coverage = requirementVersionCoverageDao
                .byRequirementVersionAndTestCase(requirementVersion.getId(), testCase.getId());
        if (coverage == null) {
            RequirementVersionCoverage newCoverage = new RequirementVersionCoverage(requirementVersion, testCase);
            newCoverage.addAllVerifyingSteps(Arrays.asList(step));
            requirementVersionCoverageDao.persist(newCoverage);
            indexationService.reindexTestCase(testCase.getId());
            indexationService.reindexRequirementVersion(requirementVersion.getId());
            return true;
        } else {
            coverage.addAllVerifyingSteps(Arrays.asList(step));
            return false;
        }

    }

    /**
     * @see VerifiedRequirementsManagerService#addVerifiedRequirementVersionToTestStep(long,
     *      long);
     */
    @Override
    @PreAuthorize("hasPermission(#testStepId, 'org.squashtest.tm.domain.testcase.TestStep' , 'LINK') and hasPermission(#requirementVersionId, 'org.squashtest.tm.domain.requirement.RequirementVersion' , 'LINK')"
            + OR_HAS_ROLE_ADMIN)
    public Collection<VerifiedRequirementException> addVerifiedRequirementVersionToTestStep(
            long requirementVersionId, long testStepId) {
        ActionTestStep step = testStepDao.findActionTestStepById(testStepId);
        TestCase testCase = step.getTestCase();
        RequirementVersion version = requirementVersionDao.findOne(requirementVersionId);
        Collection<VerifiedRequirementException> rejections = new ArrayList<>(1);
        if (version == null) {
            throw new UnknownEntityException(requirementVersionId, RequirementVersion.class);
        }
        try {
            boolean newRequirementCoverageCreated = addVerifiedRequirementVersionToTestStep(version, step,
                    testCase);
            if (newRequirementCoverageCreated) {
                testCaseImportanceManagerService.changeImportanceIfRelationsAddedToTestCase(Arrays.asList(version),
                        testCase);
            }
        } catch (RequirementAlreadyVerifiedException | RequirementVersionNotLinkableException ex) {
            LOGGER.warn(ex.getMessage());
            rejections.add(ex);
        }
        return rejections;
    }

    private List<RequirementVersion> findRequirementVersions(List<Long> requirementsIds) {

        List<RequirementLibraryNode> nodes = requirementLibraryNodeDao.findAllByIds(requirementsIds);

        if (!nodes.isEmpty()) {
            List<Requirement> requirements = new RequirementNodeWalker().walk(nodes);
            if (!requirements.isEmpty()) {
                return extractVersions(requirements);
            }
        }
        return Collections.emptyList();
    }

    @Override
    @Transactional(readOnly = true)
    public PagedCollectionHolder<List<VerifiedRequirement>> findAllVerifiedRequirementsByTestCaseId(long testCaseId,
            PagingAndSorting pas) {

        LOGGER.debug("Looking for verified requirements of TestCase[id:{}]", testCaseId);

        Set<Long> calleesIds = callTreeFinder.getTestCaseCallTree(testCaseId);

        calleesIds.add(testCaseId);

        LOGGER.debug("Fetching Requirements verified by TestCases {}", calleesIds.toString());

        List<RequirementVersion> pagedVersionVerifiedByCalles = requirementVersionCoverageDao
                .findDistinctRequirementVersionsByTestCases(calleesIds, pas);

        TestCase mainTestCase = testCaseDao.findById(testCaseId);

        List<VerifiedRequirement> pagedVerifiedReqs = buildVerifiedRequirementList(mainTestCase,
                pagedVersionVerifiedByCalles);

        long totalVerified = requirementVersionCoverageDao.numberDistinctVerifiedByTestCases(calleesIds);

        LOGGER.debug("Total count of verified requirements : {}", totalVerified);

        return new PagingBackedPagedCollectionHolder<>(pas, totalVerified, pagedVerifiedReqs);
    }

    @Override
    public List<VerifiedRequirement> findAllVerifiedRequirementsByTestCaseId(long testCaseId) {
        LOGGER.debug("Looking for verified requirements of TestCase[id:{}]", testCaseId);

        Set<Long> calleesIds = callTreeFinder.getTestCaseCallTree(testCaseId);

        calleesIds.add(testCaseId);

        LOGGER.debug("Fetching Requirements verified by TestCases {}", calleesIds.toString());

        List<RequirementVersion> pagedVersionVerifiedByCalles = requirementVersionCoverageDao
                .findDistinctRequirementVersionsByTestCases(calleesIds);

        TestCase mainTestCase = testCaseDao.findById(testCaseId);

        return buildVerifiedRequirementList(mainTestCase, pagedVersionVerifiedByCalles);
    }

    /**
     * @see org.squashtest.tm.service.internal.requirement.VerifiedRequirementsManagerService#findisReqCoveredOfCallingTCWhenisReqCoveredChanged(long,
     *      List)
     */
    @Override
    public Map<Long, Boolean> findisReqCoveredOfCallingTCWhenisReqCoveredChanged(long updatedTestCaseId,
            Collection<Long> toUpdateIds) {
        Map<Long, Boolean> result;
        result = new HashMap<>(toUpdateIds.size());
        if (testCaseHasDirectCoverage(updatedTestCaseId)
                || testCaseHasUndirectRequirementCoverage(updatedTestCaseId)) {
            // set isReqCovered = true for all calling test cases
            for (Long id : toUpdateIds) {
                result.put(id, Boolean.TRUE);
            }
        } else {
            // check each calling testCase to see if their status changed
            for (Long id : toUpdateIds) {
                Boolean value = testCaseHasDirectCoverage(id) || testCaseHasUndirectRequirementCoverage(id);
                result.put(id, value);
            }
        }

        return result;
    }

    /**
     * @see org.squashtest.tm.service.internal.requirement.VerifiedRequirementsManagerService#testCaseHasUndirectRequirementCoverage(long)
     */
    @Override
    public boolean testCaseHasUndirectRequirementCoverage(long updatedTestCaseId) {
        List<Long> calledTestCaseIds = testCaseDao.findAllDistinctTestCasesIdsCalledByTestCase(updatedTestCaseId);
        if (!calledTestCaseIds.isEmpty()) {
            for (Long id : calledTestCaseIds) {
                if (testCaseHasDirectCoverage(id) || testCaseHasUndirectRequirementCoverage(id)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @see org.squashtest.tm.service.internal.requirement.VerifiedRequirementsManagerService#testCaseHasDirectCoverage(long)
     */
    @Override
    public boolean testCaseHasDirectCoverage(long updatedTestCaseId) {
        return requirementVersionDao.countVerifiedByTestCase(updatedTestCaseId) > 0;
    }

    private List<VerifiedRequirement> buildVerifiedRequirementList(final TestCase main,
            List<RequirementVersion> pagedVersionVerifiedByCalles) {

        List<VerifiedRequirement> toReturn = new ArrayList<>(pagedVersionVerifiedByCalles.size());

        for (RequirementVersion rVersion : pagedVersionVerifiedByCalles) {
            boolean isDirect = main.verifies(rVersion);
            toReturn.add(new VerifiedRequirement(rVersion, isDirect).withVerifyingStepsFrom(main));
        }

        return toReturn;
    }

    @Override
    public PagedCollectionHolder<List<VerifiedRequirement>> findAllDirectlyVerifiedRequirementsByTestStepId(
            long testStepId, PagingAndSorting paging) {
        TestStep step = testStepDao.findById(testStepId);
        return findAllDirectlyVerifiedRequirementsByTestCaseId(step.getTestCase().getId(), paging);
    }

    @Override
    public void removeVerifiedRequirementVersionsFromTestStep(List<Long> requirementVersionsIds, long testStepId) {

        List<RequirementVersionCoverage> coverages = requirementVersionCoverageDao
                .byRequirementVersionsAndTestStep(requirementVersionsIds, testStepId);

        // if cast exception well, the input were wrong and the thread was bound
        // to grind to halt.
        ActionTestStep ts = (ActionTestStep) testStepDao.findById(testStepId);
        for (RequirementVersionCoverage cov : coverages) {
            ts.removeRequirementVersionCoverage(cov);
        }

    }

    @Override
    public void findCoverageStat(Long requirementVersionId, List<Long> iterationsIds,
            RequirementCoverageStat stats) {

        RequirementVersion mainVersion = requirementVersionDao.findOne(requirementVersionId);
        Requirement mainRequirement = mainVersion.getRequirement();
        List<RequirementVersion> descendants = findValidDescendants(mainRequirement);
        findCoverageRate(mainRequirement, mainVersion, descendants, stats);
        //if we have a valid perimeter (ie iteration(s)), we'll have to calculate verification and validation rates
        if (!iterationsIds.isEmpty()) {
            checkPerimeter(iterationsIds, stats);
            if (!stats.isCorruptedPerimeter()) {
                findExecutionRate(mainVersion, descendants, stats, iterationsIds);
            }
        }
        stats.convertRatesToPercent();
    }

    private void checkPerimeter(List<Long> iterationsIds, RequirementCoverageStat stats) {
        List<Iteration> iterations = iterationDao.findAllByIds(iterationsIds);
        if (iterations.size() != iterationsIds.size()) {
            stats.setCorruptedPerimeter(true);
        }
    }

    /**
     * Extract a {@link Map}, key : {@link ExecutionStatus} value : {@link Long}.
     * The goal is to perform arithmetic operation with this map to calculate several rates on {@link RequirementVersion}
     * Constraints from specification Feat 4434 :
     * <code>
     * <ul>
     * <li>Requirement without linked {@link TestStep} must be treated at {@link Execution} level, for last execution.
     * We must also include fast pass so we take the {@link IterationTestPlanItem} status</li>
     * <li>Requirement with linked {@link TestStep} must be treated at {@link ExecutionStep} level</li>
     * <li>Only last execution must be considered for a given {@link IterationTestPlanItem}</li>
     * <li>FastPass must be considered for all cases (ie even if the {@link RequirementVersion} is linked to {@link TestStep})</li>
     * <li>Rate must be calculate on the designed {@link Requirement} and it's descendants</li>
     * <li>The descendant list must be filtered by {@link Milestone} and exclude {@link RequirementVersion} with {@link RequirementStatus#OBSOLETE}</li>
     * </ul>
     * </code>
     * @param mainRequirement
     * @param mainVersion
     * @param descendants
     * @param stats pojo containing the computed stats
     * @param iterationsIds
     */
    private void findExecutionRate(RequirementVersion mainVersion, List<RequirementVersion> descendants,
            RequirementCoverageStat stats, List<Long> iterationsIds) {
        boolean hasDescendant = !descendants.isEmpty();
        Rate verificationRate = new Rate();
        Rate validationRate = new Rate();

        //see http://javadude.com/articles/passbyvalue.htm to understand why an array (or any object) is needed here
        Long[] mainUntestedElementsCount = new Long[] { 0L };
        Map<ExecutionStatus, Long> mainStatusMap = new EnumMap<>(ExecutionStatus.class);
        makeStatusMap(mainVersion.getRequirementVersionCoverages(), mainUntestedElementsCount, mainStatusMap,
                iterationsIds);
        verificationRate
                .setRequirementVersionRate(doRateVerifiedCalculation(mainStatusMap, mainUntestedElementsCount[0]));
        validationRate
                .setRequirementVersionRate(doRateValidatedCalculation(mainStatusMap, mainUntestedElementsCount[0]));

        if (hasDescendant) {
            verificationRate.setAncestor(true);
            validationRate.setAncestor(true);
            Set<RequirementVersionCoverage> descendantCoverages = getDescendantCoverages(descendants);
            Long[] descendantUntestedElementsCount = new Long[] { 0L };
            Map<ExecutionStatus, Long> descendantStatusMap = new EnumMap<>(ExecutionStatus.class);
            makeStatusMap(descendantCoverages, descendantUntestedElementsCount, descendantStatusMap, iterationsIds);
            verificationRate.setRequirementVersionChildrenRate(
                    doRateVerifiedCalculation(descendantStatusMap, descendantUntestedElementsCount[0]));
            validationRate.setRequirementVersionChildrenRate(
                    doRateValidatedCalculation(descendantStatusMap, descendantUntestedElementsCount[0]));

            Long[] allUntestedElementsCount = new Long[] { 0L };
            allUntestedElementsCount[0] = mainUntestedElementsCount[0] + descendantUntestedElementsCount[0];
            Map<ExecutionStatus, Long> allStatusMap = mergeMapResult(mainStatusMap, descendantStatusMap);
            verificationRate.setRequirementVersionGlobalRate(
                    doRateVerifiedCalculation(allStatusMap, allUntestedElementsCount[0]));
            validationRate.setRequirementVersionGlobalRate(
                    doRateValidatedCalculation(allStatusMap, allUntestedElementsCount[0]));
        }

        stats.addRate("verification", verificationRate);
        stats.addRate("validation", validationRate);
    }

    /**
     * Return a merged map. For each {@link ExecutionStatus}, the returned value is the value in map1 + value in map 2.
     * The state of the two arguments maps is preserved
     * @param mainStatusMap
     * @param descendantStatusMap
     * @return
     */
    private Map<ExecutionStatus, Long> mergeMapResult(Map<ExecutionStatus, Long> mainStatusMap,
            Map<ExecutionStatus, Long> descendantStatusMap) {
        Map<ExecutionStatus, Long> mergedStatusMap = new EnumMap<>(ExecutionStatus.class);
        EnumSet<ExecutionStatus> allStatus = EnumSet.allOf(ExecutionStatus.class);
        for (ExecutionStatus executionStatus : allStatus) {
            Long mainCount = mainStatusMap.get(executionStatus) == null ? 0 : mainStatusMap.get(executionStatus);
            Long descendantCount = descendantStatusMap.get(executionStatus) == null ? 0
                    : descendantStatusMap.get(executionStatus);
            Long totalCount = mainCount + descendantCount;
            mergedStatusMap.put(executionStatus, totalCount);
        }
        return mergedStatusMap;
    }

    /**
     * As above but with no return. The second map is merged into the first map, witch orginal state is lost
     * @param statusMap
     * @param statusMapToMerge
     */
    private void fusionMapResult(Map<ExecutionStatus, Long> statusMap,
            Map<ExecutionStatus, Long> statusMapToMerge) {
        for (Entry<ExecutionStatus, Long> mergeEntry : statusMapToMerge.entrySet()) {
            ExecutionStatus executionStatus = mergeEntry.getKey();
            Long originalValue = statusMap.get(executionStatus);
            Long mergedValue = mergeEntry.getValue();
            if (mergedValue != null && originalValue == null) {
                statusMap.put(executionStatus, mergedValue);
            }
            if (mergedValue != null && originalValue != null) {
                statusMap.put(executionStatus, mergedValue + originalValue);
            }
        }
    }

    private Set<RequirementVersionCoverage> getDescendantCoverages(List<RequirementVersion> descendants) {
        Set<RequirementVersionCoverage> covs = new HashSet<>();
        for (RequirementVersion requirementVersion : descendants) {
            Set<RequirementVersionCoverage> coverages = requirementVersion.getRequirementVersionCoverages();
            if (!coverages.isEmpty()) {
                covs.addAll(coverages);
            }
        }
        return covs;
    }

    private List<Long> filterTCIds(List<Long> TCIds, List<Long> tCWithItpiIds) {
        List<Long> filtered = new ArrayList<>();
        filtered.addAll(TCIds);
        filtered.removeAll(tCWithItpiIds);
        return filtered;
    }

    private List<Long> convertSetToList(Map<Long, Long> nbSimpleCoverageByTestCase) {
        List<Long> testCaseIds = new ArrayList<>();
        testCaseIds.addAll(nbSimpleCoverageByTestCase.keySet());
        return testCaseIds;
    }

    private List<Long> findTCWithItpi(List<Long> tcIds, List<Long> iterationsIds) {
        return iterationDao.findVerifiedTcIdsInIterations(tcIds, iterationsIds);
    }

    private void makeStatusMap(Set<RequirementVersionCoverage> covs, Long[] untestedElementsCount,
            Map<ExecutionStatus, Long> statusMap, List<Long> iterationsIds) {
        List<RequirementVersionCoverage> simpleCoverage = new ArrayList<>();
        List<RequirementVersionCoverage> stepedCoverage = new ArrayList<>();
        Map<Long, Long> nbSimpleCoverageByTestCase = new HashMap<>();
        Map<Long, Long> nbSteppedCoverageByTestCase = new HashMap<>();
        partRequirementVersionCoverage(covs, simpleCoverage, stepedCoverage, nbSimpleCoverageByTestCase,
                nbSteppedCoverageByTestCase);
        //Find the test case with at least one itpi
        List<Long> simpleCoverageTCIds = convertSetToList(nbSimpleCoverageByTestCase);
        List<Long> simpleTCWithItpiIds = findTCWithItpi(simpleCoverageTCIds, iterationsIds);
        //Filter to have the test case without itpi
        List<Long> mainVersionTCWithoutItpiIds = filterTCIds(simpleCoverageTCIds, simpleTCWithItpiIds);
        Map<ExecutionStatus, Long> statusMapForSimple = findResultsForSimpleCoverage(simpleTCWithItpiIds,
                iterationsIds, nbSimpleCoverageByTestCase);

        //STEPPED
        //we need : TC without ITPI -> untested, TC With ITPI but no execution -> treated like fastpass (ie the status of ITPI is applied to each stepped coverage),
        //TC with execution -> treated at test step level
        List<Long> steppedCoverageTCIds = convertSetToList(nbSteppedCoverageByTestCase);
        List<Long> steppedCoverageTCIdsWithITPI = iterationDao.findVerifiedTcIdsInIterations(steppedCoverageTCIds,
                iterationsIds);
        List<Long> steppedCoverageTCIdsWithExecution = iterationDao
                .findVerifiedTcIdsInIterationsWithExecution(steppedCoverageTCIds, iterationsIds);
        List<Long> steppedCoverageTCIdsWithoutITPI = filterTCIds(steppedCoverageTCIds,
                steppedCoverageTCIdsWithITPI);
        List<Long> steppedCoverageTCIdsWithoutExecution = filterTCIds(steppedCoverageTCIdsWithITPI,
                steppedCoverageTCIdsWithExecution);

        //TC With ITPI but no execution are treated like simple testcase
        Map<ExecutionStatus, Long> statusMapForSteppedNoExecution = findResultsForSteppedCoverageWithoutExecution(
                stepedCoverage, steppedCoverageTCIdsWithoutExecution, iterationsIds, nbSteppedCoverageByTestCase);
        untestedElementsCount[0] = calculateUntestedElementCount(mainVersionTCWithoutItpiIds,
                nbSimpleCoverageByTestCase, stepedCoverage, steppedCoverageTCIdsWithoutITPI);
        Map<ExecutionStatus, Long> statusMapForSteppedWithExecution = findResultsForSteppedCoverageWithExecution(
                stepedCoverage, steppedCoverageTCIdsWithExecution, nbSteppedCoverageByTestCase);

        //merging the three map of results
        fusionMapResult(statusMap, statusMapForSimple);
        fusionMapResult(statusMap, statusMapForSteppedNoExecution);
        fusionMapResult(statusMap, statusMapForSteppedWithExecution);
    }

    @SuppressWarnings("unchecked")
    private Map<ExecutionStatus, Long> findResultsForSteppedCoverageWithoutExecution(
            List<RequirementVersionCoverage> stepedCoverage, List<Long> testCaseIds, List<Long> iterationsIds,
            Map<Long, Long> nbSteppedCoverageByTestCase) {
        MultiMap testCaseExecutionStatus = iterationDao.findVerifiedITPI(testCaseIds, iterationsIds);
        Map<ExecutionStatus, Long> result = new EnumMap<>(ExecutionStatus.class);
        for (RequirementVersionCoverage cov : stepedCoverage) {
            Long tcId = cov.getVerifyingTestCase().getId();
            List<TestCaseExecutionStatus> tcsStatus = (List<TestCaseExecutionStatus>) testCaseExecutionStatus
                    .get(tcId);
            if (tcsStatus != null) {
                for (TestCaseExecutionStatus tcStatus : tcsStatus) {
                    //For each cov we must count one status per steps. So fast pass status is forwarded to steps...
                    result.put(tcStatus.getStatus(), (long) cov.getVerifyingSteps().size());
                }
            }
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private Map<ExecutionStatus, Long> findResultsForSteppedCoverageWithExecution(
            List<RequirementVersionCoverage> stepedCoverage, List<Long> mainVersionTCWithItpiIds,
            Map<Long, Long> nbSimpleCoverageByTestCase) {
        List<Long> testStepsIds = new ArrayList<>();
        Map<ExecutionStatus, Long> result = new EnumMap<>(ExecutionStatus.class);
        //First we compute all testStep id in a list, to allow multiple occurrence of the same step.
        //Witch is not a good practice but is allowed by the app so we must take this possibility in account for calculations.
        for (RequirementVersionCoverage cov : stepedCoverage) {
            Long tcId = cov.getVerifyingTestCase().getId();
            if (mainVersionTCWithItpiIds.contains(tcId)) {
                for (ActionTestStep step : cov.getVerifyingSteps()) {
                    testStepsIds.add(step.getId());
                }
            }
        }
        //now retrieve a list of exec steps
        MultiMap executionsStatus = executionStepDao.findStepExecutionsStatus(mainVersionTCWithItpiIds,
                testStepsIds);
        for (Long testStepsId : testStepsIds) {
            List<ExecutionStep> executionSteps = (List<ExecutionStep>) executionsStatus.get(testStepsId);
            for (ExecutionStep executionStep : executionSteps) {
                //Here come horrible code to detect if ITPI was fast passed AFTER execution.
                //We have no attribute in model to help us, and no time to develop a proper solution.
                //So we'll use execution date on itpi and exec. If the delta between two date is superior to 2 seconds,
                //we consider it's a fast pass
                Execution execution = executionStep.getExecution();
                IterationTestPlanItem itpi = execution.getTestPlan();
                Date itpiDateLastExecutedOn = itpi.getLastExecutedOn();
                Date execDateLastExecutedOn = execution.getLastExecutedOn();
                ExecutionStatus status = ExecutionStatus.READY;
                //if execution dates are null, the execution was only READY, so we don't compare dates to avoid npe
                if (itpiDateLastExecutedOn != null && execDateLastExecutedOn != null) {
                    DateTime itpiLastExecutedOn = new DateTime(itpi.getLastExecutedOn().getTime());
                    DateTime execLastExecutedOn = new DateTime(execution.getLastExecutedOn().getTime());
                    Interval interval = new Interval(execLastExecutedOn, itpiLastExecutedOn);
                    boolean fastPass = interval.toDuration().isLongerThan(new Duration(2000L));
                    //If we have a fast path use it for step status
                    status = fastPass ? itpi.getExecutionStatus() : executionStep.getExecutionStatus();
                }
                Long memo = result.get(status);
                if (memo == null) {
                    result.put(status, 1L);
                } else {
                    result.put(status, memo + 1);
                }
            }
        }
        return result;
    }

    private Long calculateUntestedElementCount(List<Long> mainVersionTCWithoutItpiIds,
            Map<Long, Long> nbSimpleCoverageByTestCase, List<RequirementVersionCoverage> stepedCoverage,
            List<Long> steppedCoverageTCIdsWithoutITPI) {
        Long total = 0L;
        for (Long tcId : mainVersionTCWithoutItpiIds) {
            Long nbCovegrage = nbSimpleCoverageByTestCase.get(tcId);
            if (nbCovegrage != null && nbCovegrage != 0L) {
                total += nbCovegrage;
            }
        }
        for (Long tcId : steppedCoverageTCIdsWithoutITPI) {
            for (RequirementVersionCoverage cov : stepedCoverage) {
                if (cov.getVerifyingTestCase().getId().equals(tcId)) {
                    total += cov.getVerifyingSteps().size();
                }
            }
        }
        return total;
    }

    private double doRateVerifiedCalculation(Map<ExecutionStatus, Long> fullCoverageResult,
            Long untestedElementsCount) {
        Set<ExecutionStatus> statusSet = getVerifiedStatus();
        return doRateCalculation(statusSet, fullCoverageResult, untestedElementsCount);
    }

    private double doRateValidatedCalculation(Map<ExecutionStatus, Long> fullCoverageResult,
            Long untestedElementsCount) {
        Set<ExecutionStatus> validStatusSet = getValidatedStatus();
        Set<ExecutionStatus> verifiedStatusSet = getVerifiedStatus();
        return doRateCalculation(validStatusSet, verifiedStatusSet, fullCoverageResult);
    }

    /**
     * Rate calculation for two status set.
     * The count on the first one will be the numerator, the count one second set will be the denominator
     * @param numeratorStatus
     * @param fullCoverageResult
     * @return
     */
    private double doRateCalculation(Set<ExecutionStatus> numeratorStatus, Set<ExecutionStatus> denominatorStatus,
            Map<ExecutionStatus, Long> fullCoverageResult) {
        double numerator = countforStatus(fullCoverageResult, numeratorStatus);
        double denominator = countforStatus(fullCoverageResult, denominatorStatus);
        return numerator / denominator;
    }

    /**
     * Rate calculation with some untested elements
     * @param statusSet
     * @param fullCoverageResult
     * @param untestedElementsCount
     * @return
     */
    private double doRateCalculation(Set<ExecutionStatus> statusSet, Map<ExecutionStatus, Long> fullCoverageResult,
            Long untestedElementsCount) {
        //Implicit conversion of all Long and Integer in floating point number to allow proper rate operation
        double execWithRequiredStatus = countforStatus(fullCoverageResult, statusSet);
        double allExecutionCount = getCandidateExecCount(fullCoverageResult);
        double nbTCWithoutItpi = untestedElementsCount;
        return execWithRequiredStatus / (allExecutionCount + nbTCWithoutItpi);
    }

    private Long getCandidateExecCount(Map<ExecutionStatus, Long> fullCoverageResult) {
        Long nbStatus = 0L;
        for (Long countForOneStatus : fullCoverageResult.values()) {
            nbStatus += countForOneStatus;
        }
        return nbStatus;
    }

    private Long countforStatus(Map<ExecutionStatus, Long> fullCoverageResult, Set<ExecutionStatus> statusSet) {
        Long count = 0L;
        for (Entry<ExecutionStatus, Long> executionStatus : fullCoverageResult.entrySet()) {
            if (statusSet.contains(executionStatus.getKey())) {

                count += executionStatus.getValue();
            }
        }
        return count;
    }

    private Set<ExecutionStatus> getVerifiedStatus() {
        Set<ExecutionStatus> verifiedStatus = new HashSet<>();
        verifiedStatus.add(ExecutionStatus.SUCCESS);
        verifiedStatus.add(ExecutionStatus.SETTLED);
        verifiedStatus.add(ExecutionStatus.FAILURE);
        verifiedStatus.add(ExecutionStatus.BLOCKED);
        verifiedStatus.add(ExecutionStatus.UNTESTABLE);
        return verifiedStatus;
    }

    private Set<ExecutionStatus> getValidatedStatus() {
        Set<ExecutionStatus> verifiedStatus = new HashSet<>();
        verifiedStatus.add(ExecutionStatus.SUCCESS);
        verifiedStatus.add(ExecutionStatus.SETTLED);
        return verifiedStatus;
    }

    private Map<ExecutionStatus, Long> findResultsForSimpleCoverage(List<Long> testCaseIds, List<Long> iterationIds,
            Map<Long, Long> nbSimpleCoverageByTestCase) {
        List<TestCaseExecutionStatus> testCaseExecutionStatus = iterationDao
                .findExecStatusForIterationsAndTestCases(testCaseIds, iterationIds);
        Map<ExecutionStatus, Long> computedResults = new EnumMap<>(ExecutionStatus.class);
        for (TestCaseExecutionStatus oneTCES : testCaseExecutionStatus) {
            ExecutionStatus status = oneTCES.getStatus();
            Long nbCoverage = nbSimpleCoverageByTestCase.get(oneTCES.getTestCaseId());
            if (computedResults.containsKey(status)) {
                computedResults.put(status, computedResults.get(status) + nbCoverage);
            } else {
                computedResults.put(status, nbCoverage);
            }
        }
        return computedResults;
    }

    /**
     * Part the {@link RequirementVersionCoverage} list in two list :
     * One with {@link RequirementVersionCoverage} with linked test steps.
     * One with {@link RequirementVersionCoverage} without linked test steps.
     * This is necessary as {@link RequirementVersionCoverage} with {@link TestStep} linked must be treated at step level
     * @param requirementVersionCoverages
     * @param simpleCoverage
     * @param stepedCoverage
     * @param nbSimpleCoverageByTestCase
     */
    private void partRequirementVersionCoverage(Set<RequirementVersionCoverage> requirementVersionCoverages,
            List<RequirementVersionCoverage> simpleCoverage, List<RequirementVersionCoverage> stepedCoverage,
            Map<Long, Long> nbSimpleCoverageByTestCase, Map<Long, Long> nbSteppedCoverageByTestCase) {
        for (RequirementVersionCoverage requirementVersionCoverage : requirementVersionCoverages) {
            Long tcId = requirementVersionCoverage.getVerifyingTestCase().getId();
            if (requirementVersionCoverage.hasSteps()) {
                stepedCoverage.add(requirementVersionCoverage);
                if (nbSteppedCoverageByTestCase.containsKey(tcId)) {
                    nbSteppedCoverageByTestCase.put(tcId, nbSteppedCoverageByTestCase.get(tcId) + 1);
                } else {
                    nbSteppedCoverageByTestCase.put(tcId, 1L);
                }
            } else {
                simpleCoverage.add(requirementVersionCoverage);
                if (nbSimpleCoverageByTestCase.containsKey(tcId)) {
                    nbSimpleCoverageByTestCase.put(tcId, nbSimpleCoverageByTestCase.get(tcId) + 1);
                } else {
                    nbSimpleCoverageByTestCase.put(tcId, 1L);
                }
            }
        }

    }

    private void findCoverageRate(Requirement mainRequirement, RequirementVersion mainVersion,
            List<RequirementVersion> descendants, RequirementCoverageStat stats) {

        Rate coverageRate = new Rate();
        boolean hasValidDescendant = !descendants.isEmpty();
        coverageRate.setRequirementVersionRate(calculateCoverageRate(mainVersion));

        if (hasValidDescendant) {
            coverageRate.setRequirementVersionChildrenRate(calculateCoverageRate(descendants));
            List<RequirementVersion> all = getAllRequirementVersion(mainVersion, descendants);
            coverageRate.setRequirementVersionGlobalRate(calculateCoverageRate(all));
            coverageRate.setAncestor(true);
        }
        stats.addRate("coverage", coverageRate);
        stats.setAncestor(hasValidDescendant);
    }

    private List<RequirementVersion> getAllRequirementVersion(RequirementVersion mainVersion,
            List<RequirementVersion> descendants) {
        List<RequirementVersion> all = new ArrayList<>();
        all.add(mainVersion);
        all.addAll(descendants);
        return all;
    }

    private double calculateCoverageRate(List<RequirementVersion> rvs) {
        double total = 0;
        double size = rvs.size();
        for (RequirementVersion rv : rvs) {
            total += calculateCoverageRate(rv);
        }
        return total / size;
    }

    /**
     * Coverage Rate is 100% for 1+ {@link TestCase} linked to this {@link RequirementVersion}. 0% if no link
     * @param mainVersion
     * @return
     */
    private Long calculateCoverageRate(RequirementVersion mainVersion) {
        if (!mainVersion.getRequirementVersionCoverages().isEmpty()) {
            return 1L;
        }
        return 0L;
    }

    private List<RequirementVersion> findValidDescendants(Requirement requirement) {
        List<Long> candidatesIds = requirementDao.findDescendantRequirementIds(Arrays.asList(requirement.getId()));
        List<Requirement> candidates = requirementDao.findAllByIds(candidatesIds);
        return extractCurrentVersions(candidates);
    }

    private List<RequirementVersion> extractCurrentVersions(List<Requirement> requirements) {

        Optional<Milestone> activeMilestone = activeMilestoneHolder.getActiveMilestone();

        List<RequirementVersion> rvs = new ArrayList<>(requirements.size());
        for (Requirement requirement : requirements) {
            RequirementVersion rv = requirement.getResource();

            if (rv.isNotObsolete()) {

                if (!activeMilestone.isPresent() || rv.getMilestones().contains(activeMilestone.get())) {
                    rvs.add(rv);
                }

            }
        }
        return rvs;
    }

}