All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.lightblue.crud.mongo.MongoCRUDController Maven / Gradle / Ivy

The newest version!
/*
 Copyright 2013 Red Hat, Inc. and/or its affiliates.

 This file is part of lightblue.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .
 */
package com.redhat.lightblue.crud.mongo;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mongodb.*;
import com.redhat.lightblue.config.ControllerConfiguration;
import com.redhat.lightblue.common.mongo.DBResolver;
import com.redhat.lightblue.common.mongo.MongoDataStore;
import com.redhat.lightblue.crud.*;
import com.redhat.lightblue.eval.FieldAccessRoleEvaluator;
import com.redhat.lightblue.eval.Projector;
import com.redhat.lightblue.eval.Updater;
import com.redhat.lightblue.interceptor.InterceptPoint;
import com.redhat.lightblue.metadata.*;
import com.redhat.lightblue.metadata.constraints.IdentityConstraint;
import com.redhat.lightblue.metadata.mongo.MongoMetadataConstants;
import com.redhat.lightblue.metadata.types.StringType;
import com.redhat.lightblue.query.*;
import com.redhat.lightblue.util.Error;
import com.redhat.lightblue.util.JsonDoc;
import com.redhat.lightblue.util.MutablePath;
import com.redhat.lightblue.util.Path;
import com.redhat.lightblue.extensions.ExtensionSupport;
import com.redhat.lightblue.extensions.Extension;
import com.redhat.lightblue.extensions.synch.LockingSupport;
import com.redhat.lightblue.extensions.valuegenerator.ValueGeneratorSupport;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class MongoCRUDController implements CRUDController, MetadataListener, ExtensionSupport {

    public static final String ID_STR = "_id";

    /**
     * Name of the property for the operation context that keeps the last saver
     * class instance used
     */
    public static final String PROP_SAVER = "MongoCRUDController:saver";

    /**
     * Name of the property for the operation context that keeps the last
     * updater class instance used
     */
    public static final String PROP_UPDATER = "MongoCRUDController:updater";

    /**
     * Name of the property for the operation context that keeps the last
     * deleter class instance used
     */
    public static final String PROP_DELETER = "MongoCRUDController:deleter";

    /**
     * Name of the property for the operation context that keeps the last finder
     * class instance used
     */
    public static final String PROP_FINDER = "MongoCRUDController:finder";

    public static final String OP_INSERT = "insert";
    public static final String OP_SAVE = "save";
    public static final String OP_FIND = "find";
    public static final String OP_UPDATE = "update";
    public static final String OP_DELETE = "delete";

    private static final Logger LOGGER = LoggerFactory.getLogger(MongoCRUDController.class);

    private static final Projection ID_PROJECTION = new FieldProjection(new Path(ID_STR), true, false);

    private final DBResolver dbResolver;
    private final ControllerConfiguration controllerCfg;

    public MongoCRUDController(ControllerConfiguration controllerCfg,DBResolver dbResolver) {
        this.dbResolver = dbResolver;
        this.controllerCfg=controllerCfg;
    }

    public DBResolver getDbResolver() {
        return dbResolver;
    }

    public ControllerConfiguration getControllerConfiguration() {
        return controllerCfg;
    }
    
    /**
     * Insertion operation for mongo
     */
    @Override
    public CRUDInsertionResponse insert(CRUDOperationContext ctx,
                                        Projection projection) {
        LOGGER.debug("insert() start");
        CRUDInsertionResponse response = new CRUDInsertionResponse();
        ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.PRE_CRUD_INSERT, ctx);
        int n = saveOrInsert(ctx, false, projection, OP_INSERT);
        response.setNumInserted(n);
        ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.POST_CRUD_INSERT, ctx);
        return response;
    }

    @Override
    public CRUDSaveResponse save(CRUDOperationContext ctx,
                                 boolean upsert,
                                 Projection projection) {
        LOGGER.debug("save() start");
        CRUDSaveResponse response = new CRUDSaveResponse();
        ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.PRE_CRUD_SAVE, ctx);
        int n = saveOrInsert(ctx, upsert, projection, OP_SAVE);
        response.setNumSaved(n);
        ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.POST_CRUD_SAVE, ctx);
        return response;
    }

    private int saveOrInsert(CRUDOperationContext ctx,
                             boolean upsert,
                             Projection projection,
                             String operation) {
        int ret = 0;
        List documents = ctx.getDocumentsWithoutErrors();
        if (documents == null || documents.isEmpty()) {
            return ret;
        }
        for (DocCtx doc : documents) {
            doc.setOriginalDocument(doc);
        }
        LOGGER.debug("saveOrInsert() start");
        Error.push(operation);
        Translator translator = new Translator(ctx, ctx.getFactory().getNodeFactory());
        try {
            FieldAccessRoleEvaluator roleEval = new FieldAccessRoleEvaluator(ctx.getEntityMetadata(ctx.getEntityName()),
                    ctx.getCallerRoles());
            LOGGER.debug("saveOrInsert: Translating docs");
            EntityMetadata md = ctx.getEntityMetadata(ctx.getEntityName());
            DBObject[] dbObjects = translator.toBson(documents);
            // dbObjects[i] is the translation of documents.get(i)
            if (dbObjects != null) {
                LOGGER.debug("saveOrInsert: {} docs translated to bson", dbObjects.length);

                MongoDataStore store = (MongoDataStore) md.getDataStore();
                DB db = dbResolver.get(store);
                DBCollection collection = db.getCollection(store.getCollectionName());

                Projection combinedProjection = Projection.add(projection, roleEval.getExcludedFields(FieldAccessRoleEvaluator.Operation.find));

                Projector projector;
                if (combinedProjection != null) {
                    projector = Projector.getInstance(combinedProjection, md);
                } else {
                    projector = null;
                }
                DocSaver saver = new BasicDocSaver(translator, roleEval);
                ctx.setProperty(PROP_SAVER, saver);
                for (int docIndex = 0; docIndex < dbObjects.length; docIndex++) {
                    DBObject dbObject = dbObjects[docIndex];
                    DocCtx inputDoc = documents.get(docIndex);
                    try {
                        saver.saveDoc(ctx, operation.equals(OP_INSERT) ? DocSaver.Op.insert : DocSaver.Op.save,
                                upsert, collection, md, dbObject, inputDoc);
                    } catch (Exception e) {
                        LOGGER.error("saveOrInsert failed: {}", e);
                        inputDoc.addError(analyzeException(e, operation, MongoCrudConstants.ERR_SAVE_ERROR, true));
                    }
                    if (projector != null) {
                        JsonDoc jsonDoc = translator.toJson(dbObject);
                        LOGGER.debug("Translated doc: {}", jsonDoc);
                        inputDoc.setOutputDocument(projector.project(jsonDoc, ctx.getFactory().getNodeFactory()));
                    } else {
                        inputDoc.setOutputDocument(new JsonDoc(new ObjectNode(ctx.getFactory().getNodeFactory())));
                    }
                    LOGGER.debug("projected doc: {}", inputDoc.getOutputDocument());
                    if (!inputDoc.hasErrors()) {
                        ret++;
                    }
                }
                ctx.getHookManager().queueHooks(ctx);
            }
        } catch (Error e) {
            ctx.addError(e);
        } catch (Exception e) {
            ctx.addError(analyzeException(e, CrudConstants.ERR_CRUD));
        } finally {
            Error.pop();
        }
        LOGGER.debug("saveOrInsert() end: {} docs requested, {} saved", documents.size(), ret);
        return ret;
    }

    @Override
    public CRUDUpdateResponse update(CRUDOperationContext ctx,
                                     QueryExpression query,
                                     UpdateExpression update,
                                     Projection projection) {
        LOGGER.debug("update start: q:{} u:{} p:{}", query, update, projection);
        Error.push(OP_UPDATE);
        CRUDUpdateResponse response = new CRUDUpdateResponse();
        Translator translator = new Translator(ctx, ctx.getFactory().getNodeFactory());
        try {
            if (query == null) {
                throw Error.get("update",MongoCrudConstants.ERR_NULL_QUERY,"");
            }
            ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.PRE_CRUD_UPDATE, ctx);
            EntityMetadata md = ctx.getEntityMetadata(ctx.getEntityName());
            if (md.getAccess().getUpdate().hasAccess(ctx.getCallerRoles())) {
                ConstraintValidator validator = ctx.getFactory().getConstraintValidator(md);
                LOGGER.debug("Translating query {}", query);
                DBObject mongoQuery = translator.translate(md, query);
                LOGGER.debug("Translated query {}", mongoQuery);
                FieldAccessRoleEvaluator roleEval = new FieldAccessRoleEvaluator(md, ctx.getCallerRoles());

                Projector projector;
                if (projection != null) {
                    Projection x = Projection.add(projection, roleEval.getExcludedFields(FieldAccessRoleEvaluator.Operation.find));
                    LOGGER.debug("Projection={}", x);
                    projector = Projector.getInstance(x, md);
                } else {
                    projector = null;
                }
                DB db = dbResolver.get((MongoDataStore) md.getDataStore());
                DBCollection coll = db.getCollection(((MongoDataStore) md.getDataStore()).getCollectionName());
                Projector errorProjector;
                if (projector == null) {
                    errorProjector = Projector.getInstance(ID_PROJECTION, md);
                } else {
                    errorProjector = projector;
                }

                // If there are any constraints for updated fields, or if we're updating arrays, we have to use iterate-update
                Updater updater = Updater.getInstance(ctx.getFactory().getNodeFactory(), md, update);

                DocUpdater docUpdater = new IterateAndUpdate(ctx.getFactory().getNodeFactory(), validator, roleEval, translator, updater,
                        projector, errorProjector);
                ctx.setProperty(PROP_UPDATER, docUpdater);
                docUpdater.update(ctx, coll, md, response, mongoQuery);
                ctx.getHookManager().queueHooks(ctx);
            } else {
                ctx.addError(Error.get(MongoCrudConstants.ERR_NO_ACCESS, "update:" + ctx.getEntityName()));
            }
        } catch (Error e) {
            ctx.addError(e);
        } catch (Exception e) {
            ctx.addError(analyzeException(e, CrudConstants.ERR_CRUD));
        } finally {
            Error.pop();
        }
        ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.POST_CRUD_UPDATE, ctx);
        LOGGER.debug("update end: updated: {}, failed: {}", response.getNumUpdated(), response.getNumFailed());
        return response;
    }

    @Override
    public CRUDDeleteResponse delete(CRUDOperationContext ctx,
                                     QueryExpression query) {
        LOGGER.debug("delete start: q:{}", query);
        Error.push(OP_DELETE);
        CRUDDeleteResponse response = new CRUDDeleteResponse();
        Translator translator = new Translator(ctx, ctx.getFactory().getNodeFactory());
        try {
            if (query == null) {
                throw Error.get("delete",MongoCrudConstants.ERR_NULL_QUERY,"");
            }
            ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.PRE_CRUD_DELETE, ctx);
            EntityMetadata md = ctx.getEntityMetadata(ctx.getEntityName());
            if (md.getAccess().getDelete().hasAccess(ctx.getCallerRoles())) {
                LOGGER.debug("Translating query {}", query);
                DBObject mongoQuery = translator.translate(md, query);
                LOGGER.debug("Translated query {}", mongoQuery);
                DB db = dbResolver.get((MongoDataStore) md.getDataStore());
                DBCollection coll = db.getCollection(((MongoDataStore) md.getDataStore()).getCollectionName());
                DocDeleter deleter = new IterateDeleter(translator);
                ctx.setProperty(PROP_DELETER, deleter);
                deleter.delete(ctx, coll, mongoQuery, response);
                ctx.getHookManager().queueHooks(ctx);
            } else {
                ctx.addError(Error.get(MongoCrudConstants.ERR_NO_ACCESS, "delete:" + ctx.getEntityName()));
            }
        } catch (Error e) {
            ctx.addError(e);
        } catch (Exception e) {
            ctx.addError(analyzeException(e, CrudConstants.ERR_CRUD));
        } finally {
            Error.pop();
        }
        ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.POST_CRUD_DELETE, ctx);
        LOGGER.debug("delete end: deleted: {}}", response.getNumDeleted());
        return response;
    }

    /**
     * Search implementation for mongo
     */
    @Override
    public CRUDFindResponse find(CRUDOperationContext ctx,
                                 QueryExpression query,
                                 Projection projection,
                                 Sort sort,
                                 Long from,
                                 Long to) {
        LOGGER.debug("find start: q:{} p:{} sort:{} from:{} to:{}", query, projection, sort, from, to);
        Error.push(OP_FIND);
        CRUDFindResponse response = new CRUDFindResponse();
        Translator translator = new Translator(ctx, ctx.getFactory().getNodeFactory());
        try {
            if (projection == null) {
                throw Error.get("find",MongoCrudConstants.ERR_NULL_PROJECTION,"");
            }
            ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.PRE_CRUD_FIND, ctx);
            EntityMetadata md = ctx.getEntityMetadata(ctx.getEntityName());
            if (md.getAccess().getFind().hasAccess(ctx.getCallerRoles())) {
                FieldAccessRoleEvaluator roleEval = new FieldAccessRoleEvaluator(md, ctx.getCallerRoles());
                LOGGER.debug("Translating query {}", query);
                DBObject mongoQuery = query==null?null:translator.translate(md, query);
                LOGGER.debug("Translated query {}", mongoQuery);
                DBObject mongoSort;
                if (sort != null) {
                    LOGGER.debug("Translating sort {}", sort);
                    mongoSort = translator.translate(sort);
                    LOGGER.debug("Translated sort {}", mongoSort);
                } else {
                    mongoSort = null;
                }
                DBObject mongoProjection = translator.translateProjection(md, getProjectionFields(projection, md), query, sort);
                LOGGER.debug("Translated projection {}", mongoProjection);
                DB db = dbResolver.get((MongoDataStore) md.getDataStore());
                DBCollection coll = db.getCollection(((MongoDataStore) md.getDataStore()).getCollectionName());
                LOGGER.debug("Retrieve db collection:" + coll);
                DocFinder finder = new BasicDocFinder(translator);
                ctx.setProperty(PROP_FINDER, finder);
                response.setSize(finder.find(ctx, coll, mongoQuery, mongoProjection, mongoSort, from, to));
                // Project results
                Projector projector = Projector.getInstance(Projection.add(projection, roleEval.getExcludedFields(FieldAccessRoleEvaluator.Operation.find)), md);
                for (DocCtx document : ctx.getDocuments()) {
                    document.setOutputDocument(projector.project(document, ctx.getFactory().getNodeFactory()));
                }
                ctx.getHookManager().queueHooks(ctx);
            } else {
                ctx.addError(Error.get(MongoCrudConstants.ERR_NO_ACCESS, "find:" + ctx.getEntityName()));
            }
        } catch (Error e) {
            ctx.addError(e);
        } catch (Exception e) {
            ctx.addError(analyzeException(e, CrudConstants.ERR_CRUD));
        } finally {
            Error.pop();
        }
        ctx.getFactory().getInterceptors().callInterceptors(InterceptPoint.POST_CRUD_FIND, ctx);
        LOGGER.debug("find end: query: {} results: {}", response.getSize());
        return response;
    }

    @Override
    public void updatePredefinedFields(CRUDOperationContext ctx, JsonDoc doc) {
        // If it is a save operation, we rely on _id being passed by client, so we don't auto-generate that
        // If not, it needs to be auto-generated
        if(ctx.getCRUDOperation()!=CRUDOperation.SAVE) {
            JsonNode idNode = doc.get(Translator.ID_PATH);
            if (idNode == null || idNode instanceof NullNode) {
                doc.modify(Translator.ID_PATH,
                           ctx.getFactory().getNodeFactory().textNode(ObjectId.get().toString()),
                           false);
            }
        }
    }

    @Override
    public  E getExtensionInstance(Class extensionClass) {
        if(extensionClass.equals(LockingSupport.class))
            return (E)new MongoLockingSupport(this);
        else if(extensionClass.equals(ValueGeneratorSupport.class))
            return (E)new MongoSequenceSupport(this);
        return null;
    }
    
    @Override
    public MetadataListener getMetadataListener() {
        return this;
    }

    @Override
    public void afterUpdateEntityInfo(Metadata md, EntityInfo ei, boolean newEntity) {
        createUpdateEntityInfoIndexes(ei);
    }

    @Override
    public void beforeUpdateEntityInfo(Metadata md, EntityInfo ei, boolean newEntity) {
        validateIndexFields(ei);
        ensureIdIndex(ei);
    }

    @Override
    public void afterCreateNewSchema(Metadata md, EntityMetadata emd) {
    }

    @Override
    public void beforeCreateNewSchema(Metadata md, EntityMetadata emd) {
        validateIndexFields(emd.getEntityInfo());
        ensureIdField(emd);
    }

    private Path translateIndexPath(Path p) {
        MutablePath newPath = new MutablePath();
        int n = p.numSegments();
        for (int i = 0; i < n; i++) {
            String x = p.head(i);
            if (!x.equals(Path.ANY)) {
                if (p.isIndex(i)) {
                    throw Error.get(MongoCrudConstants.ERR_INVALID_INDEX_FIELD, p.toString());
                }
                newPath.push(x);
            }
        }
        return newPath.immutableCopy();
    }

    private void validateIndexFields(EntityInfo ei) {
        for (Index ix : ei.getIndexes().getIndexes()) {
            List fields = ix.getFields();
            List newFields = null;
            boolean copied = false;
            int i = 0;
            for (SortKey key : fields) {
                Path p = key.getField();
                Path newPath = translateIndexPath(p);
                if (!p.equals(newPath)) {
                    SortKey newKey = new SortKey(newPath, key.isDesc());
                    if (!copied) {
                        newFields = new ArrayList<>();
                        newFields.addAll(fields);
                        copied = true;
                    }
                    newFields.set(i, newKey);
                }
            }
            if (copied) {
                ix.setFields(newFields);
                LOGGER.debug("Index rewritten as {}", ix);
            }
        }
    }

    private void ensureIdField(EntityMetadata md) {
        ensureIdField(md.getEntitySchema());
    }

    private void ensureIdField(EntitySchema schema) {
        LOGGER.debug("ensureIdField: begin");

        SimpleField idField;

        FieldTreeNode field;
        try {
            field = schema.resolve(Translator.ID_PATH);
        } catch (Error e) {
            field = null;
        }
        if (field == null) {
            LOGGER.debug("Adding _id field");
            idField = new SimpleField(ID_STR, StringType.TYPE);
            schema.getFields().addNew(idField);
        } else {
            if (field instanceof SimpleField) {
                idField = (SimpleField) field;
            } else {
                throw Error.get(MongoMetadataConstants.ERR_INVALID_ID);
            }
        }

        LOGGER.debug("ensureIdField: end");
    }

    private void ensureIdIndex(EntityInfo ei) {
        LOGGER.debug("ensureIdIndex: begin");

        Indexes indexes = ei.getIndexes();
        // We are looking for a unique index on _id
        boolean found = false;
        for (Index ix : indexes.getIndexes()) {
            List fields = ix.getFields();
            if (fields.size() == 1 && fields.get(0).getField().equals(Translator.ID_PATH)
                    && ix.isUnique()) {
                found = true;
                break;
            }
        }
        if (!found) {
            LOGGER.debug("Adding _id index");
            Index idIndex = new Index();
            idIndex.setUnique(true);
            List fields = new ArrayList<>();
            fields.add(new SortKey(Translator.ID_PATH, false));
            idIndex.setFields(fields);
            List ix = indexes.getIndexes();
            ix.add(idIndex);
            indexes.setIndexes(ix);
        } else {
            LOGGER.debug("_id index exists");
        }
        LOGGER.debug("ensureIdIndex: end");
    }

    private void createUpdateEntityInfoIndexes(EntityInfo ei) {
        LOGGER.debug("createUpdateEntityInfoIndexes: begin");

        Indexes indexes = ei.getIndexes();

        MongoDataStore ds = (MongoDataStore) ei.getDataStore();
        DB entityDB = dbResolver.get(ds);
        DBCollection entityCollection = entityDB.getCollection(ds.getCollectionName());
        Error.push("createUpdateIndex");
        try {
            List existingIndexes = entityCollection.getIndexInfo();
            Set deleteIndexes = new HashSet<>();
            deleteIndexes.addAll(existingIndexes);
            LOGGER.debug("Existing indexes: {}", existingIndexes);
            for (Index index : indexes.getIndexes()) {
                boolean createIx = !isIdIndex(index);
                if (createIx) {
                    LOGGER.debug("Processing index {}", index);
                    for (DBObject existingIndex : existingIndexes) {
                        if (indexFieldsMatch(index, existingIndex)
                                && indexOptionsMatch(index, existingIndex)) {
                            LOGGER.debug("Same index exists, not creating");
                            createIx = false;
                            deleteIndexes.remove(existingIndex);
                            break;
                        }
                    }
                }

                if (createIx) {
                    for (DBObject existingIndex : existingIndexes) {
                        if (indexFieldsMatch(index, existingIndex)
                                && !indexOptionsMatch(index, existingIndex)) {
                            LOGGER.debug("Same index exists with different options, dropping index:{}", existingIndex);
                            // Changing index options, drop the index using its name, recreate with new options
                            entityCollection.dropIndex(existingIndex.get("name").toString());
                            deleteIndexes.remove(existingIndex);
                        }
                    }
                }

                if (createIx) {
                    DBObject newIndex = new BasicDBObject();
                    for (SortKey p : index.getFields()) {
                        newIndex.put(p.getField().toString(), p.isDesc() ? -1 : 1);
                    }
                    BasicDBObject options = new BasicDBObject("unique", index.isUnique());
                    // if index is unique also make it a sparse index, so we can have non-required unique fields
                    options.append("sparse", index.isUnique());
                    if (index.getName() != null && index.getName().trim().length() > 0) {
                        options.append("name", index.getName().trim());
                    }
                    options.append("background", true);
                    LOGGER.debug("Creating index {} with options {}", newIndex, options);
                    entityCollection.createIndex(newIndex, options);
                }
            }

            // for any indexes that remain in deleteIndexes set, delete them (except _id index!)
            for (DBObject deleteIndex : deleteIndexes) {
                if (((BasicDBObject) deleteIndex.get("key")).size() != 1
                        || !((BasicDBObject) deleteIndex.get("key")).containsField(ID_STR)) {
                    // it's a multi-key index or the one key is not _id, delete it
                    entityCollection.dropIndex(deleteIndex.get("name").toString());
                }
            }
        } catch (MongoException me) {
            throw Error.get(MongoCrudConstants.ERR_ENTITY_INDEX_NOT_CREATED, me.getMessage());
        } catch (Error e) {
            // rethrow lightblue error
            throw e;
        } catch (Exception e) {
            throw analyzeException(e, MetadataConstants.ERR_ILL_FORMED_METADATA);
        } finally {
            Error.pop();
        }

        LOGGER.debug("createUpdateEntityInfoIndexes: end");
    }

    private boolean isIdIndex(Index index) {
        List fields = index.getFields();
        return fields.size() == 1
                && fields.get(0).getField().equals(Translator.ID_PATH);
    }

    private boolean compareSortKeys(SortKey sortKey, String fieldName, Object dir) {
        if (sortKey.getField().toString().equals(fieldName)) {
            int direction = ((Number) dir).intValue();
            return sortKey.isDesc() == (direction < 0);
        }
        return false;
    }

    protected boolean indexFieldsMatch(Index index, DBObject existingIndex) {
        BasicDBObject keys = (BasicDBObject) existingIndex.get("key");
        if (keys != null) {
            List fields = index.getFields();
            if (keys.size() == fields.size()) {
                Iterator sortKeyItr = fields.iterator();
                for (Map.Entry dbKeyEntry : keys.entrySet()) {
                    SortKey sortKey = sortKeyItr.next();
                    if (!compareSortKeys(sortKey, dbKeyEntry.getKey(), dbKeyEntry.getValue())) {
                        return false;
                    }
                }
                return true;
            }
        }
        return false;
    }

    private boolean indexOptionsMatch(Index index, DBObject existingIndex) {
        Boolean unique = (Boolean) existingIndex.get("unique");
        if (unique != null) {
            if ((unique && index.isUnique())
                    || (!unique && !index.isUnique())) {
                return true;
            }
        } else {
            if (!index.isUnique()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns a projection containing the requested projection, all identity
     * fields, and the objectType field
     */
    private Projection getProjectionFields(Projection requestedProjection,
                                           EntityMetadata md) {
        Field[] identityFields = md.getEntitySchema().getIdentityFields();
        List projectFields = new ArrayList<>(identityFields == null ? 1 : identityFields.length + 1);
        if (requestedProjection instanceof ProjectionList) {
            projectFields.addAll(((ProjectionList) requestedProjection).getItems());
        } else if (requestedProjection != null) {
            projectFields.add(requestedProjection);
        }
        if (identityFields != null) {
            for (Field x : identityFields) {
                projectFields.add(new FieldProjection(x.getFullPath(), true, false));
            }
        }
        projectFields.add(new FieldProjection(Translator.OBJECT_TYPE, true, false));

        return new ProjectionList(projectFields);
    }

    private Error analyzeException(Exception e, final String otherwise) {
        return analyzeException(e, otherwise, null, false);
    }

    private Error analyzeException(Exception e, final String otherwise, final String msg, boolean specialHandling) {
        if(e instanceof Error)
            return (Error)e;

        if (e instanceof CommandFailureException) {
            CommandFailureException ce = (CommandFailureException) e;
            // give a better Error.code in case auth failed which is represented in MongoDB by code == 18
            if (ce.getCode() == 18) {
                return Error.get(CrudConstants.ERR_AUTH_FAILED, e.getMessage());
            } else {
                return Error.get(CrudConstants.ERR_DATASOURCE_UNKNOWN, e.getMessage());
            }
        } else if (e instanceof MongoClientException) {
            if (e instanceof MongoTimeoutException) {
                return Error.get(CrudConstants.ERR_DATASOURCE_TIMEOUT, e.getMessage());
            } else {
                return Error.get(CrudConstants.ERR_DATASOURCE_UNKNOWN, e.getMessage());
            }
        } else if (e instanceof MongoException) {
            if (e instanceof MongoExecutionTimeoutException) {
                return Error.get(CrudConstants.ERR_DATASOURCE_TIMEOUT, e.getMessage());
            } else {
                return Error.get(CrudConstants.ERR_DATASOURCE_UNKNOWN, e.getMessage());
            }
        } else {
            if (msg == null) {
                return Error.get(otherwise, e.getMessage());
            } else {
                if(specialHandling){
                    return Error.get(otherwise, msg, e);
                }else {
                    return Error.get(otherwise, msg);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy