fr.gouv.vitam.mdbes.MongoDbAccess.java Source code

Java tutorial

Introduction

Here is the source code for fr.gouv.vitam.mdbes.MongoDbAccess.java

Source

/**
 * This file is part of POC MongoDB ElasticSearch Project.
 *
 * Copyright 2009, Frederic Bregier, and individual contributors by the @author
 * tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 * All POC MongoDB ElasticSearch Project 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.
 *
 * POC MongoDB ElasticSearch 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 POC MongoDB ElasticSearch . If not, see <http://www.gnu.org/licenses/>.
 */
package fr.gouv.vitam.mdbes;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bson.BSONObject;
import org.elasticsearch.action.ListenableActionFuture;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.QueryBuilder;

import com.fasterxml.jackson.databind.JsonNode;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;

import fr.gouv.vitam.query.GlobalDatas;
import fr.gouv.vitam.utils.FileUtil;
import fr.gouv.vitam.utils.UUID;
import fr.gouv.vitam.utils.exception.InvalidUuidOperationException;
import fr.gouv.vitam.utils.logging.VitamLogger;
import fr.gouv.vitam.utils.logging.VitamLoggerFactory;

/**
 * MongoDb Access base class
 *
 * @author "Frederic Bregier"
 *
 */
public class MongoDbAccess {
    private static final VitamLogger LOGGER = VitamLoggerFactory.getInstance(MongoDbAccess.class);

    private DB db = null;
    private DB dbadmin = null;
    private VitamCollection[] collections = null;
    protected VitamCollection domains = null;
    protected VitamCollection daips = null;
    protected VitamCollection paips = null;
    protected VitamCollection saips = null;
    protected VitamCollection duarefs = null;
    protected VitamCollection requests = null;
    private ElasticSearchAccess es = null;
    private ListenableActionFuture<BulkResponse> bulkResponseListener = null;
    protected RedisAccess ra = null;
    protected MessageDigest md;

    private static enum LinkType {
        /**
         * Link N-N
         */
        SymLinkNN,
        /**
         * Link N-
         */
        AsymLinkN,
        /**
         * Link 1-N
         */
        SymLink1N,
        /**
         * Link N-1
         */
        SymLinkN1,
        /**
         * Link 1-1
         */
        SymLink11,
        /**
         * Link 1-
         */
        AsymLink1,
        /**
         * False Link (N)-N
         */
        SymLink_N_N
    }

    protected static enum VitamCollections {
        Cdomain(Domain.class), Cdaip(DAip.class), Cpaip(PAip.class), Csaip(SAip.class), Cdua(
                DuaRef.class), Crequests(ResultMongodb.class);

        @SuppressWarnings("rawtypes")
        private Class clasz;
        private String name;
        private int rank;
        private DBCollection collection = null;

        @SuppressWarnings("rawtypes")
        private VitamCollections(final Class clasz) {
            this.clasz = clasz;
            name = clasz.getSimpleName();
            rank = ordinal();
        }

        protected String getName() {
            return name;
        }

        protected DBCollection getCollection() {
            return collection;
        }
    }

    protected static class VitamCollection {

        private final VitamCollections coll;
        protected DBCollection collection;

        protected VitamCollection(final DB db, final VitamCollections coll, final boolean recreate) {
            this.coll = coll;
            collection = db.getCollection(coll.name);
            collection.setObjectClass(coll.clasz);
            if (recreate) {
                // this.collection.dropIndexes();
                collection.createIndex(new BasicDBObject(VitamType.ID, "hashed"));
                // db.command(new BasicDBObject("collMod", coll.name).append("usePowerOf2Sizes", true));
            }
            this.coll.collection = collection;
        }

        protected DBCollection getCollection() {
            return collection;
        }
    }

