org.opentestsystem.authoring.testauth.validation.PublishingRecordValidationHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.authoring.testauth.validation.PublishingRecordValidationHelper.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.validation;

import static org.opentestsystem.authoring.testauth.config.TestAuthUtil.paramArray;
import static org.opentestsystem.authoring.testauth.publish.PublisherUtil.FIXEDFORM_SEGMENT_FILTER;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.opentestsystem.authoring.testauth.domain.AffinityGroup;
import org.opentestsystem.authoring.testauth.domain.Approval;
import org.opentestsystem.authoring.testauth.domain.Assessment;
import org.opentestsystem.authoring.testauth.domain.BlueprintElement;
import org.opentestsystem.authoring.testauth.domain.Form;
import org.opentestsystem.authoring.testauth.domain.FormPartition;
import org.opentestsystem.authoring.testauth.domain.Item;
import org.opentestsystem.authoring.testauth.domain.PublishingRecord;
import org.opentestsystem.authoring.testauth.domain.Segment;
import org.opentestsystem.authoring.testauth.domain.ValidationResult;
import org.opentestsystem.authoring.testauth.persistence.AssessmentRepository;
import org.opentestsystem.authoring.testauth.persistence.BlueprintElementRepository;
import org.opentestsystem.authoring.testauth.publish.domain.Purpose;
import org.opentestsystem.authoring.testauth.service.AffinityGroupService;
import org.opentestsystem.authoring.testauth.service.BlueprintElementService;
import org.opentestsystem.authoring.testauth.service.FormPartitionService;
import org.opentestsystem.authoring.testauth.service.FormService;
import org.opentestsystem.authoring.testauth.service.ItemService;
import org.opentestsystem.authoring.testauth.service.PerformanceLevelService;
import org.opentestsystem.authoring.testauth.service.ReportingMeasureService;
import org.opentestsystem.authoring.testauth.service.ScoringRuleService;
import org.opentestsystem.authoring.testauth.service.SegmentService;
import org.opentestsystem.shared.progman.client.ProgManClient;
import org.opentestsystem.shared.progman.client.domain.Tenant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.client.RestClientException;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;

@Component
public final class PublishingRecordValidationHelper {

    private static final String OBJECT_NAME = "publishingRecord";
    private static final String VALIDATION_ERROR_MSG_KEY = "publishingRecord.validation.error";

    private static final Ordering<Purpose> PURPOSE_ORDER = Ordering.natural()
            .onResultOf(new Function<Purpose, Integer>() {
                @Override
                public Integer apply(final Purpose input) {
                    return input.getOrder();
                }
            });

    @SuppressWarnings("rawtypes")
    private static final class VALIDATION_RESULT_OERROR_XFORMER implements Function<ValidationResult, ObjectError> {
        private final String validatedObjectName;

        public static VALIDATION_RESULT_OERROR_XFORMER getInstance(final String validatedObjectName) {
            return new VALIDATION_RESULT_OERROR_XFORMER(validatedObjectName);
        }

        private VALIDATION_RESULT_OERROR_XFORMER(final String validatedObjectName) {
            this.validatedObjectName = validatedObjectName;
        }

        @Override
        public ObjectError apply(final ValidationResult validationResult) {
            return new ObjectError(OBJECT_NAME, paramArray(VALIDATION_ERROR_MSG_KEY),
                    paramArray(this.validatedObjectName, validationResult.getMessage()), VALIDATION_ERROR_MSG_KEY);
        }
    };

    @SuppressWarnings("rawtypes")
    private static Predicate<ValidationResult> VALIDATION_RESULT_ERROR_LEVEL_FILTER = new Predicate<ValidationResult>() {
        @Override
        public boolean apply(final ValidationResult validationResult) {
            return ValidationResult.ERROR_LEVEL.equals(validationResult.getValidationLevel());
        }
    };

    @Autowired
    private transient AssessmentRepository assessmentRepository;

