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

Java tutorial

Introduction

Here is the source code for org.slc.sli.dal.convert.ContainerDocumentAccessor.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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.mongodb.*;

import org.slc.sli.validation.SchemaRepository;
import org.slc.sli.validation.schema.AppInfo;
import org.slc.sli.validation.schema.NeutralSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import org.slc.sli.common.domain.ContainerDocument;
import org.slc.sli.common.domain.ContainerDocumentHolder;
import org.slc.sli.common.util.tenantdb.TenantContext;
import org.slc.sli.common.util.uuid.UUIDGeneratorStrategy;
import org.slc.sli.domain.Entity;
import org.slc.sli.validation.schema.INaturalKeyExtractor;
import org.springframework.stereotype.Component;

/**
 * @author pghosh
 */
public class ContainerDocumentAccessor {

    private ContainerDocumentHolder containerDocumentHolder;

    private UUIDGeneratorStrategy generatorStrategy;

    private INaturalKeyExtractor naturalKeyExtractor;

    private MongoTemplate mongoTemplate;

    private SubDocAccessor subDocAccessor;

    private SchemaRepository schemaRepo;

    private final Map<String, SubDocAccessor.Location> locationMap = new HashMap<String, SubDocAccessor.Location>();

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

    public ContainerDocumentAccessor(final UUIDGeneratorStrategy strategy, final INaturalKeyExtractor extractor,
            final MongoTemplate mongoTemplate, final SchemaRepository schemaRepo) {
        this.generatorStrategy = strategy;
        this.naturalKeyExtractor = extractor;
        this.mongoTemplate = mongoTemplate;
        //TODO: Fix (springify)
        this.containerDocumentHolder = new ContainerDocumentHolder();
        this.subDocAccessor = new SubDocAccessor(mongoTemplate, strategy, extractor);
        this.schemaRepo = schemaRepo;
    }

    public boolean isContainerDocument(final String entity) {
        return containerDocumentHolder.isContainerDocument(entity);
    }

    public boolean isContainerSubdoc(final String entity) {
        boolean isContainerSubdoc = false;
        if (containerDocumentHolder.isContainerDocument(entity)) {
            isContainerSubdoc = containerDocumentHolder.getContainerDocument(entity).isContainerSubdoc();
        }
        return isContainerSubdoc;
    }

    public boolean insert(final List<Entity> entityList) {
        boolean result = true;

        for (Entity entity : entityList) {
            result &= !insert(entity).isEmpty();
        }

        return result;
    }

    public String insert(final Entity entity) {
        final DBObject query = getContainerDocQuery(entity);
        return insertContainerDoc(query, entity);
    }

    public boolean update(final String type, final String id, Map<String, Object> newValues,
            String collectionName) {
        final Query query = Query.query(Criteria.where("_id").is(id));
        return updateContainerDoc(query, newValues, collectionName, type);
    }

    public String update(final Entity entity) {
        ContainerDocument containerDocument = containerDocumentHolder.getContainerDocument(entity.getType());
        if (containerDocument.isContainerSubdoc()) {
            boolean persisted = getLocation(entity.getType()).create(entity);
            if (persisted) {
                return entity.getEntityId();
            } else {
                return "";
            }
        }
        return updateContainerDoc(entity);
    }

    public Entity findById(String collectionName, String id) {
        return getLocation(collectionName).findById(id);
    }

    public List<Entity> findAll(String collectionName, Query query) {
        return getLocation(collectionName).findAll(query);
    }

    public boolean delete(final Entity entity) {
        return deleteContainerDoc(entity);
    }