    // Structure Access
    protected static enum VitamLinks {
        /**
         * Domain to DAip N-N link. This link is symmetric.
         */
        Domain2DAip(VitamCollections.Cdomain, LinkType.SymLinkNN, "_daips", VitamCollections.Cdaip, "_doms"),
        /**
         * Daip to Daip N-N link but asymmetric where only childs reference their fathers (so only "_up" link)
         */
        DAip2DAip(VitamCollections.Cdaip, LinkType.SymLink_N_N, "_down_unused", VitamCollections.Cdaip, "_up"),
        /**
         * Daip to Paip 1-N link. This link is symmetric.
         */
        DAip2PAip(VitamCollections.Cdaip, LinkType.SymLink1N, "_paip", VitamCollections.Cpaip, "_up"),
        /**
         * Paip to Saip 1-1 link. Ths link is symmetric.
         */
        PAip2SAip(VitamCollections.Cpaip, LinkType.SymLink11, "_saip", VitamCollections.Csaip, "_paip"),
        /**
         * Daip to Dua 1-N link but asymmetric where only Daip reference its Dua (so only "_dua" link)
         */
        DAip2Dua(VitamCollections.Cdaip, LinkType.AsymLink1, "_dua", VitamCollections.Cdua),
        /**
         * Paip to Dua N-N link but asymmetric where only Paip reference its Dua(s) (so only "_duas" link)
         */
        PAip2Dua(VitamCollections.Cpaip, LinkType.AsymLinkN, "_duas", VitamCollections.Cdua);

        protected VitamCollections col1;
        protected LinkType type;
        protected String field1to2;
        protected VitamCollections col2;
        protected String field2to1;

        /**
         * @param col1
         * @param type
         * @param field1to2
         * @param col2
         * @param field2to1
         */
        private VitamLinks(final VitamCollections col1, final LinkType type, final String field1to2,
                final VitamCollections col2, final String field2to1) {
            this.col1 = col1;
            this.type = type;
            this.field1to2 = field1to2;
            this.col2 = col2;
            this.field2to1 = field2to1;
        }

        /**
         * @param clasz1
         * @param type
         * @param field1to2
         * @param clasz2
         */
        private VitamLinks(final VitamCollections col1, final LinkType type, final String field1to2,
                final VitamCollections col2) {
            this.col1 = col1;
            this.type = type;
            this.field1to2 = field1to2;
            this.col2 = col2;
        }

    }

    /**
     *
     * @param mongoClient
     *            the current valid MongoClient to use as connector to the database
     * @param dbname
     *            the MongoDB database name
     * @param esname
     *            the ElasticSearch name
     * @param unicast
     *            the unicast addresses for ElasticSearch
     * @param recreate
     *            shall we recreate the index
     * @throws InvalidUuidOperationException
     */
    @SuppressWarnings("unused")
    public MongoDbAccess(final MongoClient mongoClient, final String dbname, final String esname,
            final String unicast, final boolean recreate) throws InvalidUuidOperationException {
        db = mongoClient.getDB(dbname);
        dbadmin = mongoClient.getDB("admin");
        // Authenticate - optional
        // boolean auth = db.authenticate("foo", "bar");

        collections = new VitamCollection[VitamCollections.values().length];
        // get a collection object to work with
        domains = collections[VitamCollections.Cdomain.rank] = new VitamCollection(db, VitamCollections.Cdomain,
                recreate);
        daips = collections[VitamCollections.Cdaip.rank] = new VitamCollection(db, VitamCollections.Cdaip,
                recreate);
        paips = collections[VitamCollections.Cpaip.rank] = new VitamCollection(db, VitamCollections.Cpaip,
                recreate);
        saips = collections[VitamCollections.Csaip.rank] = new VitamCollection(db, VitamCollections.Csaip,
                recreate);
        duarefs = collections[VitamCollections.Cdua.rank] = new VitamCollection(db, VitamCollections.Cdua,
                recreate);
        if (GlobalDatas.USELRUCACHE || GlobalDatas.USEREDIS) {
            requests = null;
            collections[VitamCollections.Crequests.rank] = null;
        } else {
            requests = collections[VitamCollections.Crequests.rank] = new VitamCollection(db,
                    VitamCollections.Crequests, recreate);
        }
        final DBCursor cursor = domains.collection.find();
        for (final DBObject dbObject : cursor) {
            final Domain dom = (Domain) dbObject;
            dom.setRoot();
        }
        // elasticsearch index
        LOGGER.info("ES on cluster name: " + esname + ":" + unicast);
        es = new ElasticSearchAccess(esname, unicast, GlobalDatas.localNetworkAddress);
        try {
            md = MessageDigest.getInstance("SHA-512");
        } catch (NoSuchAlgorithmException e) {
            LOGGER.error(e);
        }
        if (GlobalDatas.USEREDIS) {
            ra = new RedisAccess(unicast, 20);
        }
    }

    /**
     * 
     * @return the ES Cluster Name
     */
    public String getEsClusterName() {
        return es.getClusterName();
    }