    @Autowired
    private transient SegmentService segmentService;

    @Autowired
    private ProgManClient progManClient;

    @Autowired
    private BlueprintElementRepository blueprintElementRepository;

    @Autowired
    private BlueprintElementService blueprintElementService;

    @Autowired
    private ItemService itemService;

    @Autowired
    private AffinityGroupService affinityGroupService;

    @Autowired
    private ScoringRuleService scoringRuleService;

    @Autowired
    private transient FormService formService;

    @Autowired
    private transient FormPartitionService formPartitionService;

    @Autowired
    private transient PerformanceLevelService performanceLevelService;

    @Autowired
    private transient ReportingMeasureService reportingMeasureService;

    @Autowired
    private MessageSource messageSource;

    // REGISTRATION = registrationform*, poolproperty*, registrationsegment, comment?
    // ADMINISTRATION = testblueprint, poolproperty*, itempool, testform*, adminsegment+, comment?
    // SCORING = testblueprint, itempool, testform*, performancelevels, scoringrules
    // REPORTING = testblueprint, performancelevels, reportingmeasures, itempool?, testform*
    // COMPLETE = testblueprint, poolproperty*, itempool, testform*, adminsegment+, performancelevels, scoringrules, reportingmeasures, comment?
    // SIMULATION = eligible for COMPLETE, REGISTRATION, or ADMINISTRATION
    @SuppressWarnings("unchecked")
    public void verifyCanPublish(final PublishingRecord publishingRecord, final boolean simulationCall) {
        final String assessmentId = publishingRecord.getAssessmentId();
        final Assessment assessment = this.assessmentRepository.findOne(assessmentId);
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);

        Tenant tenant = null;
        try {
            tenant = this.progManClient.getTenantById(assessment.getTenantId());
        } catch (final RestClientException e) {
            bindingResult
                    .addError(new ObjectError(OBJECT_NAME, "publishingRecord.testspec.tenant.retrieval.failed"));
        }
        if (tenant == null) {
            bindingResult
                    .addError(new ObjectError(OBJECT_NAME, "publishingRecord.testspec.tenant.retrieval.failed"));
        }

        // if the purpose list is empty, to get the error message to the user run through w/ REGISTRATION
        List<Purpose> purposeList = Lists
                .newArrayList(CollectionUtils.isEmpty(Arrays.asList(publishingRecord.getPurpose()))
                        ? new Purpose[] { Purpose.REGISTRATION }
                        : publishingRecord.getPurpose());

        // for non-simulation, if run in the correct order, can avoid redundant checks as prior basic 'presence' eligibility will have already been checked
        // (e.g. can't have ADMINISTRATION w/o REGISTRATION already being satisfied, so no need to re-check form/segment data)
        // however, sometimes the purpose list will have things that the assessment USED to eligible for, but is no longer (i.e. all segments deleted)
        purposeList = PURPOSE_ORDER.sortedCopy(purposeList);
        final List<Purpose> outboundPurposeList = PURPOSE_ORDER.sortedCopy(purposeList);

        // every type/purpose needs these checks...however they are hobbled for registration (don't care if no items yet for registration)
        List<ObjectError> segmentErrors = Lists.newArrayList(), formErrors = Lists.newArrayList(),
                itemErrors = Lists.newArrayList(), affinityGroupErrors = Lists.newArrayList(),
                blueprintErrors = Lists.newArrayList(), scoringRuleErrors = Lists.newArrayList(),
                performanceLevelErrors = Lists.newArrayList(), reportingMeasureErrors = Lists.newArrayList();