    /**
     * Generate query criteria for the container-embedded doc (e.g. attendanceEvent) based on natural key fields
     * specified in schema for the 'embeddedDocType'.
     *
     * @param embeddedDocType
     *        Container document type
     * @param doc
     *        Container-embedded document (e.g. single attendanceEvent)
     *
     * @return
     *        Query criteria for the container-embedded doc based on the natural key fields.
     */
    private Map<String, Object> filterByNaturalKeys(String embeddedDocType, Map<String, Object> doc) {
        Map<String, Object> filteredDoc = new HashMap<String, Object>();
        List<Map<String, Object>> fieldCriteria = new ArrayList<Map<String, Object>>();

        // get natural key fields from schema
        NeutralSchema schema = schemaRepo.getSchema(embeddedDocType);

        // don't filter if the natural keys are unknown
        if (schema == null) {
            return doc;
        }

        // loop over natural key fields
        Map<String, NeutralSchema> fieldSchemas = schema.getFields();
        for (Map.Entry<String, NeutralSchema> fieldSchema : fieldSchemas.entrySet()) {
            AppInfo appInfo = (fieldSchema.getValue() == null) ? null : fieldSchema.getValue().getAppInfo();
            if (appInfo != null && appInfo.isNaturalKey()) {
                Map<String, Object> naturalKeyCriteria = new HashMap<String, Object>();
                if (doc.containsKey(fieldSchema.getKey())) {
                    // add it to the update criteria
                    naturalKeyCriteria.put(fieldSchema.getKey(), doc.get(fieldSchema.getKey()));
                    fieldCriteria.add(naturalKeyCriteria);
                } else {
                    Map<String, Object> nonExistCriteria = new HashMap<String, Object>();
                    nonExistCriteria.put(QueryOperators.EXISTS, false);
                    // explicitly exclude missing natural key fields
                    naturalKeyCriteria.put(fieldSchema.getKey(), nonExistCriteria);
                    fieldCriteria.add(naturalKeyCriteria);
                }
            }
        }

        filteredDoc.put(QueryOperators.AND, fieldCriteria);
        return filteredDoc;
    }

    /**
     * Delete embedded documents from container doc record.
     *
     * @param containerDoc
     *        Container document containing embedded entities to delete
     *
     * @return
     *         Whether or not delete was successful.
     */
    @SuppressWarnings("unchecked")
    public boolean deleteContainerNonSubDocs(final Entity containerDoc) {
        // Extract embedded non-subdocs from container doc.
        String collection = containerDoc.getType();
        String embeddedDocType = getEmbeddedDocType(collection);
        List<Map<String, Object>> embeddedDocs = (List<Map<String, Object>>) containerDoc.getBody()
                .get(embeddedDocType);

        // Delete the specified embedded documents from the container doc in the database.
        String containerDocId = containerDoc.getEntityId();
        final BasicDBObject query = new BasicDBObject();
        query.put("_id", containerDocId);
        DBObject result = null;
        for (Map<String, Object> docToDelete : embeddedDocs) {
            // filter update to include natural key values and explicitly exclude missing natural keys
            Map<String, Object> filteredDocToDelete = filterByNaturalKeys(embeddedDocType, docToDelete);
            BasicDBObject dBDocToDelete = new BasicDBObject("body." + embeddedDocType, filteredDocToDelete);
            final BasicDBObject update = new BasicDBObject("$pull", dBDocToDelete);
            result = this.mongoTemplate.getCollection(collection).findAndModify(query, null, null, false, update,
                    true, false);
            if (result == null) {
                LOG.error("Could not delete " + embeddedDocType + " instance from " + collection
                        + " record with id " + containerDocId);
                return false;
            }
        }

        // If this was the last embedded document, delete the container doc as well.
        List<Map<String, Object>> remainingAttendanceEvents = (List<Map<String, Object>>) ((Map<String, Object>) result
                .get("body")).get(embeddedDocType);
        if (remainingAttendanceEvents == null || remainingAttendanceEvents.isEmpty()) {
            Query frQuery = new Query(Criteria.where("_id").is(containerDocId));
            Entity deleted = this.mongoTemplate.findAndRemove(frQuery, Entity.class, collection);
            if (deleted == null) {
                LOG.error("Could not delete empty " + collection + " record with id " + containerDocId);
                return false;
            }
        }

        return true;
    }

    public long count(String collectionName, Query query) {
        return getLocation(collectionName).count(query);
    }

    public boolean exists(String collectionName, String id) {
        return getLocation(collectionName).exists(id);
    }

    private DBObject getContainerDocQuery(final Entity entity) {
        final String parentKey = ContainerDocumentHelper.createParentKey(entity, containerDocumentHolder,
                generatorStrategy);

        final Query query = Query.query(Criteria.where("_id").is(parentKey));

        return query.getQueryObject();
    }

    /**
     * Get embedded document type for a container doc.
     *
     * @param containerDocType
     *        Type of container document
     *
     * @return
     *         Embedded document type of this container doc
     */
    public String getEmbeddedDocType(final String containerDocType) {
        return containerDocumentHolder.getContainerDocument(containerDocType).getFieldToPersist();
    }

