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

Java tutorial

Introduction

Here is the source code for org.opentestsystem.authoring.testauth.service.impl.BlueprintValidationHelper.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.config.TestAuthUtil.safeParseInt;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_PARENT_KEY_FUNCTION;
import static org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BLUEPRINT_ELEMENT_UNIQUE_KEY_FUNCTION;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.opentestsystem.authoring.testauth.domain.BlueprintElement;
import org.opentestsystem.authoring.testauth.domain.BlueprintElementValue;
import org.opentestsystem.authoring.testauth.domain.Segment;
import org.opentestsystem.authoring.testauth.domain.ValidationResult;
import org.opentestsystem.authoring.testauth.service.impl.BlueprintHelper.BPE_BPEV_TRANSFORMER;
import org.springframework.util.CollectionUtils;

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

public final class BlueprintValidationHelper {

    private static final String MASTER_KEY = "MASTER";
    private static final String TOP_BLUEPRINT_ELEMENT_LEVEL = "1";
    private static final String OP_MIN_FIELD = "operationalItemMinValue";
    private static final String OP_MAX_FIELD = "operationalItemMaxValue";
    private static final String FT_MIN_FIELD = "fieldTestItemMinValue";
    private static final String FT_MAX_FIELD = "fieldTestItemMaxValue";
    private static final String ACTIVE_FIELD = "active";
    private static final String PARENT_MESSAGE = "The %s value of the content standard (%s) is %s than the sum of its children's %s value (%s)";
    private static final String PARENT_SEGMENT_MESSAGE = "The %s value of the segment (%s) is %s than the sum of its blueprint's top-level standard's %s value (%s)";
    private static final String INACTIVE_MESSAGE = "Child element may not be Active if parent element is Inactive.";

    private BlueprintValidationHelper() {
        // do not instantiate
    }

    /**
     * Handles hierarchical validation for each individual <code>BlueprintElement</code>. This includes:
     * <ul>
     * <li>The sum of the child min values must be less than or equal to the parent max</li>
     * <li>The sum of the child max values must be greater than or equal to the parent min</li>
     * </ul>
     */
    protected static List<ValidationResult<BlueprintElement>> validateBlueprintHierarchicalRelationships(
            final List<BlueprintElement> blueprintElementList) {
        final List<ValidationResult<BlueprintElement>> errors = Lists.newArrayList();

        if (!CollectionUtils.isEmpty(blueprintElementList)) {
            final List<String> valueMapKeys = Lists
                    .newArrayList(blueprintElementList.get(0).getBlueprintElementValueMap().keySet());
            final Map<String, BlueprintElement> blueprintElementMap = Maps.uniqueIndex(blueprintElementList,
                    BLUEPRINT_ELEMENT_UNIQUE_KEY_FUNCTION);

            final List<BlueprintElement> filteredChildrenBlueprintElementList = Lists
                    .newArrayList(Iterables.filter(blueprintElementList, BLUEPRINT_ELEMENT_CHILD_FILTER));
            final Map<String, Collection<BlueprintElement>> blueprintElementGroupedMap = Multimaps
                    .index(filteredChildrenBlueprintElementList, BLUEPRINT_ELEMENT_PARENT_KEY_FUNCTION).asMap();

            for (final Entry<String, Collection<BlueprintElement>> blueprintElementEntry : blueprintElementGroupedMap
                    .entrySet()) {
                if (StringUtils.isNotBlank(blueprintElementEntry.getKey())) {
                    final BlueprintElement parentBlueprintElement = blueprintElementMap
                            .get(blueprintElementEntry.getKey());
                    if (parentBlueprintElement != null) {
                        for (final String valueMapKey : valueMapKeys) {
                            final List<BlueprintElementValue> bpValueList = Lists.transform(
                                    Lists.newArrayList(blueprintElementEntry.getValue()),
                                    BPE_BPEV_TRANSFORMER.getInstance(valueMapKey));
                            final BlueprintElementValue parentValue = parentBlueprintElement
                                    .getBlueprintElementValueMap().get(valueMapKey);
                            final BlueprintElementValue childSummedValue = getActualListSum(bpValueList);

                            if (childSummedValue.getOperationalItemMinValue() > parentValue
                                    .getOperationalItemMaxValue()) {
                                errors.add(buildValidationResult(parentBlueprintElement, valueMapKey, OP_MAX_FIELD,
                                        String.format(PARENT_MESSAGE, "OP Max",
                                                parentValue.getOperationalItemMaxValue(), "less", "OP Min",
                                                childSummedValue.getOperationalItemMinValue())));
                            }

                            if (childSummedValue.getOperationalItemMaxValue() < parentValue
                                    .getOperationalItemMinValue()) {
                                errors.add(buildValidationResult(parentBlueprintElement, valueMapKey, OP_MIN_FIELD,
                                        String.format(PARENT_MESSAGE, "OP Min",
                                                parentValue.getOperationalItemMinValue(), "greater", "OP Max",
                                                childSummedValue.getOperationalItemMaxValue())));
                            }

                            if (childSummedValue.getFieldTestItemMinValue() > parentValue
                                    .getFieldTestItemMaxValue()) {
                                errors.add(buildValidationResult(parentBlueprintElement, valueMapKey, FT_MAX_FIELD,
                                        String.format(PARENT_MESSAGE, "FT Max",
                                                parentValue.getFieldTestItemMaxValue(), "less", "FT Min",
                                                childSummedValue.getFieldTestItemMinValue())));
                            }

                            if (childSummedValue.getFieldTestItemMaxValue() < parentValue
                                    .getFieldTestItemMinValue()) {
                                errors.add(buildValidationResult(parentBlueprintElement, valueMapKey, FT_MIN_FIELD,
                                        String.format(PARENT_MESSAGE, "FT Min",
                                                parentValue.getFieldTestItemMinValue(), "greater", "FT Max",
                                                childSummedValue.getFieldTestItemMaxValue())));
                            }
                        }
                    }
                }
            }
        }

        return errors;
    }