        final Map<Purpose, List<ObjectError>> masterErrorMap = Maps.newHashMap();
        final List<Segment> segmentList = retrieveSegments(assessmentId);
        for (final Purpose purpose : purposeList) {
            switch (purpose) {
            case REGISTRATION:
                segmentErrors = checkSegmentInformation(publishingRecord, assessmentId, segmentList);
                formErrors = checkFormInformation(publishingRecord, assessmentId, segmentList);
                if (!segmentErrors.isEmpty() || !formErrors.isEmpty()) {
                    masterErrorMap.put(Purpose.REGISTRATION,
                            Lists.newArrayList(Iterables.concat(segmentErrors, formErrors)));
                }
                break;

            case ADMINISTRATION:
                if (simulationCall) {
                    segmentErrors = checkSegmentInformation(publishingRecord, assessmentId, segmentList);
                    formErrors = checkFormInformation(publishingRecord, assessmentId, segmentList);
                }
                itemErrors = checkItemInformation(publishingRecord, assessmentId, segmentList);
                affinityGroupErrors = checkAffinityGroupInformation(publishingRecord, assessmentId);
                blueprintErrors = checkBlueprintInformation(publishingRecord, assessment);
                if (!segmentErrors.isEmpty() || !formErrors.isEmpty() || !itemErrors.isEmpty()
                        || !affinityGroupErrors.isEmpty() || !blueprintErrors.isEmpty()) {
                    masterErrorMap.put(Purpose.ADMINISTRATION,
                            Lists.newArrayList(Iterables.concat(itemErrors, affinityGroupErrors, blueprintErrors)));
                }
                break;

            case SCORING:
                if (simulationCall) {
                    segmentErrors = checkSegmentInformation(publishingRecord, assessmentId, segmentList);
                    formErrors = checkFormInformation(publishingRecord, assessmentId, segmentList);
                    itemErrors = checkItemInformation(publishingRecord, assessmentId, segmentList);
                    affinityGroupErrors = checkAffinityGroupInformation(publishingRecord, assessmentId);
                    blueprintErrors = checkBlueprintInformation(publishingRecord, assessment);
                }
                performanceLevelErrors = checkPerformanceLevelInformation(publishingRecord, assessmentId);
                scoringRuleErrors = checkScoringRuleInformation(publishingRecord, assessmentId);
                if (!segmentErrors.isEmpty() || !formErrors.isEmpty() || !itemErrors.isEmpty()
                        || !affinityGroupErrors.isEmpty() || !blueprintErrors.isEmpty()
                        || !performanceLevelErrors.isEmpty() || !scoringRuleErrors.isEmpty()) {
                    masterErrorMap.put(Purpose.SCORING,
                            Lists.newArrayList(Iterables.concat(performanceLevelErrors, scoringRuleErrors)));
                }
                break;

            case REPORTING:
                if (simulationCall) {
                    segmentErrors = checkSegmentInformation(publishingRecord, assessmentId, segmentList);
                    formErrors = checkFormInformation(publishingRecord, assessmentId, segmentList);
                    itemErrors = checkItemInformation(publishingRecord, assessmentId, segmentList);
                    affinityGroupErrors = checkAffinityGroupInformation(publishingRecord, assessmentId);
                    blueprintErrors = checkBlueprintInformation(publishingRecord, assessment);
                }
                reportingMeasureErrors = checkReportingMeasureInformation(publishingRecord, assessmentId);
                if (!segmentErrors.isEmpty() || !formErrors.isEmpty() || !itemErrors.isEmpty()
                        || !affinityGroupErrors.isEmpty() || !blueprintErrors.isEmpty()
                        || !performanceLevelErrors.isEmpty() || !scoringRuleErrors.isEmpty()
                        || !reportingMeasureErrors.isEmpty()) {
                    masterErrorMap.put(Purpose.REPORTING, reportingMeasureErrors);
                }
                break;

            case COMPLETE:
                if (simulationCall) {
                    segmentErrors = checkSegmentInformation(publishingRecord, assessmentId, segmentList);
                    formErrors = checkFormInformation(publishingRecord, assessmentId, segmentList);
                    itemErrors = checkItemInformation(publishingRecord, assessmentId, segmentList);
                    affinityGroupErrors = checkAffinityGroupInformation(publishingRecord, assessmentId);
                    blueprintErrors = checkBlueprintInformation(publishingRecord, assessment);
                    performanceLevelErrors = checkPerformanceLevelInformation(publishingRecord, assessmentId);
                    scoringRuleErrors = checkScoringRuleInformation(publishingRecord, assessmentId);
                    reportingMeasureErrors = checkReportingMeasureInformation(publishingRecord, assessmentId);
                }
                if (!segmentErrors.isEmpty() || !formErrors.isEmpty() || !itemErrors.isEmpty()
                        || !affinityGroupErrors.isEmpty() || !blueprintErrors.isEmpty()
                        || !performanceLevelErrors.isEmpty() || !scoringRuleErrors.isEmpty()
                        || !reportingMeasureErrors.isEmpty()) {
                    masterErrorMap.put(Purpose.COMPLETE, new ArrayList<ObjectError>());
                }
                break;
            default:
                break;
            }
        }
        // remove any key in the map from what will get displayed onscreen
        // adding to the map above is successive, so if things in registration fail, that causes administration, scoring, etc. to also not be available
        outboundPurposeList.removeAll(masterErrorMap.keySet());
        publishingRecord.setPurpose(Iterables.toArray(outboundPurposeList, Purpose.class));

