org.slc.sli.dal.convert.AssessmentConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.slc.sli.dal.convert.AssessmentConverter.java

Source

/*
 * Copyright 2012-2013 inBloom, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.slc.sli.dal.convert;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.slc.sli.dal.repository.MongoEntityRepository;
import org.slc.sli.domain.Entity;
import org.slc.sli.domain.MongoEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.Map.Entry;

import static org.slc.sli.common.constants.EntityNames.*;
import static org.slc.sli.common.constants.ParameterConstants.*;

/**
 * assessment converter that transform assessment superdoc to sli assessment schema
 *
 * @author Dong Liu dliu@wgen.net
 */
@Component
public class AssessmentConverter extends GenericSuperdocConverter implements SuperdocConverter {

    private static final Logger LOG = LoggerFactory.getLogger(AssessmentConverter.class);

    private static final String OBJECTIVE_ASSESSSMENT_ID = "identificationCode";
    private static final String SUB_OBJECTIVE_ASSESSMENT_HIERARCHY = "objectiveAssessments";

    protected static final String ASSESSMENT_FAMILY_ASSESSMENT_FAMILY_REFERENCE = "assessmentFamilyReference";
    protected static final String SUB_OBJECTIVE_ASSESSMENT_REFS = "subObjectiveAssessment";
    protected static final String ASSESSMENT_ITEM_REFS = "assessmentItemRefs";

    @Override
    public Entity subdocToBodyField(Entity entity) {
        if (entity != null && entity.getType().equals(ASSESSMENT)) {
            if (entity.getBody() == null || entity.getBody().isEmpty()) {
                return null;
            }
            transformToHierarchy(entity);
            subdocsToBody(entity, ASSESSMENT_ITEM, ASSESSMENT_ITEM, Arrays.asList(ASSESSMENT_ID));
            subdocsToBody(entity, OBJECTIVE_ASSESSMENT, OBJECTIVE_ASSESSMENT, Arrays.asList(ASSESSMENT_ID));
            entity.getEmbeddedData().clear();
            collapseAssessmentPeriodDescriptor(entity);
            addFamilyHierarchy(entity);
        }
        return entity;
    }

    private void collapseAssessmentPeriodDescriptor(Entity entity) {
        Object apdId = entity.getBody().remove(ASSESSMENT_PERIOD_DESCRIPTOR_ID);
        if (apdId != null && apdId instanceof String) {
            Entity apd = retrieveAccessmentPeriodDecriptor((String) apdId);
            if (apd != null) {
                entity.getBody().put(ASSESSMENT_PERIOD_DESCRIPTOR, apd.getBody());
            }
        }
    }

    private Entity retrieveAccessmentPeriodDecriptor(String apdId) {
        Entity apd = ((MongoEntityRepository) repo).getTemplate().findById(apdId, Entity.class,
                ASSESSMENT_PERIOD_DESCRIPTOR);

        return apd;
    }

    @Override
    public void bodyFieldToSubdoc(Entity entity) {
        bodyFieldToSubdoc(entity, null);
    }

    @Override
    public void bodyFieldToSubdoc(Entity entity, SuperdocConverter.Option option) {
        if (entity != null && entity.getType().equals(ASSESSMENT)) {
            //objectiveAssessment
            String parentKey = generateDid(entity);
            @SuppressWarnings("unchecked")
            List<Entity> objectiveAssessments = transformFromHierarchy(
                    (List<Map<String, Object>>) entity.getBody().get(OBJECTIVE_ASSESSMENT), parentKey);

            entity.getBody().remove(OBJECTIVE_ASSESSMENT);
            entity.getEmbeddedData().put(OBJECTIVE_ASSESSMENT, objectiveAssessments);

            //assessmentItem
            List<Map<String, Object>> assessmentItems = new ArrayList<Map<String, Object>>();
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> topLevels = (List<Map<String, Object>>) entity.getBody()
                    .remove(ASSESSMENT_ITEM);
            if (topLevels != null) {
                assessmentItems.addAll(topLevels);
                assessmentItems.addAll(extractAssessmentItems(objectiveAssessments, parentKey));
            }
            makeSubDocs(entity, ASSESSMENT_ITEM, ASSESSMENT_ID, assessmentItems);

            //assessmentPeriodDescriptor
            Object assessmentPeriodDescriptor = entity.getBody().remove(ASSESSMENT_PERIOD_DESCRIPTOR);
            if (assessmentPeriodDescriptor instanceof Map<?, ?>) {
                //update embedded assessmentPeriodDescriptor
                @SuppressWarnings("unchecked")
                String did = generateSubdocDid((Map<String, Object>) assessmentPeriodDescriptor,
                        ASSESSMENT_PERIOD_DESCRIPTOR);
                @SuppressWarnings("unchecked")
                MongoEntity apdEntity = new MongoEntity(ASSESSMENT_PERIOD_DESCRIPTOR, did,
                        (Map<String, Object>) assessmentPeriodDescriptor, null);
                if (repo.update(ASSESSMENT_PERIOD_DESCRIPTOR, apdEntity, false)) {
                    //only record the id if it was successfully updated
                    entity.getBody().put(ASSESSMENT_PERIOD_DESCRIPTOR_ID, did);
                }
            }

            fixAssessmentFamilyReference(entity, option);
        }
    }

