org.squashtest.tm.service.internal.customfield.PrivateCustomFieldValueServiceImpl.java Source code

Java tutorial

Introduction

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.map.MultiValueMap;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.squashtest.tm.domain.IdentifiedUtil;
import org.squashtest.tm.domain.customfield.BindableEntity;
import org.squashtest.tm.domain.customfield.BoundEntity;
import org.squashtest.tm.domain.customfield.CustomField;
import org.squashtest.tm.domain.customfield.CustomFieldBinding;
import org.squashtest.tm.domain.customfield.CustomFieldValue;
import org.squashtest.tm.domain.customfield.RawValue;
import org.squashtest.tm.domain.customfield.RenderingLocation;
import org.squashtest.tm.domain.project.Project;
import org.squashtest.tm.service.advancedsearch.IndexationService;
import org.squashtest.tm.service.annotation.CachableType;
import org.squashtest.tm.service.annotation.CacheResult;
import org.squashtest.tm.service.internal.repository.BoundEntityDao;
import org.squashtest.tm.service.internal.repository.CustomFieldBindingDao;
import org.squashtest.tm.service.internal.repository.CustomFieldValueDao;
import org.squashtest.tm.service.internal.repository.CustomFieldValueDao.CustomFieldValuesPair;
import org.squashtest.tm.service.security.PermissionEvaluationService;

@Service("squashtest.tm.service.CustomFieldValueManagerService")
@Transactional
public class PrivateCustomFieldValueServiceImpl implements PrivateCustomFieldValueService {

    @Inject
    @Named("defaultEditionStatusStrategy")
    private ValueEditionStatusStrategy defaultEditionStatusStrategy;

    @Inject
    @Named("requirementBoundEditionStatusStrategy")
    private ValueEditionStatusStrategy requirementBoundEditionStatusStrategy;

    @Inject
    private CustomFieldValueDao customFieldValueDao;

    @Inject
    private CustomFieldBindingDao customFieldBindingDao;

    @Inject
    private BoundEntityDao boundEntityDao;

    @Inject
    private PermissionEvaluationService permissionService;

    @Inject
    private IndexationService indexationService;

    public void setPermissionService(PermissionEvaluationService permissionService) {
        this.permissionService = permissionService;
    }

    @Override
    @Transactional(readOnly = true)
    public boolean hasCustomFields(BoundEntity boundEntity) {
        return boundEntityDao.hasCustomField(boundEntity.getBoundEntityId(), boundEntity.getBoundEntityType());
    }

    @Override
    @Transactional(readOnly = true)
    public boolean hasCustomFields(Long boundEntityId, BindableEntity bindableEntity) {
        return boundEntityDao.hasCustomField(boundEntityId, bindableEntity);
    }

    @Override
    @Transactional(readOnly = true)
    public List<CustomFieldValue> findAllCustomFieldValues(BoundEntity boundEntity) {
        if (!permissionService.canRead(boundEntity)) {
            throw new AccessDeniedException("Access is denied");
        }
        return customFieldValueDao.findAllCustomValues(boundEntity.getBoundEntityId(),
                boundEntity.getBoundEntityType());
    }

    @Override
    @Transactional(readOnly = true)
    public List<CustomFieldValue> findAllCustomFieldValues(long boundEntityId, BindableEntity bindableEntity) {

        BoundEntity boundEntity = boundEntityDao.findBoundEntity(boundEntityId, bindableEntity);

        if (!permissionService.canRead(boundEntity)) {
            throw new AccessDeniedException("Access is denied");
        }

        return findAllCustomFieldValues(boundEntity);
    }

    @Override
    // well I'll skip the security check for this one because we don't really want to kill the db
    public List<CustomFieldValue> findAllCustomFieldValues(Collection<? extends BoundEntity> boundEntities) {

        // first, because the entities might be of different kind we must segregate them.
        Map<BindableEntity, List<Long>> compositeIds = breakEntitiesIntoCompositeIds(boundEntities);

        // second, one can now call the db and consolidate the result.
        List<CustomFieldValue> result = new ArrayList<>();

        for (Entry<BindableEntity, List<Long>> entry : compositeIds.entrySet()) {

            result.addAll(customFieldValueDao.batchedFindAllCustomValuesFor(entry.getValue(), entry.getKey()));

        }

        return result;

    }

