Java tutorial
/******************************************************************************* * 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; } }