    // TODO: private

    protected boolean updateContainerDoc(final Query query, Map<String, Object> newValues, String collectionName,
            String type) {
        final ContainerDocument containerDocument = containerDocumentHolder.getContainerDocument(type);
        if (containerDocument.isContainerSubdoc()) {
            Update update = new Update();
            for (Map.Entry<String, Object> patch : newValues.entrySet()) {
                update.set("body." + patch.getKey(), patch.getValue());
            }
            return getLocation(type).doUpdate(query, update);
        }

        //empty attendanceEvent(or other) array
        DBObject emptyArray = new BasicDBObject();
        emptyArray.put("body." + containerDocument.getFieldToPersist(), new ArrayList());
        DBObject setEmptyArray = new BasicDBObject("$set", emptyArray);
        mongoTemplate.getCollection(collectionName).update(query.getQueryObject(), setEmptyArray, false, false,
                WriteConcern.SAFE);

        TenantContext.setIsSystemCall(false);
        final String fieldToPersist = containerDocument.getFieldToPersist();
        DBObject entityDetails = new BasicDBObject();
        for (Map.Entry<String, Object> newValue : newValues.entrySet()) {
            if (!newValue.getKey().equals(containerDocument.getFieldToPersist())) {
                entityDetails.put("body." + newValue.getKey(), newValue.getValue());
            }
        }
        DBObject set = new BasicDBObject("$set", entityDetails);
        DBObject docToPersist = null;
        if (newValues.containsKey(containerDocument.getFieldToPersist())) {
            docToPersist = BasicDBObjectBuilder.start().push("$pushAll")
                    .add("body." + fieldToPersist, newValues.get(fieldToPersist)).get();
        } else {
            docToPersist = new BasicDBObject();
        }

        docToPersist.putAll(set);

        return mongoTemplate.getCollection(collectionName)
                .update(query.getQueryObject(), docToPersist, true, false, WriteConcern.SAFE).getLastError().ok();
    }

    protected String updateContainerDoc(final Entity entity) {
        TenantContext.setIsSystemCall(false);
        String parentKey = entity.getEntityId();

        final Query query = Query.query(Criteria.where("_id").is(parentKey));

        // delete the embedded docs to update
        boolean removed = deleteContainerNonSubDocs(entity);

        // insert updated embedded docs
        if (removed) {
            return insertContainerDoc(query.getQueryObject(), entity);
        } else {
            return "";
        }
    }

    protected String insertContainerDoc(final DBObject query, final Entity entity) {
        TenantContext.setIsSystemCall(false);

        boolean persisted = true;

        final DBObject docToPersist = ContainerDocumentHelper.buildDocumentToPersist(containerDocumentHolder,
                entity, generatorStrategy, naturalKeyExtractor);
        ContainerDocument containerDocument = containerDocumentHolder.getContainerDocument(entity.getType());
        persisted &= mongoTemplate.getCollection(containerDocument.getCollectionToPersist())
                .update(query, docToPersist, true, false, WriteConcern.SAFE).getLastError().ok();

        String key = (String) query.get("_id");

        if (containerDocument.isContainerSubdoc()) {
            key = ContainerDocumentHelper.getContainerDocId(entity, generatorStrategy, naturalKeyExtractor);
            getLocation(entity.getType()).create(entity);

        }

        if (persisted) {
            return key;
        } else {
            return "";
        }
    }

    protected boolean deleteContainerDoc(final Entity entity) {
        if (entity == null) {
            return false;
        }
        return getLocation(entity.getType()).delete(entity);
    }

    private SubDocAccessor.Location getLocation(String type) {
        SubDocAccessor.Location location = null;

        if (locationMap.containsKey(type)) {
            location = locationMap.get(type);
        } else {
            ContainerDocument containerDocument = containerDocumentHolder.getContainerDocument(type);
            Map<String, String> parentToSubDocField = new HashMap<String, String>();
            for (String parentKey : containerDocument.getParentNaturalKeys()) {
                parentToSubDocField.put(parentKey, "body." + parentKey);
            }
            location = subDocAccessor.createLocation(containerDocument.getCollectionName(),
                    containerDocument.getCollectionToPersist(), parentToSubDocField,
                    containerDocument.getFieldToPersist());
            locationMap.put(type, location);
        }
        return location;
    }

}