    // same : no sec, a gesture of mercy for the database
    @Override
    public List<CustomFieldValue> findAllCustomFieldValues(Collection<? extends BoundEntity> boundEntities,
            Collection<CustomField> restrictedToThoseCustomfields) {

        // first, because the entities might be of different kind we must segregate them.
        Map<BindableEntity, List<Long>> compositeIds = breakEntitiesIntoCompositeIds(boundEntities);

        // second, one can now call the db and consolidate the result.
        List<CustomFieldValue> result = new ArrayList<>();

        for (Entry<BindableEntity, List<Long>> entry : compositeIds.entrySet()) {

            result.addAll(customFieldValueDao.batchedRestrictedFindAllCustomValuesFor(entry.getValue(),
                    entry.getKey(), restrictedToThoseCustomfields));

        }

        return result;

    }

    @Override
    public void cascadeCustomFieldValuesCreation(CustomFieldBinding binding) {

        List<BoundEntity> boundEntities = boundEntityDao.findAllForBinding(binding);

        for (BoundEntity entity : boundEntities) {
            CustomFieldValue value = binding.createNewValue();
            value.setBoundEntity(entity);
            customFieldValueDao.save(value);
            if (BindableEntity.TEST_CASE == entity.getBoundEntityType()) {
                indexationService.reindexTestCase(entity.getBoundEntityId());
            }
            if (BindableEntity.REQUIREMENT_VERSION == entity.getBoundEntityType()) {
                indexationService.reindexRequirementVersion(entity.getBoundEntityId());
            }
        }
    }

    @Override
    public void cascadeCustomFieldValuesDeletion(CustomFieldBinding binding) {
        customFieldValueDao.deleteAllForBinding(binding.getId());
    }

    @Override
    public void cascadeCustomFieldValuesDeletion(List<Long> customFieldBindingIds) {

        List<CustomFieldValue> allValues = customFieldValueDao.findAllCustomValuesOfBindings(customFieldBindingIds);
        for (CustomFieldValue value : allValues) {
            BoundEntity boundEntity = boundEntityDao.findBoundEntity(value);
            if (boundEntity != null) {
                switch (boundEntity.getBoundEntityType()) {
                case TEST_CASE:
                    indexationService.reindexTestCase(boundEntity.getBoundEntityId());
                    break;
                case REQUIREMENT_VERSION:
                    indexationService.reindexRequirementVersion(boundEntity.getBoundEntityId());
                    break;
                default:
                    break;
                }
            }
        }
        deleteCustomFieldValues(allValues);

    }

    @Override
    public void createAllCustomFieldValues(BoundEntity entity, Project project) {

        if (project == null) {
            project = entity.getProject();
        }

        List<CustomFieldBinding> bindings = optimizedFindCustomField(entity, project);

        /* **************************************************************************************************
         * [Issue 3808]
         *
         * It seems that after #2061 (revision 9540a9a08c49) a defensive block of code was added in order to
         * prevent the creation of a custom field if it exists already for the target entity.
         *
         * I don't know really why it was needed but it killed performances, so I'm rewriting it
         * and hope it makes it faster. Best should be to get rid of it completely.
         ************************************************************************************************* */
        List<CustomFieldBinding> whatIsAlreadyBound = customFieldBindingDao
                .findEffectiveBindingsForEntity(entity.getBoundEntityId(), entity.getBoundEntityType());

        bindings.removeAll(whatIsAlreadyBound);

        /* **** /[Issue 3808]  ************/

        for (CustomFieldBinding binding : bindings) {
            CustomFieldValue value = binding.createNewValue();
            value.setBoundEntity(entity);
            customFieldValueDao.save(value);
        }

        if (BindableEntity.TEST_CASE == entity.getBoundEntityType()) {
            indexationService.reindexTestCase(entity.getBoundEntityId());
        }
        if (BindableEntity.REQUIREMENT_VERSION == entity.getBoundEntityType()) {
            indexationService.reindexRequirementVersion(entity.getBoundEntityId());
        }

    }

