org.opentestsystem.authoring.testauth.service.impl.BlueprintElementServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.authoring.testauth.service.impl.BlueprintElementServiceImpl.java

Source

/*******************************************************************************
 * Educational Online Test Delivery System
 * Copyright (c) 2013 American Institutes for Research
 * 
 * Distributed under the AIR Open Source License, Version 1.0
 * See accompanying file AIR-License-1_0.txt or at
 * http://www.smarterapp.org/documents/American_Institutes_for_Research_Open_Source_Software_License.pdf
 ******************************************************************************/
package org.opentestsystem.authoring.testauth.service.impl;

import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_EQUIVALENCE;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_FILTER_REMOVE;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_GRADE_FUNCTION;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_REMOVE_TRANSFORMER;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_UNIQUE_KEY_FUNCTION;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_VALUE_DIFF_TRANS;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BP_ELEMENT_ID_TRANSFORMER;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.CORE_STANDARDS_INVALID_BLUEPRINT_FILTER;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.CORE_STANDARD_BLUEPRINT_ELEMENT_TRANSFORMER;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.opentestsystem.authoring.testauth.domain.Assessment;
import org.opentestsystem.authoring.testauth.domain.BlueprintElement;
import org.opentestsystem.authoring.testauth.domain.CoreStandardPublicationPayloadElement;
import org.opentestsystem.authoring.testauth.domain.CoreStandardPublicationResponse;
import org.opentestsystem.authoring.testauth.domain.Segment;
import org.opentestsystem.authoring.testauth.domain.ValidationResult;
import org.opentestsystem.authoring.testauth.domain.search.BlueprintElementSearchRequest;
import org.opentestsystem.authoring.testauth.persistence.BlueprintElementRepository;
import org.opentestsystem.authoring.testauth.service.AssessmentService;
import org.opentestsystem.authoring.testauth.service.BlueprintElementService;
import org.opentestsystem.authoring.testauth.service.CoreStandardsService;
import org.opentestsystem.authoring.testauth.service.PerformanceLevelService;
import org.opentestsystem.authoring.testauth.service.ScoringRuleService;
import org.opentestsystem.authoring.testauth.service.SegmentService;
import org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_ADD_DEFAULT_SEGMENT_VALUE_TRANSFORMER;
import org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_CLEAR_SEGMENT_VALUE_TRANSFORMER;
import org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_FILTER_BY_CHOSEN_GRADES;
import org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_INJECT_DEFAULT_VALUES_TRANSFORMER;
import org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_REMOVE_SEGMENT_VALUE_TRANSFORMER;
import org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_UPDATE_ISA_PARAMETERS_TRANSFORMER;
import org.opentestsystem.authoring.testauth.validation.BlueprintElementValidator;
import org.opentestsystem.authoring.testauth.validation.ValidationHelper;
import org.opentestsystem.shared.exception.LocalizedException;
import org.opentestsystem.shared.exception.RestException;
import org.opentestsystem.shared.search.domain.SearchResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;

import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.MapDifference.ValueDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