    /**
     * 
     * @param tohash
     * @return the corresponding digest as a "false" String
     */
    public final String createDigest(final String tohash) {
        synchronized (md) {
            md.update(tohash.getBytes(FileUtil.UTF8));
            return UUID.toHex(md.digest());
        }
    }

    /**
     * Drop all data and index from MongoDB and ElasticSearch
     *
     * @param model
     */
    public final void reset(final String model) {
        for (int i = 0; i < collections.length; i++) {
            if (collections[i] != null && collections[i].collection != null) {
                collections[i].collection.drop();
            }
        }
        es.deleteIndex(GlobalDatas.INDEXNAME);
        es.addIndex(GlobalDatas.INDEXNAME, model);
        ensureIndex();
    }

    /**
     * Update the Index for a new model
     *
     * @param model
     */
    public void updateEsIndex(final String model) {
        es.addIndex(GlobalDatas.INDEXNAME, model);
    }

    /**
     * Close database access (ElasticSearch, Couchbase, Redis, ...)
     */
    public final void close() {
        es.close();
        if (ra != null) {
            ra.close();
        }
    }

    /**
     * To be called once only when closing the application
     */
    public final void closeFinal() {
        if (ra != null) {
            ra.finalClose();
        }
    }

    /**
     * Ensure that all MongoDB database schema are indexed
     */
    @SuppressWarnings("unused")
    public void ensureIndex() {
        for (int i = 0; i < collections.length; i++) {
            if (collections[i] != null && collections[i].collection != null) {
                collections[i].collection.createIndex(new BasicDBObject(VitamType.ID, "hashed"));
            }
        }
        Domain.addIndexes(this);
        DAip.addIndexes(this);
        PAip.addIndexes(this);
        SAip.addIndexes(this);
        DuaRef.addIndexes(this);
        if (!(GlobalDatas.USELRUCACHE || GlobalDatas.USEREDIS)) {
            ResultMongodb.addIndexes(this);
        }
    }

    /**
     * Remove temporarily the MongoDB Index (import optimization?)
     */
    public void removeIndexBeforeImport() {
        try {
            daips.collection.dropIndex(new BasicDBObject(VitamLinks.DAip2DAip.field2to1, 1));
            daips.collection.dropIndex(new BasicDBObject(VitamLinks.Domain2DAip.field2to1, 1));
            daips.collection.dropIndex(new BasicDBObject(DAip.DAIPDEPTHS, 1));
        } catch (final Exception e) {
            LOGGER.error("Error while removing indexes before import", e);
        }
    }

