org.squashtest.tm.service.internal.testautomation.AutomatedSuiteManagerServiceImpl.java Source code

Java tutorial

Introduction

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

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.MultiValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.squashtest.tm.core.foundation.lang.Couple;
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.customfield.CustomFieldValue;
import org.squashtest.tm.domain.execution.Execution;
import org.squashtest.tm.domain.execution.ExecutionStatus;
import org.squashtest.tm.domain.testautomation.AutomatedExecutionExtender;
import org.squashtest.tm.domain.testautomation.AutomatedSuite;
import org.squashtest.tm.domain.testautomation.AutomatedTest;
import org.squashtest.tm.domain.testautomation.TestAutomationProject;
import org.squashtest.tm.domain.testcase.TestCase;
import org.squashtest.tm.exception.execution.TestPlanItemNotExecutableException;
import org.squashtest.tm.service.advancedsearch.IndexationService;
import org.squashtest.tm.service.customfield.CustomFieldValueFinderService;
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.*;
import org.squashtest.tm.service.security.PermissionEvaluationService;
import org.squashtest.tm.service.security.PermissionsUtils;
import org.squashtest.tm.service.testautomation.AutomatedExecutionSetIdentifier;
import org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService;
import org.squashtest.tm.service.testautomation.TestAutomationCallbackService;
import org.squashtest.tm.service.testautomation.model.SuiteExecutionConfiguration;
import org.squashtest.tm.service.testautomation.model.TestAutomationProjectContent;
import org.squashtest.tm.service.testautomation.spi.TestAutomationConnector;
import org.squashtest.tm.service.testautomation.spi.TestAutomationException;
import org.squashtest.tm.service.testautomation.spi.UnknownConnectorKind;

import javax.inject.Inject;
import javax.inject.Provider;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;

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

@Transactional
@Service("squashtest.tm.service.AutomatedSuiteManagementService")
public class AutomatedSuiteManagerServiceImpl implements AutomatedSuiteManagerService {

    private static final String DELETE = "DELETE";

    private static final String EXECUTE = "EXECUTE";

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

    private static final int DEFAULT_THREAD_TIMEOUT = 30000; // timeout as milliseconds

    private int timeoutMillis = DEFAULT_THREAD_TIMEOUT;

    @Inject
    private AutomatedSuiteDao autoSuiteDao;

    @Inject
    private IterationDao iterationDao;

    @Inject
    private TestSuiteDao testSuiteDao;

    @Inject
    private IterationTestPlanDao testPlanDao;

    @Inject
    private ExecutionDao executionDao;

    @Inject
    private CustomFieldValueFinderService customFieldValueFinder;

    @Inject
    private PrivateDenormalizedFieldValueService denormalizedFieldValueService;

    @Inject
    private TestAutomationCallbackService callbackService;

    @Inject
    private Provider<TaParametersBuilder> paramBuilder;

    @Inject
    private TestAutomationConnectorRegistry connectorRegistry;

    @Inject
    private PermissionEvaluationService permissionService;

    @Inject
    private CampaignNodeDeletionHandler deletionHandler;

    @Inject
    private IndexationService indexationService;

    @Inject
    private PrivateCustomFieldValueService customFieldValuesService;

    public int getTimeoutMillis() {
        return timeoutMillis;
    }

    public void setTimeoutMillis(int timeoutMillis) {
        this.timeoutMillis = timeoutMillis;
    }

    /**
     *
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#findById(java.lang.String)
     */
    @Override
    public AutomatedSuite findById(String id) {
        return autoSuiteDao.findById(id);
    }

    /**
     *
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#createFromIterationTestPlan(long)
     */
    @Override
    @PreAuthorize("hasPermission(#iterationId, 'org.squashtest.tm.domain.campaign.Iteration', 'EXECUTE')"
            + OR_HAS_ROLE_ADMIN)
    public AutomatedSuite createFromIterationTestPlan(long iterationId) {
        Iteration iteration = iterationDao.findById(iterationId);
        List<IterationTestPlanItem> items = iteration.getTestPlans();
        return createFromItems(items);
    }

    /**
     *
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#createFromTestSuiteTestPlan(long)
     */
    @Override
    @PreAuthorize("hasPermission(#testSuiteId, 'org.squashtest.tm.domain.campaign.TestSuite', 'EXECUTE')"
            + OR_HAS_ROLE_ADMIN)
    public AutomatedSuite createFromTestSuiteTestPlan(long testSuiteId) {
        TestSuite suite = testSuiteDao.findById(testSuiteId);
        List<IterationTestPlanItem> items = suite.getTestPlan();
        return createFromItems(items);
    }