        final List<ObjectError> masterErrorsList = Lists
                .newArrayList(Iterables.concat(Iterables.toArray(masterErrorMap.values(), List.class)));
        final List<ValidationResult<Approval>> validationResultList = Lists.newArrayList();
        for (final ObjectError objectError : masterErrorsList) {
            validationResultList.add(new ValidationResult<Approval>(null, this.messageSource
                    .getMessage(objectError.getDefaultMessage(), objectError.getArguments(), Locale.US)));
        }

        publishingRecord.setValidationResultList(validationResultList);
    }

    private List<ObjectError> checkSegmentInformation(final PublishingRecord publishingRecord,
            final String assessmentId, final List<Segment> segmentList) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);
        if (CollectionUtils.isEmpty(segmentList)) {
            bindingResult
                    .addError(new ObjectError(OBJECT_NAME, "publishingRecord.segment.empty.cannot.be.published"));
        }
        return bindingResult.getAllErrors();
    }

    private List<Segment> retrieveSegments(final String assessmentId) {
        final List<Segment> segmentList = this.segmentService.findSegmentListByAssessmentId(assessmentId);
        if (!CollectionUtils.isEmpty(segmentList)) {
            this.segmentService.loadReferenceData(segmentList);
        }
        return segmentList;
    }

    private List<ObjectError> checkFormInformation(final PublishingRecord publishingRecord,
            final String assessmentId, final List<Segment> segmentList) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);

        final List<Segment> fixedFormSegmentList = Lists
                .newArrayList(Iterables.filter(segmentList, FIXEDFORM_SEGMENT_FILTER));

        if (fixedFormSegmentList.size() > 0) {
            final List<Form> formList = this.formService.getFormsByAssessmentId(assessmentId);
            if (CollectionUtils.isEmpty(formList)) {
                bindingResult
                        .addError(new ObjectError(OBJECT_NAME, "publishingRecord.fixedformsegment.form.required"));
            }
            for (final Form form : formList) {
                final List<FormPartition> formPartitionList = this.formPartitionService
                        .getFormPartitionsByFormId(form.getId());
                if (CollectionUtils.isEmpty(formPartitionList)
                        || formPartitionList.size() != fixedFormSegmentList.size()) {
                    bindingResult.addError(
                            new ObjectError(OBJECT_NAME, "publishingRecord.segment.formPartition.unequal"));
                }
            }
        }
        return bindingResult.getAllErrors();
    }

    private List<ObjectError> checkBlueprintInformation(final PublishingRecord publishingRecord,
            final Assessment assessment) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);
        if (this.blueprintElementRepository
                .findAllByAssessmentIdAndActiveAndGradeIn(assessment.getId(), true, assessment.getGrade())
                .size() < 1) {
            bindingResult
                    .addError(new ObjectError(OBJECT_NAME, "publishingRecord.blueprint.empty.cannot.be.published"));
        } else {
            final List<ValidationResult<BlueprintElement>> validationResultList = this.blueprintElementService
                    .validateBlueprint(assessment.getId());
            if (!CollectionUtils.isEmpty(validationResultList)) {
                for (final ObjectError error : Iterables.transform(
                        Iterables.filter(validationResultList, VALIDATION_RESULT_ERROR_LEVEL_FILTER),
                        VALIDATION_RESULT_OERROR_XFORMER.getInstance("Blueprint"))) {
                    bindingResult.addError(error);
                }
            }
        }
        return bindingResult.getAllErrors();
    }

    private List<ObjectError> checkAffinityGroupInformation(final PublishingRecord publishingRecord,
            final String assessmentId) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);
        final List<ValidationResult<AffinityGroup>> validationResultList = this.affinityGroupService
                .validateAffinityGroups(assessmentId);
        if (!CollectionUtils.isEmpty(validationResultList)) {
            for (final ObjectError error : Iterables.transform(
                    Iterables.filter(validationResultList, VALIDATION_RESULT_ERROR_LEVEL_FILTER),
                    VALIDATION_RESULT_OERROR_XFORMER.getInstance("Affinity Group"))) {
                bindingResult.addError(error);
            }
        }
        return bindingResult.getAllErrors();
    }

    private List<ObjectError> checkItemInformation(final PublishingRecord publishingRecord,
            final String assessmentId, final List<Segment> segmentList) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);

        final List<Segment> fixedFormSegmentList = Lists
                .newArrayList(Iterables.filter(segmentList, FIXEDFORM_SEGMENT_FILTER));
        final List<Segment> adaptiveSegmentList = Lists
                .newArrayList(Iterables.filter(segmentList, Predicates.not(FIXEDFORM_SEGMENT_FILTER)));

        if (fixedFormSegmentList.size() > 0) {
            final List<Form> formList = this.formService.getFormsByAssessmentId(assessmentId);
            for (final Form form : formList) {
                final List<FormPartition> formPartitionList = this.formPartitionService
                        .getFormPartitionsByFormId(form.getId());
                for (final FormPartition formPartition : formPartitionList) {
                    final List<Item> itemList = this.itemService.getItemsByFormPartitionId(formPartition.getId());
                    if (CollectionUtils.isEmpty(itemList)) {
                        bindingResult.addError(new ObjectError(OBJECT_NAME,
                                paramArray("publishingRecord.formPartition.items.required"),
                                paramArray(formPartition.getName()),
                                "publishingRecord.formPartition.items.required"));
                    }

                    final List<ValidationResult<FormPartition>> validationResultList = this.formService
                            .validateForms(assessmentId);
                    if (!CollectionUtils.isEmpty(validationResultList)) {
                        for (final ObjectError error : Iterables.transform(
                                Iterables.filter(validationResultList, VALIDATION_RESULT_ERROR_LEVEL_FILTER),
                                VALIDATION_RESULT_OERROR_XFORMER.getInstance("Form"))) {
                            bindingResult.addError(error);
                        }
                    }
                }
            }
        }
        if (adaptiveSegmentList.size() > 0) {
            for (final Segment segment : adaptiveSegmentList) {
                final List<Item> itemList = this.itemService.getItemsBySegmentId(segment.getId());
                if (CollectionUtils.isEmpty(itemList)) {
                    bindingResult.addError(
                            new ObjectError(OBJECT_NAME, paramArray("publishingRecord.segment.items.required"),
                                    paramArray(segment.getLabel()), "publishingRecord.segment.items.required"));
                }
            }
            final List<ValidationResult<Segment>> validationResultList = this.itemService
                    .validateItemPools(assessmentId);
            if (!CollectionUtils.isEmpty(validationResultList)) {
                for (final ObjectError error : Iterables.transform(
                        Iterables.filter(validationResultList, VALIDATION_RESULT_ERROR_LEVEL_FILTER),
                        VALIDATION_RESULT_OERROR_XFORMER.getInstance("Item Pool"))) {
                    bindingResult.addError(error);
                }
            }
        }
        return bindingResult.getAllErrors();
    }

    private List<ObjectError> checkPerformanceLevelInformation(final PublishingRecord publishingRecord,
            final String assessmentId) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);
        if (this.performanceLevelService.getPerformanceLevelsByAssessmentId(assessmentId).size() < 1) {
            bindingResult.addError(new ObjectError(OBJECT_NAME, "publishingRecord.performancelevel.required"));
        }
        return bindingResult.getAllErrors();
    }

    private List<ObjectError> checkScoringRuleInformation(final PublishingRecord publishingRecord,
            final String assessmentId) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);
        if (this.scoringRuleService.getScoringRulesByAssessmentId(assessmentId).size() < 1) {
            bindingResult.addError(new ObjectError(OBJECT_NAME, "publishingRecord.scoringrule.required"));
        }
        return bindingResult.getAllErrors();
    }

    private List<ObjectError> checkReportingMeasureInformation(final PublishingRecord publishingRecord,
            final String assessmentId) {
        final BindingResult bindingResult = new BeanPropertyBindingResult(publishingRecord, OBJECT_NAME);
        if (this.reportingMeasureService.getReportingMeasuresByAssessmentId(assessmentId).size() < 1) {
            bindingResult.addError(new ObjectError(OBJECT_NAME, "publishingRecord.reportingmeasure.required"));
        }
        return bindingResult.getAllErrors();
    }

    public List<Purpose> determineAvailableSpecTypesForAssessment(final String assessmentId) {
        // for certain retrievals by assessment ID, it is likely there will be a lot of data, while search uses pageSize of 10 by default and is likely to be more efficient
        final boolean hasSegment = this.segmentService.findSegmentListByAssessmentId(assessmentId).size() > 0;
        final boolean hasPerformanceLevels = this.performanceLevelService
                .getPerformanceLevelsByAssessmentId(assessmentId).size() > 0;
        final boolean hasScoringRules = this.scoringRuleService.getScoringRulesByAssessmentId(assessmentId)
                .size() > 0;
        final boolean hasReportingMeasures = this.reportingMeasureService
                .getReportingMeasuresByAssessmentId(assessmentId).size() > 0;
        final boolean hasBlueprint = this.blueprintElementRepository
                .findAllByAssessmentIdAndActive(assessmentId, true).size() > 0;

        final List<Purpose> specTypesThisAssessmentCanGenerate = Lists.newArrayList();

        if (hasSegment) {
            specTypesThisAssessmentCanGenerate.add(Purpose.REGISTRATION);
        }
        if (hasBlueprint && hasSegment) {
            specTypesThisAssessmentCanGenerate.add(Purpose.ADMINISTRATION);
        }
        if (hasBlueprint && hasPerformanceLevels && hasScoringRules) {
            specTypesThisAssessmentCanGenerate.add(Purpose.SCORING);
        }
        if (hasBlueprint && hasPerformanceLevels && hasReportingMeasures) {
            specTypesThisAssessmentCanGenerate.add(Purpose.REPORTING);
        }
        if (hasBlueprint && hasSegment && hasPerformanceLevels && hasScoringRules && hasReportingMeasures) {
            specTypesThisAssessmentCanGenerate.add(Purpose.COMPLETE);
        }

        return specTypesThisAssessmentCanGenerate;
    }
}