    /**
     * Reset MongoDB Index (import optimization?)
     */
    public void resetIndexAfterImport() {
        LOGGER.info("Rebuild indexes");
        daips.collection.createIndex(new BasicDBObject(VitamLinks.DAip2DAip.field2to1, 1));
        daips.collection.createIndex(new BasicDBObject(VitamLinks.Domain2DAip.field2to1, 1));
        daips.collection.createIndex(new BasicDBObject(DAip.DAIPDEPTHS, 1));
    }

    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        // get a list of the collections in this database and print them out
        final Set<String> collectionNames = db.getCollectionNames();
        for (final String s : collectionNames) {
            builder.append(s);
            builder.append('\n');
        }
        for (final VitamCollection coll : collections) {
            if (coll != null && coll.collection != null) {
                final List<DBObject> list = coll.collection.getIndexInfo();
                for (final DBObject dbObject : list) {
                    builder.append(coll.coll.name);
                    builder.append(' ');
                    builder.append(dbObject);
                    builder.append('\n');
                }
            }
        }
        return builder.toString();
    }

    /**
     *
     * @return the current number of DAip
     */
    public long getDaipSize() {
        return daips.collection.count();
    }

    /**
     *
     * @return the current number of PAip
     */
    public long getPaipSize() {
        return paips.collection.count();
    }

    /**
     * 
     * @return the size of the Result Cache
     */
    public long getCacheSize() {
        if (GlobalDatas.USELRUCACHE) {
            return ResultLRU.count();
        } else if (GlobalDatas.USEREDIS) {
            return ra.getCount();
        } else {
            return requests.collection.count();
        }
    }

    /**
     * Force flush on disk (MongoDB): should not be used
     */
    protected void flushOnDisk() {
        dbadmin.command(new BasicDBObject("fsync", 1).append("async", true));
    }

    /**
     *
     * @param collection
     * @param ref
     * @return a VitamType generic object from ID ref value
     */
    public final VitamType loadFromObjectId(final VitamCollection collection, final String ref) {
        return (VitamType) collection.collection.findOne(ref);
    }

    /**
     * Load a BSONObject into VitamType
     *
     * @param obj
     * @param coll
     * @return the VitamType casted object
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public final VitamType loadFromBSONObject(final BSONObject obj, final VitamCollections coll)
            throws InstantiationException, IllegalAccessException {
        final VitamType vt = (VitamType) coll.clasz.newInstance();
        vt.putAll(obj);
        vt.getAfterLoad();
        return vt;
    }

    /**
     *
     * @param col
     * @param collection
     * @param field
     * @param ref
     * @return the VitamType casted object
     */
    public final VitamType fineOne(final VitamCollections col, final String field, final String ref) {
        final BasicDBObject obj = new BasicDBObject(field, ref);
        final VitamType vitobj = (VitamType) col.collection.findOne(obj);
        if (vitobj == null) {
            return null;
        } else {
            vitobj.getAfterLoad();
        }
        return vitobj;
    }

    /**
     * Find the corresponding id in col collection if it exists
     *
     * @param col
     * @param id
     * @return the VitamType casted object
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public final VitamType findOne(final VitamCollections col, final String id)
            throws InstantiationException, IllegalAccessException {
        if (id == null || id.length() == 0) {
            return null;
        }
        final VitamType vitobj = (VitamType) col.collection.findOne(id);
        if (vitobj == null) {
            return null;
        } else {
            vitobj.getAfterLoad();
        }
        return vitobj;
    }

    private static BasicDBObject IDONLY = new BasicDBObject(VitamType.ID, 1);

    /**
     *
     * @param col
     * @param id
     * @return True if one VitamType object exists with this id
     */
    public final boolean exists(final VitamCollections col, final String id) {
        if (id == null || id.length() == 0) {
            return false;
        }
        if (col == VitamCollections.Crequests) {
            if (GlobalDatas.USELRUCACHE) {
                return ResultLRU.exists(id);
            } else if (GlobalDatas.USEREDIS) {
                return ra.exists(id);
            } else {
                String nid = createDigest(id);
                return col.collection.findOne(nid, IDONLY) != null;
            }
        }
        return col.collection.findOne(id, IDONLY) != null;
    }

    /**
    *
    * @param id
    * @return the ResultInterface if any (null else)
    */
    public final ResultInterface load(final String id) {
        if (id == null || id.length() == 0) {
            return null;
        }
        if (GlobalDatas.USELRUCACHE) {
            // LRU Cache
            return ResultLRU.LRU_ResultCached.get(id);
        } else if (GlobalDatas.USEREDIS) {
            JsonNode vt = ra.getFromId(id);
            if (vt != null) {
                ResultRedis ri = (ResultRedis) createOneResult();
                ri.setId(this, id);
                ri.loadFromJson(vt);
                return ri;
            }
            return null;
        } else {
            String nid = createDigest(id);
            ResultMongodb rm = (ResultMongodb) requests.collection.findOne(nid);
            if (rm != null) {
                rm.getAfterLoad();
                rm.loaded = true;
            }
            return rm;
        }
    }

    /**
    *
    * @param id (possibly the modified id already)
    * @return the ResultInterface if any (null else)
    */
    public final ResultInterface reload(final String id) {
        if (id == null || id.length() == 0) {
            return null;
        }
        if (GlobalDatas.USELRUCACHE) {
            // LRU Cache
            return ResultLRU.LRU_ResultCached.get(id);
        } else if (GlobalDatas.USEREDIS) {
            JsonNode vt = ra.getFromId(id);
            if (vt != null) {
                ResultRedis ri = (ResultRedis) createOneResult();
                ri.setId(this, id);
                ri.loadFromJson(vt);
                return ri;
            }
            return null;
        } else {
            ResultMongodb rm = (ResultMongodb) requests.collection.findOne(id);
            if (rm != null) {
                rm.getAfterLoad();
                rm.loaded = true;
            }
            return rm;
        }
    }

    /**
     *
     * @param collection
     *            domain of request
     * @param condition
     *            where condition
     * @param idProjection
     *            select condition
     * @return the DbCursor on the find request based on the given collection
     */
    public final DBCursor find(final VitamCollection collection, final BasicDBObject condition,
            final BasicDBObject idProjection) {
        return collection.collection.find(condition, idProjection);
    }

    /**
     *
     * @param indexName
     * @param type
     * @param currentNodes
     *            current parent nodes
     * @param subdepth the relative depth
     * @param condition
     * @param filterCond
     * @param useStart True if currentNodes are final ids subsets (not parents)
     * @return the ResultCached associated with this request. 
     *         Note that the exact depth is not checked, so it must be checked
     *         after (using checkAncestor method)
     */
    public final ResultInterface getSubDepth(final String indexName, final String type,
            final Collection<String> currentNodes, final int subdepth, final QueryBuilder condition,
            final FilterBuilder filterCond, final boolean useStart) {
        if (useStart) {
            return es.getSubDepthStart(indexName, type, currentNodes.toArray(new String[0]), subdepth, condition,
                    filterCond);
        } else {
            return es.getSubDepth(indexName, type, currentNodes.toArray(new String[0]), subdepth, condition,
                    filterCond);
        }
    }

    /**
     *
     * @param indexName
     * @param type
     * @param subset subset of valid nodes
     * @param condition
     * @param filterCond
     * @return the ResultCached associated with this request
     */
    public final ResultInterface getNegativeSubDepth(final String indexName, final String type,
            final Collection<String> subset, final QueryBuilder condition, final FilterBuilder filterCond) {
        return es.getNegativeSubDepth(indexName, type, subset.toArray(new String[0]), condition, filterCond);
    }

    /**
     * 
     * @param model
     * @param id
     * @param json
     * @return True if inserted in ES
     */
    public final boolean addEsEntryIndex(final String model, String id, String json) {
        return es.addEntryIndex(GlobalDatas.INDEXNAME, model, id, json);
    }

    /**
      * Add indexes to ES model
      *
      * @param indexes
      * @param model
      */
    public final void addEsEntryIndex(final Map<String, String> indexes, final String model) {
        addEsEntryIndex(GlobalDatas.BLOCKING, indexes, model);
    }

    private final void checkPreviousBulkEs() {
        synchronized (this) {
            if (bulkResponseListener != null) {
                BulkResponse response = bulkResponseListener.actionGet();
                if (response.hasFailures()) {
                    LOGGER.error("ES previous insert in error: " + response.buildFailureMessage());
                }
                bulkResponseListener = null;
            }
        }
    }

    /**
     * Add indexes to ES model
     *
     * @param blocking
     * @param indexes
     * @param model
     * @return True if done (and if blocking)
     */
    public final boolean addEsEntryIndex(final boolean blocking, final Map<String, String> indexes,
            final String model) {
        checkPreviousBulkEs();
        if (blocking) {
            return es.addEntryIndexesBlocking(GlobalDatas.INDEXNAME, model, indexes);
        } else {
            synchronized (this) {
                bulkResponseListener = es.addEntryIndexes(GlobalDatas.INDEXNAME, model, indexes);
            }
            return true;
        }
    }

    /**
     * Add a Link according to relation defined, where the relation is defined in obj1->obj2 way by default (even if symmetric)
     *
     * @param obj1
     * @param relation
     * @param obj2
     * @return a {@link DBObject} that hold a possible update part (may be null)
     */
    protected final DBObject addLink(final VitamType obj1, final VitamLinks relation, final VitamType obj2) {
        switch (relation.type) {
        case AsymLink1:
            MongoDbAccess.addAsymmetricLink(obj1, relation.field1to2, obj2);
            break;
        case SymLink11:
            MongoDbAccess.addAsymmetricLink(obj1, relation.field1to2, obj2);
            return MongoDbAccess.addAsymmetricLinkUpdate(obj2, relation.field2to1, obj1);
        case AsymLinkN:
            MongoDbAccess.addAsymmetricLinkset(obj1, relation.field1to2, obj2, false);
            break;
        case SymLink1N:
            return MongoDbAccess.addSymmetricLink(obj1, relation.field1to2, obj2, relation.field2to1);
        case SymLinkN1:
            return MongoDbAccess.addReverseSymmetricLink(obj1, relation.field1to2, obj2, relation.field2to1);
        case SymLinkNN:
            return MongoDbAccess.addSymmetricLinkset(obj1, relation.field1to2, obj2, relation.field2to1);
        case SymLink_N_N:
            return addAsymmetricLinkset(obj2, relation.field2to1, obj1, true);
        default:
            break;
        }
        return null;
    }

    /**
     * Update the link
     *
     * @param obj1
     * @param vtReloaded
     * @param relation
     * @param src
     * @return the update part
     */
    protected final BasicDBObject updateLink(final VitamType obj1, final VitamType vtReloaded,
            final VitamLinks relation, final boolean src) {
        // DBCollection coll = (src ? relation.col1.collection : relation.col2.collection);
        final String fieldname = (src ? relation.field1to2 : relation.field2to1);
        // VitamType vt = (VitamType) coll.findOne(new BasicDBObject("_id", obj1.get("_id")));
        if (vtReloaded != null) {
            String srcOid = (String) vtReloaded.remove(fieldname);
            final String targetOid = (String) obj1.get(fieldname);
            if (srcOid != null && targetOid != null) {
                if (targetOid.equals(srcOid)) {
                    srcOid = null;
                } else {
                    srcOid = targetOid;
                }
            } else if (targetOid != null) {
                srcOid = targetOid;
            } else if (srcOid != null) {
                obj1.put(fieldname, srcOid);
                srcOid = null;
            }
            if (srcOid != null) {
                // need to add $set
                return new BasicDBObject(fieldname, srcOid);
            }
        } else {
            // nothing since save will be done just after
        }
        return null;
    }

    /**
     * Update the links
     *
     * @param obj1
     * @param vtReloaded
     * @param relation
     * @param src
     * @return the update part
     */
    protected final BasicDBObject updateLinks(final VitamType obj1, final VitamType vtReloaded,
            final VitamLinks relation, final boolean src) {
        // DBCollection coll = (src ? relation.col1.collection : relation.col2.collection);
        final String fieldname = (src ? relation.field1to2 : relation.field2to1);
        // VitamType vt = (VitamType) coll.findOne(new BasicDBObject("_id", obj1.get("_id")));
        if (vtReloaded != null) {
            @SuppressWarnings("unchecked")
            final List<String> srcList = (List<String>) vtReloaded.remove(fieldname);
            @SuppressWarnings("unchecked")
            final List<String> targetList = (List<String>) obj1.get(fieldname);
            if (srcList != null && targetList != null) {
                targetList.removeAll(srcList);
            } else if (targetList != null) {
                // srcList empty
            } else {
                // targetList empty
                obj1.put(fieldname, srcList);
            }
            if (targetList != null && !targetList.isEmpty()) {
                // need to add $addToSet
                return new BasicDBObject(fieldname, new BasicDBObject("$each", targetList));
            }
        } else {
            // nothing since save will be done just after, except checking array exists
            if (!obj1.containsField(fieldname)) {
                obj1.put(fieldname, new ArrayList<>());
            }
        }
        return null;
    }

    /**
     * Update links (not saved to database but to file)
     *
     * @param obj1
     * @param relation
     * @param src
     */
    protected final void updateLinksToFile(final VitamType obj1, final VitamLinks relation, final boolean src) {
        final String fieldname = (src ? relation.field1to2 : relation.field2to1);
        // nothing since save will be done just after, except checking array exists
        if (!obj1.containsField(fieldname)) {
            obj1.put(fieldname, new ArrayList<>());
        }
    }

    /**
     * Add an asymmetric relation (n-1) between Obj1 and Obj2
     *
     * @param obj1
     * @param obj1ToObj2
     * @param obj2
     * @param obj2ToObj1
     * @return a {@link DBObject} for update
     */
    private static final DBObject addReverseSymmetricLink(final VitamType obj1, final String obj1ToObj2,
            final VitamType obj2, final String obj2ToObj1) {
        addAsymmetricLinkset(obj1, obj1ToObj2, obj2, false);
        return addAsymmetricLinkUpdate(obj2, obj2ToObj1, obj1);
    }

    /**
     * Add an asymmetric relation (1-n) between Obj1 and Obj2
     *
     * @param obj1
     * @param obj1ToObj2
     * @param obj2
     * @param obj2ToObj1
     * @return a {@link DBObject} for update
     */
    private static final DBObject addSymmetricLink(final VitamType obj1, final String obj1ToObj2,
            final VitamType obj2, final String obj2ToObj1) {
        addAsymmetricLink(obj1, obj1ToObj2, obj2);
        return addAsymmetricLinkset(obj2, obj2ToObj1, obj1, true);
    }

    /**
     * Add a symmetric relation (n-n) between Obj1 and Obj2
     *
     * @param obj1
     * @param obj1ToObj2
     * @param obj2
     * @param obj2ToObj1
     * @return a {@link DBObject} for update
     */
    private static final DBObject addSymmetricLinkset(final VitamType obj1, final String obj1ToObj2,
            final VitamType obj2, final String obj2ToObj1) {
        addAsymmetricLinkset(obj1, obj1ToObj2, obj2, false);
        return addAsymmetricLinkset(obj2, obj2ToObj1, obj1, true);
    }

    /**
     * Add a single relation (1) from Obj1 to Obj2
     *
     * @param obj1
     * @param obj1ToObj2
     * @param obj2
     */
    private static final void addAsymmetricLink(final VitamType obj1, final String obj1ToObj2,
            final VitamType obj2) {
        final String refChild = (String) obj2.get(VitamType.ID);
        obj1.put(obj1ToObj2, refChild);
    }

    /**
     * Add a single relation (1) from Obj1 to Obj2 in update mode
     *
     * @param db
     * @param obj1
     * @param obj1ToObj2
     * @param obj2
     * @return a {@link DBObject} for update
     */
    private static final DBObject addAsymmetricLinkUpdate(final VitamType obj1, final String obj1ToObj2,
            final VitamType obj2) {
        final String refChild = (String) obj2.get(VitamType.ID);
        if (obj1.containsField(obj1ToObj2)) {
            if (obj1.get(obj1ToObj2).equals(refChild)) {
                return null;
            }
        }
        obj1.put(obj1ToObj2, refChild);
        return new BasicDBObject("$set", new BasicDBObject(obj1ToObj2, refChild));
    }

    /**
     * Add a one way relation (n) from Obj1 to Obj2, with no Save
     *
     * @param obj1
     * @param obj1ToObj2
     * @param obj2
     * @return true if the link is updated
     */
    protected static final boolean addAsymmetricLinksetNoSave(final VitamType obj1, final String obj1ToObj2,
            final VitamType obj2) {
        @SuppressWarnings("unchecked")
        ArrayList<String> relation12 = (ArrayList<String>) obj1.get(obj1ToObj2);
        final String oid2 = (String) obj2.get(VitamType.ID);
        if (relation12 == null) {
            relation12 = new ArrayList<String>();
        }
        if (relation12.contains(oid2)) {
            return false;
        }
        relation12.add(oid2);
        obj1.put(obj1ToObj2, relation12);
        return true;
    }

    /**
     * Add a one way relation (n) from Obj1 to Obj2
     *
     * @param obj1
     * @param obj1ToObj2
     * @param obj2
     * @param toUpdate
     *            True if this element will be updated through $addToSet only
     * @return a {@link DBObject} for update
     */
    private static final DBObject addAsymmetricLinkset(final VitamType obj1, final String obj1ToObj2,
            final VitamType obj2, final boolean toUpdate) {
        @SuppressWarnings("unchecked")
        ArrayList<String> relation12 = (ArrayList<String>) obj1.get(obj1ToObj2);
        final String oid2 = (String) obj2.get(VitamType.ID);
        if (relation12 == null) {
            if (toUpdate) {
                return new BasicDBObject("$addToSet", new BasicDBObject(obj1ToObj2, oid2));
            }
            relation12 = new ArrayList<String>();
        }
        if (relation12.contains(oid2)) {
            return null;
        }
        if (toUpdate) {
            return new BasicDBObject("$addToSet", new BasicDBObject(obj1ToObj2, oid2));
        } else {
            relation12.add(oid2);
            obj1.put(obj1ToObj2, relation12);
            return null;
        }
    }

    /**
     * 
     * @return a new ResultInterface
     */
    public static ResultInterface createOneResult() {
        if (GlobalDatas.USELRUCACHE) {
            return new ResultLRU();
        } else if (GlobalDatas.USEREDIS) {
            return new ResultRedis();
        } else {
            return new ResultMongodb();
        }
    }

    /**
     * 
     * @param collection 
     * @return a new ResultInterface
     */
    public static ResultInterface createOneResult(Collection<String> collection) {
        if (GlobalDatas.USELRUCACHE) {
            return new ResultLRU(collection);
        } else if (GlobalDatas.USEREDIS) {
            return new ResultRedis(collection);
        } else {
            return new ResultMongodb(collection);
        }
    }

}