    public List<Map<String, Object>> extractAssessmentItems(List<Entity> objectiveAssessments, String parentKey) {
        List<Map<String, Object>> assessmentItems = new ArrayList<Map<String, Object>>();
        for (Entity objectiveAssessment : objectiveAssessments) {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> objectiveItems = (List<Map<String, Object>>) objectiveAssessment.getBody()
                    .remove(ASSESSMENT_ITEM);
            if (objectiveItems != null) {
                assessmentItems.addAll(objectiveItems);
                List<String> refs = makeAssessmentItemRefs(objectiveItems, parentKey);
                objectiveAssessment.getBody().put(ASSESSMENT_ITEM_REFS, refs);
            }
        }
        return assessmentItems;
    }

    @Override
    public Iterable<Entity> subdocToBodyField(Iterable<Entity> entities) {
        List<Entity> converted = new ArrayList<Entity>();
        if (entities != null) {
            for (Entity entity : entities) {
                Entity newEntity = subdocToBodyField(entity);
                if (newEntity != null) {
                    converted.add(newEntity);
                }
            }
        }
        return converted;
    }

    @Override
    public void bodyFieldToSubdoc(Iterable<Entity> entities) {
        bodyFieldToSubdoc(entities, null);
    }

    @Override
    public void bodyFieldToSubdoc(Iterable<Entity> entities, SuperdocConverter.Option option) {
        if (entities != null) {
            for (Entity entity : entities) {
                bodyFieldToSubdoc(entity, option);
            }
        }
    }

    /**
     * On update, AssessmentFamilyReference will not exist on the entity. Fetch it from
     * the db and re-add it to the entity.
     */
    private void fixAssessmentFamilyReference(Entity entity, SuperdocConverter.Option option) {
        //assessmentFamilyHierarchy is ignored on create/update
        Object assessmentFamilyHierarchy = entity.getBody().remove(ASSESSMENT_FAMILY_HIERARCHY);

        // There is a requirement that assessment.assessmentFamilyReference be READONLY via the API
        // so only add assessmentFamilyReference back if the delete option is NOT set
        if (option == null || Option.DELETE_ASSESSMENT_FAMILY_REFERENCE != option) {
            MongoTemplate mongo = ((MongoEntityRepository) repo).getTemplate();
            Entity existingAssessment = mongo.findById(entity.getEntityId(), Entity.class, ASSESSMENT);
            if (existingAssessment == null) {
                return;
            }
            if (existingAssessment.getBody().containsKey(ASSESSMENT_FAMILY_REFERENCE)) {
                String assessmentFamilyRef = (String) existingAssessment.getBody().get(ASSESSMENT_FAMILY_REFERENCE);
                entity.getBody().put(ASSESSMENT_FAMILY_REFERENCE, assessmentFamilyRef);
            }
        }
    }

    /**
     * Construct the assessmentFamilyHierarchy string from the assessment family reference
     */
    private void addFamilyHierarchy(Entity entity) {
        Object object = entity.getBody().remove(ASSESSMENT_FAMILY_REFERENCE);
        if (object == null || !(object instanceof String)) {
            // we don't validate assessmentFamilyHierarchy any more, so someone could have passed in
            // an object, array, number, etc.
            return;
        }
        String familyRef = (String) object;
        MongoTemplate mongo = ((MongoEntityRepository) repo).getTemplate();
        Entity family = mongo.findById(familyRef, Entity.class, ASSESSMENT_FAMILY);

        List<String> familyTitles = new LinkedList<String>();
        Set<String> seenFamilyRefs = new HashSet<String>();
        seenFamilyRefs.add(familyRef);
        while (family != null) {
            String familyTitle = (String) family.getBody().get(ASSESSMENT_FAMILY_TITLE);
            if (familyTitle == null) {
                LOG.error("Required assessmentFamilyTitle is null for assessmentFamily with _id : {}",
                        new Object[] { family.getEntityId() });
                break;
            }
            familyTitles.add(familyTitle);
            String parentRef = (String) family.getBody().get(ASSESSMENT_FAMILY_ASSESSMENT_FAMILY_REFERENCE);
            family = null;

            if (parentRef != null) {
                if (!seenFamilyRefs.contains(parentRef)) {
                    family = mongo.findById(parentRef, Entity.class, ASSESSMENT_FAMILY);
                    seenFamilyRefs.add(parentRef);
                } else {
                    LOG.error(
                            "Circular reference detected in assessment family hierarchy. _id : {} occurs twice in hierarchy.",
                            new Object[] { parentRef });
                }
            }
        }

        String familyHierarchyString = Joiner.on(".").join(Lists.reverse(familyTitles));
        entity.getBody().put(ASSESSMENT_FAMILY_HIERARCHY, familyHierarchyString);
    }