@Service
public class BlueprintElementServiceImpl extends AssessmentChildHelper implements BlueprintElementService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BlueprintElementServiceImpl.class);

    @Autowired
    private transient BlueprintElementRepository blueprintElementRepository;

    @Autowired
    private AssessmentService assessmentService;

    @Autowired
    private SegmentService segmentService;

    @Autowired
    private CoreStandardsService coreStandardsService;

    @Autowired
    private BlueprintElementValidator blueprintElementValidator;

    @Autowired
    private ScoringRuleService scoringRuleService;

    @Autowired
    private PerformanceLevelService performanceLevelService;

    @Override
    public BlueprintElement getBlueprintElement(final String blueprintElementId) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Finding blueprintElement for Id: " + blueprintElementId);
        }
        return this.blueprintElementRepository.findOne(blueprintElementId);
    }

    @Override
    public List<BlueprintElement> getBlueprintElementsByAssessmentId(final String assessmentId) {
        return this.blueprintElementRepository.findAllByAssessmentId(assessmentId);
    }

    @Override
    public SearchResponse<BlueprintElement> searchBlueprintElements(final Map<String, String[]> parameterMap) {
        final BlueprintElementSearchRequest searchRequest = new BlueprintElementSearchRequest(parameterMap);
        if (searchRequest.isValid()) {
            return this.blueprintElementRepository.search(searchRequest);
        }
        throw new RestException("blueprintElement.search.invalidsearchcriteria");
    }

    @Override
    public BlueprintElement saveBlueprintElement(final String blueprintElementId,
            final BlueprintElement blueprintElement) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Saving blueprintElement");
        }
        if (blueprintElementId != null && (blueprintElement == null || StringUtils.isEmpty(blueprintElement.getId())
                || !blueprintElementId.equals(blueprintElement.getId()))) {
            throw new LocalizedException("blueprintElement.invalid.id");
        }
        checkForLockedAssessment(blueprintElement.getAssessmentId());
        BlueprintElement savedBlueprintElement = null;
        try {
            validateBlueprintElement(blueprintElement);
            savedBlueprintElement = this.blueprintElementRepository.save(blueprintElement);
        } catch (final DuplicateKeyException dke) {
            throw new LocalizedException("blueprintElement.already.exists",
                    new String[] { blueprintElement.getId() }, dke);
        }
        return savedBlueprintElement;
    }

    @Override
    public void removeByAssessmentId(final String assessmentId) {
        checkForLockedAssessment(assessmentId);
        this.blueprintElementRepository.delete(this.blueprintElementRepository.findAllByAssessmentId(assessmentId));
    }

    @Override
    public List<BlueprintElement> saveBlueprintElementList(final List<BlueprintElement> blueprintElements) {
        final List<BlueprintElement> savedBlueprintElements = Lists.newArrayList();
        if (!CollectionUtils.isEmpty(blueprintElements)) {
            final String assessmentId = blueprintElements.get(0).getAssessmentId();
            checkForLockedAssessment(assessmentId);
            final List<BlueprintElement> blueprintElementsToRemove = Lists
                    .newArrayList(Iterables.filter(blueprintElements, BLUEPRINT_ELEMENT_FILTER_REMOVE));
            final List<BlueprintElement> blueprintElementsToSave = Lists.newArrayList(
                    Iterables.filter(blueprintElements, Predicates.not(BLUEPRINT_ELEMENT_FILTER_REMOVE)));
            try {
                if (!CollectionUtils.isEmpty(blueprintElementsToRemove)) {
                    this.blueprintElementRepository.delete(blueprintElementsToRemove);
                    final List<String> bpReferenceIdsToDelete = Lists.transform(blueprintElementsToRemove,
                            BP_ELEMENT_ID_TRANSFORMER);
                    cleanupForRelatedData(assessmentId, bpReferenceIdsToDelete);
                }
                if (!CollectionUtils.isEmpty(blueprintElementsToSave)) {
                    final List<String> bpReferenceIdsToDelete = Lists
                            .transform(
                                    Lists.newArrayList(Iterables.filter(blueprintElementsToSave,
                                            BlueprintHelper.BP_ELEMENT_ID_INACTIVE_FILTER)),
                                    BP_ELEMENT_ID_TRANSFORMER);
                    savedBlueprintElements.addAll(this.blueprintElementRepository.save(blueprintElementsToSave));
                    cleanupForRelatedData(assessmentId, bpReferenceIdsToDelete);
                }
            } catch (final DuplicateKeyException dke) {
                throw new LocalizedException("blueprintElement.listhasduplicates", dke);
            }
        }

        return savedBlueprintElements;
    }

    @Override
    public int synchronizeWithCoreStandards(final String assessmentId) {

        checkForLockedAssessment(assessmentId);
        final Assessment assessment = this.assessmentService.getAssessment(assessmentId);

        String client = assessment.getClient();
        List<BlueprintElement> blueprintElementListToSave = Lists.newArrayList();

        final List<BlueprintElement> blueprintElementList = this.blueprintElementRepository
                .findAllByAssessmentId(assessmentId);

        final List<Segment> segmentList = this.segmentService.findSegmentListByAssessmentId(assessmentId);
        this.segmentService.loadReferenceData(segmentList);
        final Map<String, Segment> segmentMap = SegmentHelper.createSegmentIdKeyMap(segmentList);
        final CoreStandardPublicationResponse csResponse = this.coreStandardsService
                .getCoreStandardsForPublication(assessment.getPublication().getCoreStandardsPublicationKey());
        final List<CoreStandardPublicationPayloadElement> coreStandardsList = csResponse != null
                ? csResponse.getPayload()
                : null;

        // coreStandards are empty, wipe out blueprint elements (should never happen)
        if (CollectionUtils.isEmpty(coreStandardsList)) {
            blueprintElementListToSave = Lists.transform(blueprintElementList,
                    BLUEPRINT_ELEMENT_REMOVE_TRANSFORMER);
        } else {
            final List<BlueprintElement> coreStandardsElementList = Lists.newArrayList(Iterables.filter(
                    Iterables.transform(coreStandardsList,
                            CORE_STANDARD_BLUEPRINT_ELEMENT_TRANSFORMER.getInstance(client)),
                    CORE_STANDARDS_INVALID_BLUEPRINT_FILTER));

            // first time around, default active booleans to correspond to the grades in the assessment
            if (CollectionUtils.isEmpty(blueprintElementList)) {
                blueprintElementListToSave = Lists.transform(coreStandardsElementList,
                        BLUEPRINT_ELEMENT_INJECT_DEFAULT_VALUES_TRANSFORMER.getInstance(assessmentId,
                                assessment.getGrade(), segmentMap));
            } else {
                // subsequent synch
                blueprintElementListToSave = buildListOfDifferences(assessmentId, coreStandardsElementList,
                        blueprintElementList, segmentMap, assessment.getGrade());
            }
        }
        saveBlueprintElementList(blueprintElementListToSave);

        assessment.setBlueprintSynced(true);
        this.assessmentService.updateAssessment(assessment);

        return Lists.newArrayList(Iterables.filter(blueprintElementListToSave,
                BLUEPRINT_ELEMENT_FILTER_BY_CHOSEN_GRADES.getInstance(assessment.getGrade()))).size();
    }

    @Override
    public Map<String, Integer> getElementTotalCounts(final String assessmentId) {
        final Map<String, Integer> elementCountsMap = Maps.newHashMap();
        elementCountsMap.put("opMin", 0);
        elementCountsMap.put("opMax", 0);
        elementCountsMap.put("ftMin", 0);
        elementCountsMap.put("ftMax", 0);

        final Map<String, String[]> searchParams = Maps.newHashMap();
        searchParams.put("assessmentId", new String[] { assessmentId });
        searchParams.put("level", new String[] { "1" });
        searchParams.put("pageSize", new String[] { "1000" });
        searchParams.put("active", new String[] { "true" });
        final List<BlueprintElement> blueprintElements = searchBlueprintElements(searchParams).getSearchResults();

        for (final BlueprintElement blueprintElement : blueprintElements) {
            elementCountsMap.put("opMin",
                    elementCountsMap.get("opMin") + blueprintElement.getMasterValue().getOperationalItemMinValue());
            elementCountsMap.put("opMax",
                    elementCountsMap.get("opMax") + blueprintElement.getMasterValue().getOperationalItemMaxValue());
            elementCountsMap.put("ftMin",
                    elementCountsMap.get("ftMin") + blueprintElement.getMasterValue().getFieldTestItemMinValue());
            elementCountsMap.put("ftMax",
                    elementCountsMap.get("ftMax") + blueprintElement.getMasterValue().getFieldTestItemMaxValue());
        }
        return elementCountsMap;
    }

    private List<BlueprintElement> buildListOfDifferences(final String blueprintId,
            final List<BlueprintElement> coreStandardsElementList,
            final List<BlueprintElement> blueprintElementList, final Map<String, Segment> segmentMap,
            final String[] assessmentGrades) {
        List<BlueprintElement> blueprintElementListToSave = Lists.newArrayList();
        final Map<String, BlueprintElement> blueprintElementMap = Maps.uniqueIndex(blueprintElementList,
                BLUEPRINT_ELEMENT_UNIQUE_KEY_FUNCTION);
        final Map<String, BlueprintElement> coreStandardsElementMap = Maps.uniqueIndex(coreStandardsElementList,
                BLUEPRINT_ELEMENT_UNIQUE_KEY_FUNCTION);

        final Map<String, ValueDifference<BlueprintElement>> entriesDiffering = Maps
                .difference(blueprintElementMap, coreStandardsElementMap, BLUEPRINT_ELEMENT_EQUIVALENCE)
                .entriesDiffering();
        final Map<String, BlueprintElement> entriesOnlyOnLeft = Maps
                .difference(blueprintElementMap, coreStandardsElementMap).entriesOnlyOnLeft();
        final Map<String, BlueprintElement> entriesOnlyOnRight = Maps
                .difference(blueprintElementMap, coreStandardsElementMap).entriesOnlyOnRight();

        blueprintElementListToSave = Lists
                .newArrayList(Iterables.transform(entriesDiffering.values(), BLUEPRINT_ELEMENT_VALUE_DIFF_TRANS));

        // subsequent time around, default active booleans for the additions from core standards only to correspond to the grades already in use
        Iterables.addAll(blueprintElementListToSave,
                Iterables.transform(entriesOnlyOnRight.values(),
                        BLUEPRINT_ELEMENT_INJECT_DEFAULT_VALUES_TRANSFORMER.getInstance(blueprintId,
                                convertExistingBpElementGradesIntoDistinctGradeArray(blueprintElementList,
                                        assessmentGrades),
                                segmentMap)));

        Iterables.addAll(blueprintElementListToSave,
                Iterables.transform(entriesOnlyOnLeft.values(), BLUEPRINT_ELEMENT_REMOVE_TRANSFORMER));
        return blueprintElementListToSave;
    }

    private String[] convertExistingBpElementGradesIntoDistinctGradeArray(
            final List<BlueprintElement> blueprintElementList, final String[] assessmentGrades) {
        final List<String> existingGrades = Lists.transform(blueprintElementList, BLUEPRINT_ELEMENT_GRADE_FUNCTION);
        final Set<String> uniqueGrades = Sets.newHashSet(existingGrades);
        // ensure that at the very least the assessment grades are included as active
        uniqueGrades.addAll(Arrays.asList(assessmentGrades));
        return Iterables.toArray(uniqueGrades, String.class);
    }

    @Override
    public List<BlueprintElement> addBlueprintElementValueGroup(final String assessmentId, final String segmentId,
            final String segmentIdToClone) {
        checkForLockedAssessment(assessmentId);
        final Segment segment = this.segmentService.getSegment(segmentId);

        final List<BlueprintElement> blueprintElementList = this.blueprintElementRepository
                .findAllByAssessmentId(assessmentId);
        final List<BlueprintElement> updatedBlueprintElementList = Lists.transform(blueprintElementList,
                BLUEPRINT_ELEMENT_ADD_DEFAULT_SEGMENT_VALUE_TRANSFORMER.getInstance(segmentId, segmentIdToClone,
                        segment == null ? null : segment.getItemSelectionAlgorithm()));
        return this.blueprintElementRepository.save(updatedBlueprintElementList);
    }

    @Override
    public List<BlueprintElement> removeBlueprintElementValueGroup(final String assessmentId,
            final String segmentId) {
        checkForLockedAssessment(assessmentId);
        final List<BlueprintElement> blueprintElementList = this.blueprintElementRepository
                .findAllByAssessmentId(assessmentId);
        if (!CollectionUtils.isEmpty(blueprintElementList)) {
            final List<BlueprintElement> updatedBlueprintElementList = Lists.transform(blueprintElementList,
                    BLUEPRINT_ELEMENT_REMOVE_SEGMENT_VALUE_TRANSFORMER.getInstance(segmentId));
            return this.blueprintElementRepository.save(updatedBlueprintElementList);
        }
        return null;
    }

    @Override
    public List<BlueprintElement> updateBlueprintElementItemSelectionParameters(final String assessmentId,
            final String segmentId) {
        checkForLockedAssessment(assessmentId);
        final Segment segment = this.segmentService.getSegment(segmentId);

        final List<BlueprintElement> blueprintElementList = this.blueprintElementRepository
                .findAllByAssessmentId(assessmentId);
        final List<BlueprintElement> updatedBlueprintElementList = Lists.transform(blueprintElementList,
                BLUEPRINT_ELEMENT_UPDATE_ISA_PARAMETERS_TRANSFORMER.getInstance(segmentId,
                        segment == null ? null : segment.getItemSelectionAlgorithm()));
        return this.blueprintElementRepository.save(updatedBlueprintElementList);
    }

    @Override
    public int activateBlueprintElementGrades(final String assessmentId, final String[] grades,
            final boolean activeFlag) {
        return activateBlueprintElement(assessmentId, null, activeFlag, false, grades);
    }

    @Override
    public int activateBlueprintElementStandard(final String assessmentId, final String grade,
            final String standardKey, final boolean activeFlag) {
        return activateBlueprintElement(assessmentId, standardKey, activeFlag, true, new String[] { grade });
    }

    private int activateBlueprintElement(final String assessmentId, final String standardKey,
            final boolean activeFlag, final boolean byStandard, final String... grades) {
        checkForLockedAssessment(assessmentId);
        final List<String> alteredBpElementIds = Lists.newArrayList();

        final Assessment assessment = this.assessmentService.getAssessment(assessmentId);

        String client = assessment.getClient();
        String blueprintCompatibleStandardKey = standardKey;
        if (byStandard) {
            blueprintCompatibleStandardKey = convertIntoBlueprintCompatible(client, standardKey);
        }
        final List<BlueprintElement> blueprintElements = byStandard
                ? this.blueprintElementRepository.findAllChildrenByAssessmentIdAndGradeAndParentKey(assessmentId,
                        grades[0], blueprintCompatibleStandardKey)
                : this.blueprintElementRepository.findAllByAssessmentIdAndGradeIn(assessmentId, grades);
        for (final BlueprintElement bpElement : blueprintElements) {
            if (bpElement.isActive() && !activeFlag) {
                alteredBpElementIds.add(bpElement.getId());
            }
            bpElement.setActive(activeFlag);
        }

        return saveBlueprintElementList(blueprintElements).size();
    }

    private String convertIntoBlueprintCompatible(String client, String standardKey) {
        //EF: we have CoreStandards as "SBAC-ELA-v1:2-W" and need to transform to "SBAC-2-W" to match
        // what we store in BlueprintElement collection
        int pubEndPos = StringUtils.indexOf(standardKey, ":");
        if (pubEndPos > 0)
            return (client.concat("-").concat(StringUtils.substring(standardKey, pubEndPos + 1)));
        else
            return (client.concat("-").concat(standardKey));
    }

    @Override
    public List<ValidationResult<BlueprintElement>> validateBlueprint(final String assessmentId) {
        final List<BlueprintElement> activeBlueprintElementList = this.blueprintElementRepository
                .findAllByAssessmentIdAndActive(assessmentId, true);
        final List<ValidationResult<BlueprintElement>> blueprintValidationResultList = Lists.newArrayList();

        blueprintValidationResultList.addAll(
                BlueprintValidationHelper.validateBlueprintElementValueItemRanges(activeBlueprintElementList));
        blueprintValidationResultList.addAll(
                BlueprintValidationHelper.validateBlueprintHierarchicalRelationships(activeBlueprintElementList));

        final List<Segment> segments = this.segmentService.findSegmentListByAssessmentId(assessmentId);
        blueprintValidationResultList.addAll(BlueprintValidationHelper
                .validateTopLevelBlueprintVsSegmentRelationships(segments, activeBlueprintElementList));

        final List<BlueprintElement> blueprintElementList = this.blueprintElementRepository
                .findAllByAssessmentId(assessmentId);
        blueprintValidationResultList.addAll(
                BlueprintValidationHelper.validateInactiveBlueprintHierarchicalRelationships(blueprintElementList));

        return blueprintValidationResultList;
    }

    @Override
    public List<BlueprintElement> clearBlueprint(final String assessmentId, final String segmentId) {
        checkForLockedAssessment(assessmentId);
        final List<BlueprintElement> blueprintElementList = this.blueprintElementRepository
                .findAllByAssessmentId(assessmentId);
        final List<BlueprintElement> updatedBlueprintElementList = Lists.transform(blueprintElementList,
                BLUEPRINT_ELEMENT_CLEAR_SEGMENT_VALUE_TRANSFORMER.getInstance(segmentId));
        return this.blueprintElementRepository.save(updatedBlueprintElementList);
    }

    private void validateBlueprintElement(final BlueprintElement blueprintElement) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(blueprintElement, "blueprintElement");
        this.blueprintElementValidator.validate(blueprintElement, bindingResult);
        if (bindingResult.hasErrors()) {
            throw ValidationHelper.convertErrorsToConstraintException(blueprintElement, bindingResult);
        }
    }

    private void cleanupForRelatedData(final String assessmentId, final List<String> bpReferenceIdsToDelete) {
        this.scoringRuleService.removeByBlueprintReferenceId(assessmentId,
                Iterables.toArray(bpReferenceIdsToDelete, String.class));
        for (final String bpReferenceIdToDelete : bpReferenceIdsToDelete) {
            this.performanceLevelService.removeByReferenceId(bpReferenceIdToDelete);
        }
        if (this.blueprintElementRepository.findAllByAssessmentIdAndActive(assessmentId, true).size() < 1) {
            // if there's nothing active left, wipe out any straggler leaf node/level scoring rules
            this.scoringRuleService.removeByBlueprintReferenceId(assessmentId, new String[] {});
        }
    }
}