    /**
     * Handles active/inactive hierarchical validation for each individual <code>BlueprintElement</code>. This includes:
     * <ul>
     * <li>A child standard may not be ACTIVE if it's parent is INACTIVE</li>
     * </ul>
     */
    protected static List<ValidationResult<BlueprintElement>> validateInactiveBlueprintHierarchicalRelationships(
            final List<BlueprintElement> blueprintElementList) {
        final List<ValidationResult<BlueprintElement>> errors = Lists.newArrayList();

        if (!CollectionUtils.isEmpty(blueprintElementList)) {
            final Map<String, BlueprintElement> blueprintElementMap = Maps.uniqueIndex(blueprintElementList,
                    BLUEPRINT_ELEMENT_UNIQUE_KEY_FUNCTION);

            final List<BlueprintElement> filteredChildrenBlueprintElementList = Lists
                    .newArrayList(Iterables.filter(blueprintElementList, BLUEPRINT_ELEMENT_CHILD_FILTER));
            final Map<String, Collection<BlueprintElement>> blueprintElementGroupedMap = Multimaps
                    .index(filteredChildrenBlueprintElementList, BLUEPRINT_ELEMENT_PARENT_KEY_FUNCTION).asMap();

            for (final Entry<String, Collection<BlueprintElement>> blueprintElementEntry : blueprintElementGroupedMap
                    .entrySet()) {
                if (StringUtils.isNotBlank(blueprintElementEntry.getKey())) {
                    final BlueprintElement parentBlueprintElement = blueprintElementMap
                            .get(blueprintElementEntry.getKey());
                    if (parentBlueprintElement != null) {
                        // validate parent/child inactive rules
                        if (!parentBlueprintElement.isActive()) {
                            for (final BlueprintElement childElement : blueprintElementGroupedMap
                                    .get(blueprintElementEntry.getKey())) {
                                if (childElement.isActive()) {
                                    errors.add(buildValidationResult(childElement, MASTER_KEY, ACTIVE_FIELD,
                                            INACTIVE_MESSAGE));
                                }
                            }
                        }
                    }
                }
            }
        }

        return errors;
    }