    /**
     *
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#sortByProject(java.lang.String)
     */
    @Override
    public Collection<TestAutomationProjectContent> sortByProject(String autoSuiteId) {
        // security delegated to sortByProject(AutomatedSuite)
        AutomatedSuite suite = findById(autoSuiteId);
        return sortByProject(suite);
    }

    /**
     *
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#sortByProject(org.squashtest.tm.domain.testautomation.AutomatedSuite)
     */

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public Collection<TestAutomationProjectContent> sortByProject(AutomatedSuite suite) {
        // security handled by in the code

        List<AutomatedExecutionExtender> extenders = suite.getExecutionExtenders();

        PermissionsUtils.checkPermission(permissionService, extenders, EXECUTE);

        // first sort them using a map
        MultiMap testsByProjects = new MultiValueMap();

        for (AutomatedExecutionExtender extender : extenders) {
            if (extender.isProjectDisassociated()) {
                continue;
            }
            TestAutomationProject project = extender.getAutomatedProject();
            AutomatedTest test = extender.getAutomatedTest();

            testsByProjects.put(project, test);
        }

        // now make a friendly bean of it
        Collection<TestAutomationProjectContent> projectContents = new LinkedList<>();

        Set<Entry> entries = testsByProjects.entrySet();
        for (Entry e : entries) {
            TestAutomationProject project = (TestAutomationProject) e.getKey();
            Collection<AutomatedTest> tests = (Collection) e.getValue();
            TestAutomationConnector connector = connectorRegistry
                    .getConnectorForKind(project.getServer().getKind());
            boolean orderGuaranteed = connector.testListIsOrderGuaranteed(tests);
            projectContents.add(new TestAutomationProjectContent(project, tests, orderGuaranteed));
        }

        return projectContents;

    }

    /**
     *
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#delete(java.lang.String)
     */
    @Override
    // security delegated to delete(AutomatedSuite)
    public void delete(String automatedSuiteId) {
        AutomatedSuite suite = findById(automatedSuiteId);
        delete(suite);
    }

    /**
     *
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#delete(org.squashtest.tm.domain.testautomation.AutomatedSuite)
     */
    @SuppressWarnings("unchecked")
    @Override
    // security handled in the code
    public void delete(AutomatedSuite suite) {

        PermissionsUtils.checkPermission(permissionService, suite.getExecutionExtenders(), DELETE);

        List<AutomatedExecutionExtender> toremove = new ArrayList<>(suite.getExecutionExtenders());
        suite.getExecutionExtenders().clear();

        List<Execution> execs = new ArrayList<>(CollectionUtils.collect(toremove, new ExecutionCollector()));

        deletionHandler.deleteExecutions(execs);

        autoSuiteDao.delete(suite);
    }

    @Override
    @PostFilter("hasPermission(filterObject, 'READ')" + OR_HAS_ROLE_ADMIN)
    @Transactional(readOnly = true)
    public List<Execution> findExecutionsByAutomatedTestSuiteId(String automatedTestSuiteId) {

        List<Execution> executions = new ArrayList<>();
        AutomatedSuite suite = autoSuiteDao.findById(automatedTestSuiteId);
        for (AutomatedExecutionExtender e : suite.getExecutionExtenders()) {
            executions.add(e.getExecution());
        }
        return executions;
    }

    @Override
    // security delegated to start(AutomatedSuite, Collection)
    public void start(String autoSuiteId) {
        AutomatedSuite suite = autoSuiteDao.findById(autoSuiteId);
        start(suite, new ArrayList<SuiteExecutionConfiguration>());
    }

    @Override
    // security delegated to start(AutomatedSuite, Collection)
    public void start(AutomatedSuite suite) {
        start(suite, new ArrayList<SuiteExecutionConfiguration>());
    }

    @Override
    // security delegated to start(AutomatedSuite, Collection)
    public void start(String suiteId, Collection<SuiteExecutionConfiguration> configuration) {
        AutomatedSuite suite = autoSuiteDao.findById(suiteId);
        start(suite, configuration);
    }