    /**
     * Transform the entity from the flat subdoc representation to one that shows nested objective
     * assessments and assessment items in a hierarchical fashion
     *
     * @param entity
     * @return
     */
    private Entity transformToHierarchy(Entity entity) {
        List<Entity> oas = entity.getEmbeddedData().get(OBJECTIVE_ASSESSMENT);
        Map<String, Entity> items = getAssessmentItemMap(entity.getEmbeddedData().get(ASSESSMENT_ITEM));
        if (oas != null) {
            Map<String, Entity> allOAs = new LinkedHashMap<String, Entity>();
            for (Entity oa : oas) {
                addEmbeddedAssessmentItems(items, oa);
                allOAs.put(oa.getBody().get(OBJECTIVE_ASSESSSMENT_ID).toString(), oa);
            }
            Set<String> topLevelOAs = new HashSet<String>(allOAs.keySet());
            for (Entry<String, Entity> oa : allOAs.entrySet()) {
                @SuppressWarnings("unchecked")
                List<String> subOAs = (List<String>) oa.getValue().getBody().get(SUB_OBJECTIVE_ASSESSMENT_REFS);
                if (subOAs != null) {
                    List<Map<String, Object>> actuals = new ArrayList<Map<String, Object>>(subOAs.size());
                    for (String ref : subOAs) {
                        if (topLevelOAs.remove(ref)) {
                            Map<String, Object> actual = allOAs.get(ref).getBody();
                            actual.remove(ASSESSMENT_ID);
                            actuals.add(actual);
                        }
                    }
                    oa.getValue().getBody().put(SUB_OBJECTIVE_ASSESSMENT_HIERARCHY, actuals);
                    oa.getValue().getBody().remove(SUB_OBJECTIVE_ASSESSMENT_REFS);
                }
            }
            List<Entity> transformed = new ArrayList<Entity>(topLevelOAs.size());
            for (String id : topLevelOAs) {
                transformed.add(allOAs.get(id));
            }
            entity.getEmbeddedData().put(OBJECTIVE_ASSESSMENT, transformed);
            entity.getEmbeddedData().put(ASSESSMENT_ITEM, new ArrayList<Entity>(items.values()));
        }
        return entity;
    }

    /**
     * Adds in any embedded assessment items to the objective assessment entity
     * Any assessment items added are also removed from the map of items so they won't show up again
     *
     * @param items
     * @param oa
     */
    private void addEmbeddedAssessmentItems(Map<String, Entity> items, Entity oa) {
        List<?> itemRefs = (List<?>) oa.getBody().remove(ASSESSMENT_ITEM_REFS);
        if (itemRefs != null) {
            List<Map<String, Object>> resolvedItems = new ArrayList<Map<String, Object>>();
            for (Object ref : itemRefs) {
                Entity resolved = items.remove(ref);
                if (resolved != null) {
                    Map<String, Object> item = resolved.getBody();
                    item.remove("_id");
                    item.remove(ASSESSMENT_ID);
                    resolvedItems.add(item);
                }
            }
            oa.getBody().put(ASSESSMENT_ITEM, resolvedItems);
        }
    }

    private Map<String, Entity> getAssessmentItemMap(List<Entity> items) {
        if (items == null) {
            return Collections.emptyMap();
        }
        Map<String, Entity> map = new HashMap<String, Entity>(items.size());
        for (Entity item : items) {
            map.put(item.getEntityId(), item);
        }
        return map;
    }

    private List<Entity> transformFromHierarchy(List<Map<String, Object>> originals, String parentKey) {
        List<Entity> oas = new ArrayList<Entity>();
        if (originals != null) {
            for (Map<String, Object> original : originals) {
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> subs = (List<Map<String, Object>>) original
                        .get(SUB_OBJECTIVE_ASSESSMENT_HIERARCHY);
                Entity oa = makeOAEntity(original, parentKey);
                if (subs != null && !subs.isEmpty()) {
                    oas.addAll(transformFromHierarchy(subs, parentKey));
                    oa.getBody().put(SUB_OBJECTIVE_ASSESSMENT_REFS, makeOARefs(subs));
                    original.remove(SUB_OBJECTIVE_ASSESSMENT_HIERARCHY);
                }
                oas.add(oa);
            }
        }
        return oas;
    }

    private List<String> makeOARefs(List<Map<String, Object>> oas) {
        List<String> refs = new ArrayList<String>(oas.size());
        for (Map<String, Object> oa : oas) {
            refs.add(oa.get(OBJECTIVE_ASSESSSMENT_ID).toString());
        }
        return refs;
    }

    private Entity makeOAEntity(Map<String, Object> map, String parentKey) {
        map.put(ASSESSMENT_ID, parentKey);
        String did = generateSubdocDid(map, OBJECTIVE_ASSESSMENT);
        return new MongoEntity(OBJECTIVE_ASSESSMENT, did, map, null);
    }

    private List<String> makeAssessmentItemRefs(List<Map<String, Object>> items, String parentKey) {
        List<String> refs = new ArrayList<String>(items.size());
        for (Map<String, Object> item : items) {
            item.put(ASSESSMENT_ID, parentKey);
            refs.add(generateSubdocDid(item, ASSESSMENT_ITEM));
        }
        return refs;
    }

}