org.squashtest.tm.service.internal.deletion.CampaignDeletionHandlerImpl.java Source code

Java tutorial

Introduction

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.squashtest.tm.domain.campaign.Campaign;
import org.squashtest.tm.domain.campaign.CampaignFolder;
import org.squashtest.tm.domain.campaign.CampaignLibraryNode;
import org.squashtest.tm.domain.campaign.CampaignTestPlanItem;
import org.squashtest.tm.domain.campaign.Iteration;
import org.squashtest.tm.domain.campaign.IterationTestPlanItem;
import org.squashtest.tm.domain.campaign.TestSuite;
import org.squashtest.tm.domain.execution.Execution;
import org.squashtest.tm.domain.execution.ExecutionStep;
import org.squashtest.tm.domain.milestone.Milestone;
import org.squashtest.tm.domain.testautomation.AutomatedExecutionExtender;
import org.squashtest.tm.service.campaign.IterationTestPlanManagerService;
import org.squashtest.tm.service.deletion.BoundToLockedMilestonesReport;
import org.squashtest.tm.service.deletion.BoundToMultipleMilestonesReport;
import org.squashtest.tm.service.deletion.BoundToNotSelectedTestSuite;
import org.squashtest.tm.service.deletion.MilestoneModeNoFolderDeletion;
import org.squashtest.tm.service.deletion.NotDeletableCampaignsPreviewReport;
import org.squashtest.tm.service.deletion.OperationReport;
import org.squashtest.tm.service.deletion.SingleOrMultipleMilestonesReport;
import org.squashtest.tm.service.deletion.SuppressionPreviewReport;
import org.squashtest.tm.service.internal.campaign.CampaignNodeDeletionHandler;
import org.squashtest.tm.service.internal.customfield.PrivateCustomFieldValueService;
import org.squashtest.tm.service.internal.denormalizedField.PrivateDenormalizedFieldValueService;
import org.squashtest.tm.service.internal.repository.AutomatedTestDao;
import org.squashtest.tm.service.internal.repository.CampaignDao;
import org.squashtest.tm.service.internal.repository.CampaignDeletionDao;
import org.squashtest.tm.service.internal.repository.CampaignFolderDao;
import org.squashtest.tm.service.internal.repository.FolderDao;
import org.squashtest.tm.service.internal.repository.IterationDao;
import org.squashtest.tm.service.internal.repository.TestSuiteDao;
import org.squashtest.tm.service.milestone.ActiveMilestoneHolder;
import org.squashtest.tm.service.security.PermissionEvaluationService;
import org.squashtest.tm.service.security.PermissionsUtils;
import org.squashtest.tm.service.security.SecurityCheckableObject;

import com.google.common.base.Optional;