    /**
     * Handles top level blueprint vs. segment validation for each individual <code>BlueprintElement</code>. This includes:
     * <ul>
     * <li>The sum of the blueprint top-level min values must be less than or equal to the segment max</li>
     * <li>The sum of the blueprint top-level max values must be greater than or equal to the segment min</li>
     * </ul>
     */
    protected static List<ValidationResult<BlueprintElement>> validateTopLevelBlueprintVsSegmentRelationships(
            final List<Segment> segments, final List<BlueprintElement> blueprintElementList) {
        final List<ValidationResult<BlueprintElement>> errors = Lists.newArrayList();
        final List<BlueprintElement> topLevelBlueprintElements = Lists
                .newArrayList(Iterables.filter(blueprintElementList, BLUEPRINT_ELEMENT_TOP_LEVEL_FILTER));

        for (final Segment segment : segments) {
            final List<BlueprintElementValue> topLevelBpValuesBySegment = Lists.newArrayList(Iterables.transform(
                    Iterables.filter(topLevelBlueprintElements,
                            BLUEPRINT_ELEMENT_SEGMENT_VALUE_FILTER.getInstance(segment.getId())),
                    BLUEPRINT_ELEMENT_SEGMENT_VALUE_TRANSFORMER.getInstance(segment.getId())));

            if (!topLevelBpValuesBySegment.isEmpty()) {
                final BlueprintElementValue segmentSumBpValue = BlueprintHelper
                        .getListSum(topLevelBpValuesBySegment);
                if (segmentSumBpValue.getOperationalItemMinValue() > segment.getMaxOpItems()) {
                    errors.add(buildValidationResult(null, segment.getId(), OP_MAX_FIELD,
                            String.format(PARENT_SEGMENT_MESSAGE, "OP Max", segment.getMaxOpItems(), "less",
                                    "OP Min", segmentSumBpValue.getOperationalItemMinValue())));
                }

                if (segmentSumBpValue.getOperationalItemMaxValue() < segment.getMinOpItems()) {
                    errors.add(buildValidationResult(null, segment.getId(), OP_MIN_FIELD,
                            String.format(PARENT_SEGMENT_MESSAGE, "OP Min", segment.getMinOpItems(), "greater",
                                    "OP Max", segmentSumBpValue.getOperationalItemMaxValue())));
                }

                if (segmentSumBpValue.getFieldTestItemMinValue() > segment.getMaxFtItems()) {
                    errors.add(buildValidationResult(null, segment.getId(), FT_MAX_FIELD,
                            String.format(PARENT_SEGMENT_MESSAGE, "FT Max", segment.getMaxFtItems(), "less",
                                    "FT Min", segmentSumBpValue.getFieldTestItemMinValue())));
                }

                if (segmentSumBpValue.getFieldTestItemMaxValue() < segment.getMinFtItems()) {
                    errors.add(buildValidationResult(null, segment.getId(), FT_MIN_FIELD,
                            String.format(PARENT_SEGMENT_MESSAGE, "FT Min", segment.getMinFtItems(), "greater",
                                    "FT Max", segmentSumBpValue.getFieldTestItemMaxValue())));
                }
            }
        }

        return errors;
    }

    /**
     * Handles basic validation for each individual <code>BlueprintElementValue</code> in the blueprint list. This includes:
     * <ul>
     * <li>No min item value may be larger than the corresponding max item value</li>
     * </ul>
     */
    protected static List<ValidationResult<BlueprintElement>> validateBlueprintElementValueItemRanges(
            final List<BlueprintElement> blueprintElementList) {
        return Lists.newArrayList(Iterables.concat(
                Iterables.transform(blueprintElementList, BLUEPRINT_ELEMENT_ITEM_RANGE_VALIDATION_TRANSFORMER)));
    }

    private static final Function<BlueprintElement, List<ValidationResult<BlueprintElement>>> BLUEPRINT_ELEMENT_ITEM_RANGE_VALIDATION_TRANSFORMER = new Function<BlueprintElement, List<ValidationResult<BlueprintElement>>>() {
        @Override
        public List<ValidationResult<BlueprintElement>> apply(final BlueprintElement blueprintElement) {
            final List<ValidationResult<BlueprintElement>> errors = Lists.newArrayList();

            // validate individual blueprint element values
            for (final Entry<String, BlueprintElementValue> entry : blueprintElement.getBlueprintElementValueMap()
                    .entrySet()) {
                if (entry.getValue().getOperationalItemMinValue() > entry.getValue().getOperationalItemMaxValue()) {
                    errors.add(buildValidationResult(blueprintElement, entry.getKey(), OP_MIN_FIELD,
                            "OP Min Value must be less than or equal to OP Max Value."));
                }

                if (entry.getValue().getFieldTestItemMinValue() > entry.getValue().getFieldTestItemMaxValue()) {
                    errors.add(buildValidationResult(blueprintElement, entry.getKey(), FT_MIN_FIELD,
                            "FT Min Value must be less than or equal to FT Max Value."));
                }

            }

            return errors;
        }
    };