    @SuppressWarnings("unchecked")
    @Override
    public void createAllCustomFieldValues(Collection<? extends BoundEntity> entities, Project p) {

        if (entities.isEmpty()) {
            return;
        }

        BoundEntity firstEntity = entities.iterator().next();

        Project project = p;
        if (p == null) {
            project = firstEntity.getProject();
        }

        List<CustomFieldBinding> bindings = optimizedFindCustomField(firstEntity, project);

        /* **************************************************************************************************
         * [Issue 3808]
         *
         * It seems that after #2061 (revision 9540a9a08c49) a defensive block of code was added in order to
         * prevent the creation of a custom field if it exists already for the target entity.
         *
         * I don't know really why it was needed but it killed performances, so I'm rewriting it
         * and hope it makes it faster. Best should be to get rid of it completely. Its inefficient and ugly.
         ************************************************************************************************* */

        MultiValueMap bindingPerEntities = findEffectiveBindings(entities);

        /* **** /[Issue 3808]  ************/

        // main loop
        for (BoundEntity entity : entities) {

            Collection<CustomFieldBinding> toBeBound = bindings;

            Collection<CustomFieldBinding> effectiveBindings = bindingPerEntities
                    .getCollection(entity.getBoundEntityId());

            if (effectiveBindings != null) {
                toBeBound = CollectionUtils.subtract(bindings, effectiveBindings);
            }

            for (CustomFieldBinding toBind : toBeBound) {
                CustomFieldValue value = toBind.createNewValue();
                value.setBoundEntity(entity);
                customFieldValueDao.save(value);

            }

            if (BindableEntity.TEST_CASE == entity.getBoundEntityType()) {
                indexationService.reindexTestCase(entity.getBoundEntityId());
            }
            if (BindableEntity.REQUIREMENT_VERSION == entity.getBoundEntityType()) {
                indexationService.reindexRequirementVersion(entity.getBoundEntityId());
            }
        }

    }

    @Override
    public void deleteAllCustomFieldValues(BoundEntity entity) {
        customFieldValueDao.deleteAllForEntity(entity.getBoundEntityId(), entity.getBoundEntityType());
    }

    @Override
    public void deleteAllCustomFieldValues(BindableEntity entityType, List<Long> entityIds) {
        customFieldValueDao.deleteAllForEntities(entityType, entityIds);
    }

    @Override
    public void copyCustomFieldValues(BoundEntity source, BoundEntity recipient) {

        List<CustomFieldValue> sourceValues = customFieldValueDao.findAllCustomValues(source.getBoundEntityId(),
                source.getBoundEntityType());

        for (CustomFieldValue value : sourceValues) {
            CustomFieldValue copy = value.copy();
            copy.setBoundEntity(recipient);
            customFieldValueDao.save(copy);
        }

    }

    /**
     * @see org.squashtest.tm.service.customfield.CustomFieldValueFinderService#areValuesEditable(long,
     *      org.squashtest.tm.domain.customfield.BindableEntity)
     */
    @Override
    public boolean areValuesEditable(long boundEntityId, BindableEntity bindableEntity) {
        return editableStrategy(bindableEntity).isEditable(boundEntityId, bindableEntity);
    }

    @Override
    public List<CustomFieldValue> findAllForEntityAndRenderingLocation(BoundEntity boundEntity,
            RenderingLocation renderingLocation) {
        return customFieldValueDao.findAllForEntityAndRenderingLocation(boundEntity.getBoundEntityId(),
                boundEntity.getBoundEntityType(), renderingLocation);
    }

    /**
     * @see PrivateCustomFieldValueService#copyCustomFieldValues(Map, BindableEntity)
     */
    @Override
    public void copyCustomFieldValues(Map<Long, BoundEntity> copiedEntityBySourceId,
            BindableEntity bindableEntityType) {
        Set<Long> sourceEntitiesIds = copiedEntityBySourceId.keySet();
        List<CustomFieldValue> sourceValues = customFieldValueDao.batchedFindAllCustomValuesFor(sourceEntitiesIds,
                bindableEntityType);

        for (CustomFieldValue cufSource : sourceValues) {
            BoundEntity targetCopy = copiedEntityBySourceId.get(cufSource.getBoundEntityId());
            CustomFieldValue copy = cufSource.copy();
            copy.setBoundEntity(targetCopy);
            customFieldValueDao.save(copy);
        }

    }

    @Override
    public void copyCustomFieldValuesContent(BoundEntity source, BoundEntity recipient) {

        List<CustomFieldValuesPair> pairs = customFieldValueDao.findPairedCustomFieldValues(
                source.getBoundEntityType(), source.getBoundEntityId(), recipient.getBoundEntityId());

        for (CustomFieldValuesPair pair : pairs) {
            pair.copyContent();
        }
    }

    @Override
    public void changeValue(long customFieldValueId, RawValue newValue) {

        CustomFieldValue changedValue = customFieldValueDao.findById(customFieldValueId);

        BoundEntity boundEntity = boundEntityDao.findBoundEntity(changedValue);

        Long boundEntityId = customFieldValueDao.findBoundEntityId(customFieldValueId);

        if (!permissionService.hasMoreThanRead(boundEntity)) {
            throw new AccessDeniedException("access is denied");
        }

        newValue.setValueFor(changedValue);

        if (BindableEntity.TEST_CASE == boundEntity.getBoundEntityType()) {
            indexationService.reindexTestCase(boundEntityId);
        }
        if (BindableEntity.REQUIREMENT_VERSION == boundEntity.getBoundEntityType()) {
            indexationService.reindexRequirementVersion(boundEntityId);
        }
    }