@Component("squashtest.tm.service.deletion.CampaignNodeDeletionHandler")
public class CampaignDeletionHandlerImpl extends AbstractNodeDeletionHandler<CampaignLibraryNode, CampaignFolder>
        implements CampaignNodeDeletionHandler {

    private static final String CAMPAIGNS_TYPE = "campaigns";
    private static final Logger LOGGER = LoggerFactory.getLogger(CampaignDeletionHandlerImpl.class);

    @Inject
    private CampaignFolderDao folderDao;

    @Inject
    private CampaignDeletionDao deletionDao;

    @Inject
    private CampaignDao campaignDao;

    @Inject
    private IterationDao iterationDao;

    @Inject
    private TestSuiteDao suiteDao;

    @Inject
    private AutomatedTestDao autoTestDao;

    @Inject
    private PrivateCustomFieldValueService customValueService;

    @Inject
    private PrivateDenormalizedFieldValueService denormalizedFieldValueService;

    @Inject
    private PermissionEvaluationService permissionEvaluationService;

    @Inject
    private IterationTestPlanManagerService iterationTestPlanManagerService;

    @Inject
    private ActiveMilestoneHolder activeMilestoneHolder;

    @Override
    protected FolderDao<CampaignFolder, CampaignLibraryNode> getFolderDao() {
        return folderDao;
    }

    /* ************************** diagnostic section ******************************* */

    @Override
    protected List<SuppressionPreviewReport> diagnoseSuppression(List<Long> nodeIds) {

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

        List<SuppressionPreviewReport> reportList = new ArrayList<>();

        List<Campaign> campaigns = campaignDao.findAllByIds(nodeIds);

        //by default the user is assumed to be allowed to delete the campaigns without warning
        reportLocksByInsufficientPrivileges(campaigns, reportList);

        // always check for nodes locked by milestones
        reportLocksByMilestone(nodeIds, reportList);

        // milestone mode : additional checks
        if (activeMilestone.isPresent()) {

            // separate the campaigns from the folders
            List<Long>[] separateIds = deletionDao.separateFolderFromCampaignIds(nodeIds);

            // no folder shall be deleted
            reportNoFoldersAllowed(separateIds[0], reportList);

            // check if some elements are bound to multiple milestones
            reportMultipleMilestoneBinding(separateIds[1], reportList);
        }

        return reportList;
    }

    protected void reportMultipleMilestoneBinding(List<Long> campaignIds,
            List<SuppressionPreviewReport> reportList) {
        List<Long> boundNodes = campaignDao.findCampaignIdsHavingMultipleMilestones(campaignIds);
        if (!boundNodes.isEmpty()) {
            // case 1 : all the test cases are bound to multiple milestones
            if (campaignIds.size() == boundNodes.size()) {
                reportList.add(new BoundToMultipleMilestonesReport(CAMPAIGNS_TYPE));
            }
            // case 2 : there is a mixed cases of test cases that will be removed
            // from the milestone and some will be removed - period
            else {
                reportList.add(new SingleOrMultipleMilestonesReport(CAMPAIGNS_TYPE));
            }
        }
    }

    protected void reportNoFoldersAllowed(List<Long> folderIds, List<SuppressionPreviewReport> reportList) {
        if (!folderIds.isEmpty()) {
            reportList.add(new MilestoneModeNoFolderDeletion(CAMPAIGNS_TYPE));
        }
    }

    protected void reportLocksByMilestone(List<Long> nodeIds, List<SuppressionPreviewReport> reportList) {
        List<Long> lockedNodes = deletionDao.findCampaignsWhichMilestonesForbidsDeletion(nodeIds);
        if (!lockedNodes.isEmpty()) {
            reportList.add(new BoundToLockedMilestonesReport(CAMPAIGNS_TYPE));
        }
    }

    protected void reportLocksByInsufficientPrivileges(List<Campaign> campaigns,
            List<SuppressionPreviewReport> reportList) {
        NotDeletableCampaignsPreviewReport report;
        for (Campaign campaign : campaigns) {

            if (campaignDao.countRunningOrDoneExecutions(campaign.getId()) > 0) {

                try {
                    PermissionsUtils.checkPermission(permissionEvaluationService,
                            new SecurityCheckableObject(campaign, "EXTENDED_DELETE"));

                    //The user is allowed to delete the campaign but must be warned
                    report = new NotDeletableCampaignsPreviewReport();
                    report.addName(campaign.getName());
                    report.setHasRights(true);
                    reportList.add(report);
                } catch (AccessDeniedException exception) { // NOSONAR : this exception is part of the nominal use case

                    //The user is not allowed to delete the campaign
                    report = new NotDeletableCampaignsPreviewReport();
                    report.addName(campaign.getName());
                    report.setHasRights(false);
                    reportList.add(report);
                }

            }
        }
    }

    @Override
    public List<SuppressionPreviewReport> simulateIterationDeletion(List<Long> targetIds) {

        List<SuppressionPreviewReport> reportList = new ArrayList<>();
        NotDeletableCampaignsPreviewReport report;
        List<Iteration> iterations = iterationDao.findAllByIds(targetIds);

        //by default the user is assumed to be allowed to delete the iterations without warning

        for (Iteration iteration : iterations) {

            if (iterationDao.countRunningOrDoneExecutions(iteration.getId()) > 0) {

                try {
                    PermissionsUtils.checkPermission(permissionEvaluationService,
                            new SecurityCheckableObject(iteration, "EXTENDED_DELETE"));

                    //The user is allowed to delete the iteration but must be warned
                    report = new NotDeletableCampaignsPreviewReport();
                    report.addName(iteration.getName());
                    report.setHasRights(true);
                    reportList.add(report);
                } catch (AccessDeniedException exception) {// NOSONAR : this exception is part of the nominal use case
                    LOGGER.trace("The user is not allowed to delete the iteration");
                    report = new NotDeletableCampaignsPreviewReport();
                    report.addName(iteration.getName());
                    report.setHasRights(false);
                    reportList.add(report);
                }
            }
        }

        return reportList;
    }

    @Override
    public List<SuppressionPreviewReport> simulateExecutionDeletion(Long execId) {

        // TODO : implement the specs when they are ready. Default is "nothing special".
        return Collections.emptyList();
    }

    @Override
    public List<SuppressionPreviewReport> simulateSuiteDeletion(List<Long> targetIds) {

        List<SuppressionPreviewReport> reportList = new ArrayList<>();

        List<TestSuite> suites = suiteDao.findAllByIds(targetIds);

        // Check that test case do not belong to other suite that is not in the selection

        if (containTestPlanItemThatBelongToOtherTestSuite(suites, targetIds)) {
            reportList.add(new BoundToNotSelectedTestSuite());
        }

        // check test case execution
        addTcExecutionErrorToReport(reportList, suites);

        return reportList;
    }

    private void addTcExecutionErrorToReport(List<SuppressionPreviewReport> reportList, List<TestSuite> suites) {

        NotDeletableCampaignsPreviewReport report;
        for (TestSuite suite : suites) {
            if (containExecutedTc(suite)) {
                try {
                    PermissionsUtils.checkPermission(permissionEvaluationService,
                            new SecurityCheckableObject(suite, "EXTENDED_DELETE"));

                    // The user is allowed to delete the test suite but must be warned
                    report = new NotDeletableCampaignsPreviewReport();
                    report.addName(suite.getName());
                    report.setHasRights(true);
                    reportList.add(report);
                } catch (AccessDeniedException exception) {// NOSONAR : this exception is part of the nominal use case

                    // The user is not allowed to delete the test suite
                    report = new NotDeletableCampaignsPreviewReport();
                    report.addName(suite.getName());
                    report.setHasRights(false);
                    reportList.add(report);
                }

            }

        }

    }

    private boolean containExecutedTc(TestSuite suite) {

        for (IterationTestPlanItem itpi : suite.getTestPlan()) {

            if (!itpi.getExecutions().isEmpty()) {
                return true;
            }

        }

        return false;
    }

    private boolean containTestPlanItemThatBelongToOtherTestSuite(List<TestSuite> suites, List<Long> targetIds) {

        for (TestSuite suite : suites) {

            for (IterationTestPlanItem itpi : suite.getTestPlan()) {

                for (TestSuite ts : itpi.getTestSuites()) {

                    if (!targetIds.contains(ts.getId())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /* *************************locked entities detection section ******************* */

    @Override
    protected List<Long> detectLockedNodes(List<Long> nodeIds) {

        List<Campaign> campaigns = campaignDao.findAllByIds(nodeIds);
        List<Long> lockedNodes = new ArrayList<>(nodeIds.size());

        for (Campaign campaign : campaigns) {

            if (campaignDao.countRunningOrDoneExecutions(campaign.getId()) > 0) {
                try {
                    PermissionsUtils.checkPermission(permissionEvaluationService,
                            new SecurityCheckableObject(campaign, "EXTENDED_DELETE"));
                } catch (AccessDeniedException exception) { // NOSONAR : this exception is part of the nominal use case
                    lockedNodes.add(campaign.getId());
                }
            }
        }

        // check for locked campaigns
        List<Long> lockedByMilestones = deletionDao.findCampaignsWhichMilestonesForbidsDeletion(nodeIds);
        lockedNodes.addAll(lockedByMilestones);

        Optional<Milestone> activeMilestone = activeMilestoneHolder.getActiveMilestone();
        /*
         * milestone mode provides additional checks :
         * - 1) no folder shall be deleted (enqueued outright)
         * - 2) no campaign that doesn't belong to the milestone shall be deleted
         * - 3) no campaign bound to more than one milestone shall be deleted (they will be unbound though, but later).
         */
        if (activeMilestone.isPresent()) {

            // 1 - no folder shall be deleted
            List<Long> folderIds = deletionDao.separateFolderFromCampaignIds(nodeIds)[0];

            // 2 - no campaign that aren't bound to the current milestone shall be deleted
            List<Long> notBoundToMilestone = campaignDao.findNonBoundCampaign(nodeIds,
                    activeMilestone.get().getId());

            // 3 - no campaign bound to more than one milestone shall be deleted
            List<Long> boundToMoreMilestones = campaignDao.findCampaignIdsHavingMultipleMilestones(nodeIds);

            lockedNodes.addAll(folderIds);
            lockedNodes.addAll(boundToMoreMilestones);
            lockedNodes.addAll(notBoundToMilestone);

        }

        return lockedNodes;
    }

    /* *********************************************************************************
     * deletion section
     *
     * Sorry, no time to implement something smarter. Maybe in future releases ?
     *
     *
     * TODO : - implement a careful deletion procedure once the policies and rules are defined. - improve code
     * efficiency.
     *
     * ******************************************************************************
     */

    @Override
    /*
     * by Nodes we mean the CampaignLibraryNodes.
     */
    protected OperationReport batchDeleteNodes(List<Long> ids) {

        //prepare the operation report:
        List<Long>[] separatedIds = deletionDao.separateFolderFromCampaignIds(ids);

        List<Campaign> campaigns = campaignDao.findAllByIds(ids);

        //empty of those campaigns
        deleteCampaignContent(campaigns);

        // now we can delete the folders as well
        deletionDao.removeEntities(ids);

        //and finally prepare the operation report.
        OperationReport report = new OperationReport();
        report.addRemoved(separatedIds[0], "folder");
        report.addRemoved(separatedIds[1], "campaign");

        return report;
    }

    @Override
    protected OperationReport batchUnbindFromMilestone(List<Long> ids) {

        List<Long> remainingIds = deletionDao.findRemainingCampaignIds(ids);

        // some node should not be unbound.
        List<Long> lockedIds = deletionDao.findCampaignsWhichMilestonesForbidsDeletion(remainingIds);
        remainingIds.removeAll(lockedIds);

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

        deletionDao.unbindFromMilestone(remainingIds, activeMilestone.get().getId());

        report.addRemoved(remainingIds, "campaign");

        return report;
    }

    @Override
    public OperationReport deleteIterations(List<Long> targetIds) {

        List<Iteration> iterations = iterationDao.findAllByIds(targetIds);
        List<Iteration> iterationsToBeDeleted = new ArrayList<>(iterations.size());
        List<Long> deletedTargetIds = new ArrayList<>(targetIds.size());

        for (Iteration iteration : iterations) {

            if (iterationDao.countRunningOrDoneExecutions(iteration.getId()) > 0) {

                try {
                    PermissionsUtils.checkPermission(permissionEvaluationService,
                            new SecurityCheckableObject(iteration, "EXTENDED_DELETE"));
                    registerIterationDeletion(iteration, iterationsToBeDeleted, deletedTargetIds);
                } catch (AccessDeniedException exception) { // NOSONAR : this exception is part of the nominal use case
                    // Apparently, we don't wanna do anything, not even log something.
                }
            } else {
                registerIterationDeletion(iteration, iterationsToBeDeleted, deletedTargetIds);
            }
        }

        doDeleteIterations(iterationsToBeDeleted);

        OperationReport report = new OperationReport();
        report.addRemoved(deletedTargetIds, "iteration");

        return report;
    }

    private void registerIterationDeletion(Iteration iteration, List<Iteration> iterationsToBeDeleted,
            List<Long> deletedTargetIds) {
        Campaign camp = iteration.getCampaign();
        camp.removeIteration(iteration);
        iterationsToBeDeleted.add(iteration);
        deletedTargetIds.add(iteration.getId());
    }

    private void doDeleteSuites(Collection<TestSuite> testSuites) {

        for (TestSuite testSuite : testSuites) {
            for (IterationTestPlanItem testPlanItem : testSuite.getTestPlan()) {
                testPlanItem.getTestSuites().clear();
            }
            testSuite.getIteration().removeTestSuite(testSuite);

            customValueService.deleteAllCustomFieldValues(testSuite);

            deletionDao.removeEntity(testSuite);
        }

        deletionDao.flush();
    }

    @Override
    public void deleteExecution(Execution execution) {
        deleteExecSteps(execution);

        IterationTestPlanItem testPlanItem = execution.getTestPlan();
        testPlanItem.removeExecution(execution);
        deleteAutomatedExecutionExtender(execution);

        denormalizedFieldValueService.deleteAllDenormalizedFieldValues(execution);
        customValueService.deleteAllCustomFieldValues(execution);

        deletionDao.removeEntity(execution);
    }

    /*
     * we just remove the content of a campaign here. The actual removal of the campaign will be processed in the
     * calling methods.
     *
     * The operations carried over a campaign are : - removal of all its iterations,
     *
     * the rest is supposed to cascade normally (node hierarchy, campaign test plans).
     */

    private void deleteCampaignContent(List<Campaign> campaigns) {

        for (Campaign campaign : campaigns) {
            deleteCampaignTestPlan(campaign.getTestPlan());
            campaign.getTestPlan().clear();

            List<Iteration> allIterations = new ArrayList<>(campaign.getIterations());
            campaign.getIterations().clear();
            doDeleteIterations(allIterations);

            customValueService.deleteAllCustomFieldValues(campaign);
        }

    }

    private void deleteCampaignTestPlan(List<CampaignTestPlanItem> itemList) {
        for (CampaignTestPlanItem item : itemList) {
            deletionDao.removeEntity(item);
        }
    }

    /*
     * removing an iteration means :
     * - remove the test suites
     * - removing its test plan,
     * - remove itself from repository.
     */
    private void doDeleteIterations(List<Iteration> iterations) {
        for (Iteration iteration : iterations) {

            Collection<TestSuite> suites = new ArrayList<>(iteration.getTestSuites());
            iteration.getTestSuites().clear();
            doDeleteSuites(suites);

            List<IterationTestPlanItem> items = new ArrayList<>(iteration.getTestPlans());
            iteration.getTestPlans().clear();
            deleteIterationTestPlan(items);

            customValueService.deleteAllCustomFieldValues(iteration);

            deletionDao.removeEntity(iteration);
        }
    }

    /*
     * removing a test plan :
     *
     * - remove the executions
     * - remove itself.
     */
    private void deleteIterationTestPlan(List<IterationTestPlanItem> testPlan) {
        for (IterationTestPlanItem item : testPlan) {
            deleteIterationTestPlanItem(item);
        }
    }

    @Override
    public void deleteIterationTestPlanItem(IterationTestPlanItem item) {
        List<Execution> execs = new ArrayList<>(item.getExecutions());
        deleteExecutions(execs);

        deletionDao.removeEntity(item);
    }

    /*
     *
     */
    @Override
    public void deleteExecutions(List<Execution> executions) {
        Collection<Execution> executionsCopy = new ArrayList<>(executions);
        for (Execution execution : executionsCopy) {
            deleteExecution(execution);
        }
    }

    /*
     * removing the steps mean :
     * - remove the denormalized custom fields,
     * - remote the custom fields,
     * - remove themselves.
     */
    public void deleteExecSteps(Execution execution) {

        /*
         * Even when asking the EntityManager to remove a step - thus assigning it a status DELETED -,
         * it can still abort its deletion when some random flush occurs :
         *
         * flushing the Execution (still in status MANAGED)
         * -> triggers cascade PERSIST on its steps
         * -> thus assign status MANAGED to the step that should have been deleted
         *
         * To prevent that to occur, first thing to do is to clear the step list.
         */

        Collection<ExecutionStep> steps = new ArrayList<>(execution.getSteps());
        execution.getSteps().clear();

        // now we can delete them
        for (ExecutionStep step : steps) {

            denormalizedFieldValueService.deleteAllDenormalizedFieldValues(step);
            customValueService.deleteAllCustomFieldValues(step);
            deletionDao.removeEntity(step);
        }

    }

    private void deleteAutomatedExecutionExtender(Execution execution) {
        if (execution.getAutomatedExecutionExtender() != null) {
            AutomatedExecutionExtender extender = execution.getAutomatedExecutionExtender();
            autoTestDao.removeIfUnused(extender.getAutomatedTest());
            deletionDao.removeEntity(extender);
            execution.setAutomatedExecutionExtender(null);
        }
    }

    @Override
    public OperationReport deleteSuites(List<Long> suiteIds, boolean removeFromIter) {
        List<TestSuite> suites = suiteDao.findAllByIds(suiteIds);

        if (removeFromIter) {
            removeItpiFromIteration(suites, suiteIds);
        }

        doDeleteSuites(suites);

        OperationReport report = new OperationReport();
        report.addRemoved(suiteIds, "test-suite");
        return report;

    }

    @Override
    protected boolean isMilestoneMode() {
        return activeMilestoneHolder.getActiveMilestone().isPresent();
    }

    private void removeItpiFromIteration(List<TestSuite> suites, final List<Long> targetIds) {

        Set<Long> idsToRemove = new HashSet<>();

        // TODO : maybe use a couple of HQL queries that would compute this with more efficiency

        for (TestSuite suite : suites) {

            for (IterationTestPlanItem itpi : suite.getTestPlan()) {

                // Try to find one test suite in the IterationTestPlanItem that is none of the test suites to remove
                Object result = CollectionUtils.find(itpi.getTestSuites(), new Predicate() {
                    @Override
                    public boolean evaluate(Object ts) {
                        TestSuite testSuite = (TestSuite) ts;
                        return !targetIds.contains(testSuite.getId());
                    }
                });

                // If nothing if found (ie : the iteration test plan item only belong to test suite that are selected)
                // we want to remove it from the iteration.
                if (result == null) {
                    idsToRemove.add(itpi.getId());
                }

            }
        }

        for (Long id : idsToRemove) {
            iterationTestPlanManagerService.removeTestPlanFromIteration(id);
        }

    }

}