    @Override
    // security handled in the code
    public void start(AutomatedSuite suite, Collection<SuiteExecutionConfiguration> configuration) {

        PermissionsUtils.checkPermission(permissionService, suite.getExecutionExtenders(), EXECUTE);

        ExtenderSorter sorter = new ExtenderSorter(suite, configuration);

        TestAutomationCallbackService securedCallback = new CallbackServiceSecurityWrapper(callbackService);

        while (sorter.hasNext()) {

            Entry<String, Collection<AutomatedExecutionExtender>> extendersByKind = sorter.getNextEntry();

            TestAutomationConnector connector;

            try {
                connector = connectorRegistry.getConnectorForKind(extendersByKind.getKey());
                Collection<Couple<AutomatedExecutionExtender, Map<String, Object>>> tests = collectAutomatedExecs(
                        extendersByKind.getValue());
                connector.executeParameterizedTests(tests, suite.getId(), securedCallback);
            } catch (UnknownConnectorKind ex) {
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error("Test Automation : unknown connector :", ex);
                }
                notifyExecutionError(extendersByKind.getValue(), ex.getMessage());
            } catch (TestAutomationException ex) {
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error("Test Automation : an error occured :", ex);
                }
                notifyExecutionError(extendersByKind.getValue(), ex.getMessage());
            }

        }
    }

    // ******************* create suite private methods ***************************

    private AutomatedSuite createFromItems(List<IterationTestPlanItem> items) {

        AutomatedSuite newSuite = autoSuiteDao.createNewSuite();

        for (IterationTestPlanItem item : items) {
            if (item.isAutomated()) {
                Execution exec = addAutomatedExecution(item);
                newSuite.addExtender(exec.getAutomatedExecutionExtender());
            }
        }

        return newSuite;

    }

    private Execution addAutomatedExecution(IterationTestPlanItem item) throws TestPlanItemNotExecutableException {

        Execution execution = item.createAutomatedExecution();

        executionDao.persist(execution);
        item.addExecution(execution);

        createCustomFieldsForExecutionAndExecutionSteps(execution);
        createDenormalizedFieldsForExecutionAndExecutionSteps(execution);
        indexationService.reindexTestCase(item.getReferencedTestCase().getId());

        return execution;
    }

    private void createCustomFieldsForExecutionAndExecutionSteps(Execution execution) {
        customFieldValuesService.createAllCustomFieldValues(execution, execution.getProject());
        customFieldValuesService.createAllCustomFieldValues(execution.getSteps(), execution.getProject());
    }

    private void createDenormalizedFieldsForExecutionAndExecutionSteps(Execution execution) {
        LOGGER.debug("Create denormalized fields for Execution {}", execution.getId());

        TestCase sourceTC = execution.getReferencedTestCase();
        denormalizedFieldValueService.createAllDenormalizedFieldValues(sourceTC, execution);
        denormalizedFieldValueService.createAllDenormalizedFieldValuesForSteps(execution);

    }

    // ******************* execute suite private methods **************************

    private Collection<Couple<AutomatedExecutionExtender, Map<String, Object>>> collectAutomatedExecs(
            Collection<AutomatedExecutionExtender> extenders) {

        Collection<Couple<AutomatedExecutionExtender, Map<String, Object>>> tests = new ArrayList<>(
                extenders.size());

        for (AutomatedExecutionExtender extender : extenders) {
            tests.add(createAutomatedExecAndParams(extender));
        }

        return tests;

    }

    private Couple<AutomatedExecutionExtender, Map<String, Object>> createAutomatedExecAndParams(
            AutomatedExecutionExtender extender) {
        Execution execution = extender.getExecution();

        Collection<CustomFieldValue> tcFields = customFieldValueFinder
                .findAllCustomFieldValues(execution.getReferencedTestCase());
        Collection<CustomFieldValue> iterFields = customFieldValueFinder
                .findAllCustomFieldValues(execution.getIteration());
        Collection<CustomFieldValue> campFields = customFieldValueFinder
                .findAllCustomFieldValues(execution.getCampaign());

        Map<String, Object> params = paramBuilder.get().testCase().addEntity(execution.getReferencedTestCase())
                .addCustomFields(tcFields).iteration().addCustomFields(iterFields).campaign()
                .addCustomFields(campFields).build();

        return new Couple<>(extender, params);
    }

    private void notifyExecutionError(Collection<AutomatedExecutionExtender> failedExecExtenders, String message) {
        for (AutomatedExecutionExtender extender : failedExecExtenders) {
            extender.setExecutionStatus(ExecutionStatus.ERROR);
            extender.setResultSummary(message);
        }
    }

    /**
     * That wrapper is a TestAutomationCallbackService, that ensures that the security context is properly set for any
     * thread that requires its services.
     *
     * @author bsiri
     *
     */
    private static class CallbackServiceSecurityWrapper implements TestAutomationCallbackService {

        private SecurityContext secContext;

        private TestAutomationCallbackService wrapped;

        /*
         * the SecurityContext here is the one from the original thread. The others methods will use that instance of
         * SecurityContext for all their operations from now on (see the code, it's straightforward).
         */
        CallbackServiceSecurityWrapper(TestAutomationCallbackService service) {
            secContext = SecurityContextHolder.getContext();
            wrapped = service;
        }

        @Override
        public void updateResultURL(AutomatedExecutionSetIdentifier execIdentifier, URL resultURL) {
            SecurityContextHolder.setContext(secContext);
            wrapped.updateResultURL(execIdentifier, resultURL);
        }

        @Override
        public void updateExecutionStatus(AutomatedExecutionSetIdentifier execIdentifier,
                ExecutionStatus newStatus) {
            SecurityContextHolder.setContext(secContext);
            wrapped.updateExecutionStatus(execIdentifier, newStatus);

        }

        @Override
        public void updateResultSummary(AutomatedExecutionSetIdentifier execIdentifier, String newSummary) {
            SecurityContextHolder.setContext(secContext);
            wrapped.updateResultSummary(execIdentifier, newSummary);
        }

    }

    private static class ExtenderSorter {

        private Map<Long, SuiteExecutionConfiguration> configurationByProject;

        private Map<String, Collection<AutomatedExecutionExtender>> extendersByKind;

        private Iterator<Entry<String, Collection<AutomatedExecutionExtender>>> iterator = null;

        public ExtenderSorter(AutomatedSuite suite, Collection<SuiteExecutionConfiguration> configuration) {

            configurationByProject = new HashMap<>(configuration.size());

            for (SuiteExecutionConfiguration conf : configuration) {
                configurationByProject.put(conf.getProjectId(), conf);
            }

            // rem : previous impl relied on a HashMap, which broke the tests on java 8. as I have no damn clue about
            // the desired order, let's retort to keys natural order using a TreeMap
            extendersByKind = new TreeMap();

            for (AutomatedExecutionExtender extender : suite.getExecutionExtenders()) {

                String serverKind = extender.getAutomatedTest().getProject().getServer().getKind();

                register(extender, serverKind);

            }

            iterator = extendersByKind.entrySet().iterator();

        }

        public boolean hasNext() {
            return iterator.hasNext();
        }

        public Map.Entry<String, Collection<AutomatedExecutionExtender>> getNextEntry() {

            return iterator.next();

        }

        private void register(AutomatedExecutionExtender extender, String serverKind) {

            if (!extendersByKind.containsKey(serverKind)) {
                extendersByKind.put(serverKind, new LinkedList<AutomatedExecutionExtender>());
            }

            SuiteExecutionConfiguration conf = configurationByProject.get(extender.getAutomatedProject().getId());
            if (conf != null) {
                extender.setNodeName(conf.getNode());
            }

            extendersByKind.get(serverKind).add(extender);

        }

    }

    private static final class ExecutionCollector implements Transformer {
        @Override
        public Object transform(Object input) {
            return ((AutomatedExecutionExtender) input).getExecution();
        }
    }

    /**
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#createFromItemsAndIteration(java.util.List, long)
     */
    @Override
    public AutomatedSuite createFromItemsAndIteration(List<Long> testPlanIds, long iterationId) {
        PermissionsUtils.checkPermission(permissionService, testPlanIds, EXECUTE,
                IterationTestPlanItem.class.getName());

        List<IterationTestPlanItem> items = testPlanDao.findAllByIdsOrderedByIterationTestPlan(testPlanIds);

        return createFromItems(items);
    }

    /**
     * @see org.squashtest.tm.service.testautomation.AutomatedSuiteManagerService#createFromItemsAndTestSuite(java.util.List, long)
     */
    @Override
    public AutomatedSuite createFromItemsAndTestSuite(List<Long> testPlanIds, long testSuiteId) {
        PermissionsUtils.checkPermission(permissionService, testPlanIds, EXECUTE,
                IterationTestPlanItem.class.getName());

        List<IterationTestPlanItem> items = testPlanDao.findAllByIdsOrderedBySuiteTestPlan(testPlanIds,
                testSuiteId);

        return createFromItems(items);
    }

}