    // This method is just here to use the @CacheResult annotation
    @CacheResult(type = CachableType.CUSTOM_FIELD)
    private List<CustomFieldBinding> optimizedFindCustomField(BoundEntity entity) {
        return customFieldBindingDao.findAllForProjectAndEntity(entity.getProject().getId(),
                entity.getBoundEntityType());
    }

    // This method is just here to use the @CacheResult annotation
    @CacheResult(type = CachableType.CUSTOM_FIELD)
    private List<CustomFieldBinding> optimizedFindCustomField(BoundEntity entity, Project project) {
        return customFieldBindingDao.findAllForProjectAndEntity(project.getId(), entity.getBoundEntityType());
    }

    @Override
    // basically it's a copypasta of createAllCustomFieldValues, with some extra code in it.
    public void migrateCustomFieldValues(BoundEntity entity) {

        List<CustomFieldValue> valuesToUpdate = customFieldValueDao.findAllCustomValues(entity.getBoundEntityId(),
                entity.getBoundEntityType());
        if (entity.getProject() != null) {
            List<CustomFieldBinding> projectBindings = optimizedFindCustomField(entity);

            for (CustomFieldBinding binding : projectBindings) {

                CustomFieldValue updatedCUFValue = binding.createNewValue();

                for (CustomFieldValue formerCUFValue : valuesToUpdate) {
                    if (formerCUFValue.representsSameCustomField(updatedCUFValue)) {
                        // here we use a RawValue as a container that hides us the arity of the value (single or multi-valued)
                        RawValue rawValue = formerCUFValue.asRawValue();
                        rawValue.setValueFor(updatedCUFValue);
                        break;
                    }
                }

                updatedCUFValue.setBoundEntity(entity);
                customFieldValueDao.save(updatedCUFValue);

            }
        }

        deleteCustomFieldValues(valuesToUpdate);

    }

    @Override
    public void migrateCustomFieldValues(Collection<BoundEntity> entities) {
        for (BoundEntity entity : entities) {
            migrateCustomFieldValues(entity);
        }
    }

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

    private Map<BindableEntity, List<Long>> breakEntitiesIntoCompositeIds(
            Collection<? extends BoundEntity> boundEntities) {

        Map<BindableEntity, List<Long>> segregatedEntities = new EnumMap<>(BindableEntity.class);

        for (BoundEntity entity : boundEntities) {
            List<Long> idList = segregatedEntities.get(entity.getBoundEntityType());

            if (idList == null) {
                idList = new ArrayList<>();
                segregatedEntities.put(entity.getBoundEntityType(), idList);
            }
            idList.add(entity.getBoundEntityId());
        }
        return segregatedEntities;
    }

    // will break if the collection is empty so use it responsible
    private MultiValueMap findEffectiveBindings(Collection<? extends BoundEntity> entities) {

        Map<BindableEntity, List<Long>> compositeIds = breakEntitiesIntoCompositeIds(entities);
        Entry<BindableEntity, List<Long>> firstEntry = compositeIds.entrySet().iterator().next();

        List<Long> entityIds = firstEntry.getValue();
        BindableEntity type = firstEntry.getKey();

        List<Object[]> whatIsAlreadyBound = customFieldBindingDao.findEffectiveBindingsForEntities(entityIds, type);

        MultiValueMap bindingsPerEntity = new MultiValueMap();
        for (Object[] tuple : whatIsAlreadyBound) {
            Long entityId = (Long) tuple[0];
            CustomFieldBinding binding = (CustomFieldBinding) tuple[1];
            bindingsPerEntity.put(entityId, binding);
        }

        return bindingsPerEntity;
    }

    /**
     * @param bindableEntity
     * @return
     */
    private ValueEditionStatusStrategy editableStrategy(BindableEntity bindableEntity) {
        switch (bindableEntity) {
        case REQUIREMENT_VERSION:
            return requirementBoundEditionStatusStrategy;
        default:
            return defaultEditionStatusStrategy;
        }
    }

    private void deleteCustomFieldValues(List<CustomFieldValue> values) {
        List<Long> valueIds = IdentifiedUtil.extractIds(values);
        customFieldValueDao.deleteAll(valueIds);

    }

}