    private static final class BLUEPRINT_ELEMENT_SEGMENT_VALUE_FILTER implements Predicate<BlueprintElement> {
        private final String segmentId;

        public static BLUEPRINT_ELEMENT_SEGMENT_VALUE_FILTER getInstance(final String segmentId) {
            return new BLUEPRINT_ELEMENT_SEGMENT_VALUE_FILTER(segmentId);
        }

        private BLUEPRINT_ELEMENT_SEGMENT_VALUE_FILTER(final String segmentId) {
            this.segmentId = segmentId;
        }

        @Override
        public boolean apply(final BlueprintElement blueprintElement) {
            return blueprintElement != null && blueprintElement.getBlueprintElementValueMap() != null
                    && blueprintElement.getBlueprintElementValueMap().get(this.segmentId) != null;
        }
    };

    private static final class BLUEPRINT_ELEMENT_SEGMENT_VALUE_TRANSFORMER
            implements Function<BlueprintElement, BlueprintElementValue> {
        private final String segmentId;

        public static BLUEPRINT_ELEMENT_SEGMENT_VALUE_TRANSFORMER getInstance(final String segmentId) {
            return new BLUEPRINT_ELEMENT_SEGMENT_VALUE_TRANSFORMER(segmentId);
        }

        private BLUEPRINT_ELEMENT_SEGMENT_VALUE_TRANSFORMER(final String segmentId) {
            this.segmentId = segmentId;
        }

        @Override
        public BlueprintElementValue apply(final BlueprintElement blueprintElement) {
            return blueprintElement.getBlueprintElementValueMap().get(this.segmentId);
        }
    };

    private static final Predicate<BlueprintElement> BLUEPRINT_ELEMENT_CHILD_FILTER = new Predicate<BlueprintElement>() {
        @Override
        public boolean apply(final BlueprintElement blueprintElement) {
            return blueprintElement != null && StringUtils.isNotBlank(blueprintElement.getParentKey());
        }
    };

    private static final Predicate<BlueprintElement> BLUEPRINT_ELEMENT_TOP_LEVEL_FILTER = new Predicate<BlueprintElement>() {
        @Override
        public boolean apply(final BlueprintElement blueprintElement) {
            return blueprintElement != null && StringUtils.isNotBlank(blueprintElement.getLevel())
                    && blueprintElement.getLevel().trim().equalsIgnoreCase(TOP_BLUEPRINT_ELEMENT_LEVEL);
        }
    };

    private static final ValidationResult<BlueprintElement> buildValidationResult(
            final BlueprintElement blueprintElement, final String segmentId, final String fieldName,
            final String message) {
        final ValidationResult<BlueprintElement> result = new ValidationResult<BlueprintElement>();
        result.setValidatedObject(blueprintElement);
        result.setSegmentId(segmentId);
        result.setFieldName(fieldName);
        result.setMessage(message);
        return result;
    }

    private static final BlueprintElementValue getActualListSum(
            final Collection<BlueprintElementValue> bpValueList) {
        final BlueprintElementValue sumValue = new BlueprintElementValue(0, 0, 0, 0);
        for (final BlueprintElementValue nextValue : bpValueList) {
            sumValue.setOperationalItemMinValue(
                    sumValue.getOperationalItemMinValue() + safeParseInt(nextValue.getOperationalItemMinValue()));
            sumValue.setOperationalItemMaxValue(
                    sumValue.getOperationalItemMaxValue() + safeParseInt(nextValue.getOperationalItemMaxValue()));
            sumValue.setFieldTestItemMinValue(
                    sumValue.getFieldTestItemMinValue() + safeParseInt(nextValue.getFieldTestItemMinValue()));
            sumValue.setFieldTestItemMaxValue(
                    sumValue.getFieldTestItemMaxValue() + safeParseInt(nextValue.getFieldTestItemMaxValue()));
        }
        return sumValue;
    }

}