/*******************************************************************************
* Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019)
*
* contact.vitam@culture.gouv.fr
*
* This software is a computer program whose purpose is to implement a digital archiving back-office system managing
* high volumetry securely and efficiently.
*
* This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free
* software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as
* circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license,
* users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the
* successive licensors have only limited liability.
*
* In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or
* developing or reproducing the software by the user in light of its specific status of free software, that may mean
* that it is complicated to manipulate, and that also therefore means that it is reserved for developers and
* experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the
* software's suitability as regards their requirements in conditions enabling the security of their systems and/or data
* to be ensured and, more generally, to use and operate it in the same conditions as regards security.
*
* The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you
* accept its terms.
*******************************************************************************/
/**
*
*/
package fr.gouv.vitam.metadata.core.database.collections;
import static com.mongodb.client.model.Filters.eq;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.bson.Document;
import org.bson.conversions.Bson;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoException;
import com.mongodb.client.FindIterable;
import com.mongodb.client.model.Filters;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import fr.gouv.vitam.common.database.builder.request.configuration.BuilderToken.FILTERARGS;
import fr.gouv.vitam.common.database.builder.request.configuration.BuilderToken.UPDATEACTION;
import fr.gouv.vitam.common.database.builder.request.configuration.BuilderToken.UPDATEACTIONARGS;
import fr.gouv.vitam.metadata.api.exception.MetaDataExecutionException;
/**
* MongoDb Helper for Metadata
*/
public class MongoDbMetadataHelper {
/**
* Quick projection for ID Only
*/
public static final BasicDBObject ID_PROJECTION = new BasicDBObject(MetadataDocument.ID, 1);
protected static final String ADD_TO_SET = "$addToSet";
private MongoDbMetadataHelper() {
// Empty constructor
}
/**
* Does not call getAfterLoad
*
* @param metadataCollections (not results except if already hashed)
* @param ref
* @return a MetadataDocument generic object from ID = ref value
*/
@SuppressWarnings("rawtypes")
public static final MetadataDocument findOneNoAfterLoad(final MetadataCollections metadataCollections,
final String ref) {
return (MetadataDocument<?>) metadataCollections.getCollection().find(eq(MetadataDocument.ID, ref)).first();
}
/**
* Load a Document into MetadataDocument<?>. Calls getAfterLoad
*
* @param coll
* @param obj
* @return the MetadataDocument<?> casted object
* @throws InstantiationException
* @throws IllegalAccessException
*/
@SuppressWarnings("rawtypes")
public static final MetadataDocument loadFromDocument(final MetadataCollections coll, final Document obj)
throws InstantiationException, IllegalAccessException {
final MetadataDocument<?> vt = (MetadataDocument<?>) coll.getClasz().newInstance();
vt.putAll(obj);
vt.getAfterLoad();
return vt;
}
/**
* Calls getAfterLoad
*
* @param col (not Results except if already hashed)
* @param field
* @param ref
* @return the MetadataDocument casted object using field = ref
*/
@SuppressWarnings("rawtypes")
public static final MetadataDocument findOne(final MetadataCollections col, final String field, final String ref) {
final MetadataDocument<?> vitobj =
(MetadataDocument<?>) col.getCollection().find(eq(field, ref)).first();
if (vitobj == null) {
return null;
} else {
vitobj.getAfterLoad();
}
return vitobj;
}
/**
* Find the corresponding id in col collection if it exists. Calls getAfterLoad
*
* @param col (not results except if already hashed)
* @param id
* @return the MetadataDocument casted object using ID = id
*/
@SuppressWarnings("rawtypes")
public static final MetadataDocument findOne(final MetadataCollections col, final String id) {
if (id == null || id.length() == 0) {
return null;
}
return findOne(col, MetadataDocument.ID, id);
}
/**
* OK with native id for Results
*
* @param col
* @param id
* @return True if one MetadataDocument object exists with this id
*/
public static final boolean exists(final MetadataCollections col, final String id) {
if (id == null || id.length() == 0) {
return false;
}
return col.getCollection().find(eq(MetadataDocument.ID, id)).projection(MongoDbMetadataHelper.ID_PROJECTION)
.first() != null;
}
/**
* Does not call getAfterLoad.
*
* @param collection domain of request
* @param condition where condition
* @param projection select condition
* @return the FindIterable on the find request based on the given collection
*/
public static final FindIterable<?> select(final MetadataCollections collection,
final Bson condition,
final Bson projection) {
if (projection != null) {
return collection.getCollection().find(condition).projection(projection);
} else {
return collection.getCollection().find(condition);
}
}
/**
* Does not call getAfterLoad.
*
* @param collection domain of request
* @param condition where condition
* @param projection select condition
* @param orderBy orderBy condition
* @param offset offset (0 by default)
* @param limit limit (0 for no limit)
* @return the FindIterable on the find request based on the given collection
*/
public static final FindIterable<?> select(final MetadataCollections collection,
final Bson condition, final Bson projection, final Bson orderBy,
final int offset, final int limit) {
FindIterable<?> find = collection.getCollection().find(condition).skip(offset);
if (projection != null) {
find = find.projection(projection);
}
if (orderBy != null) {
find = find.sort(orderBy);
}
if (limit > 0) {
find = find.limit(limit);
}
return find;
}
/**
* @param collection domain of request
* @param condition where condition
* @param data update
* @param nb nb of item to update
* @return the UpdateResult on the update request based on the given collection
* @throws MetaDataExecutionException
*/
public static final UpdateResult update(final MetadataCollections collection,
final Bson condition, final Bson data, int nb)
throws MetaDataExecutionException {
try {
if (nb > 1) {
return collection.getCollection().updateMany(condition, data);
} else {
return collection.getCollection().updateOne(condition, data);
}
} catch (final MongoException e) {
throw new MetaDataExecutionException(e);
}
}
/**
* @param collection domain of request
* @param condition where condition
* @param nb nb of item to delete
* @return the DeleteResult on the update request based on the given collection
* @throws MetaDataExecutionException
*/
public static final DeleteResult delete(final MetadataCollections collection,
final Bson condition, int nb)
throws MetaDataExecutionException {
try {
if (nb > 1) {
return collection.getCollection().deleteMany(condition);
} else {
return collection.getCollection().deleteOne(condition);
}
} catch (final MongoException e) {
throw new MetaDataExecutionException(e);
}
}
/**
* @param targetIds
* @param ancestorIds
* @return the Filter condition to find if ancestorIds are ancestors of ObjectGroup targetIds
*/
public static final Bson queryObjectGroupForAncestors(Set<String> targetIds, Set<String> ancestorIds) {
return Filters.and(Filters.in(MetadataDocument.OG, targetIds),
Filters.or(Filters.in(MetadataDocument.UP, ancestorIds), Filters.in(MetadataDocument.ID, ancestorIds)));
}
/**
* @param targetIds
* @param ancestorIds
* @return the Filter condition to find if ancestorIds are ancestors of targetIds
*/
public static final Bson queryForAncestors(Set<String> targetIds, Set<String> ancestorIds) {
return Filters.and(Filters.in(MetadataDocument.ID, targetIds), Filters.in(MetadataDocument.UP, ancestorIds));
}
/**
* @param targetIds
* @param ancestorIds
* @return the Filter condition to find if ancestorIds are ancestors of targetIds or equals to targetIds
*/
public static final Bson queryForAncestorsOrSame(Set<String> targetIds, Set<String> ancestorIds) {
ancestorIds.addAll(targetIds);
// TODO P1 understand why it add empty string
ancestorIds.remove("");
final int size = ancestorIds.size();
if (size > 0) {
return Filters.or(
Filters.and(Filters.in(MetadataDocument.ID, targetIds), Filters.in(MetadataDocument.ID, ancestorIds)),
Filters.and(Filters.in(MetadataDocument.ID, targetIds), Filters.in(MetadataDocument.UP, ancestorIds)));
}
return new BasicDBObject();
}
/**
* 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 BasicDBObject} that hold a possible update part (may be null) as { $addToSet : { field : value }
* } or { field : value }
*/
@SuppressWarnings("rawtypes")
protected static final BasicDBObject addLink(final MetadataDocument obj1,
final VitamLinks relation,
final MetadataDocument obj2) {
switch (relation.type) {
case SYM_LINK_N1:
setAsymmetricLink(obj1, relation.field1to2, obj2);
return addAsymmetricLinkset(obj2, relation.field2to1, obj1, true);
case SYM_LINK_N_N:
return addAsymmetricLinkset(obj2, relation.field2to1, obj1, true);
default:
break;
}
return null;
}
/**
* Update the link (1 link type)
*
* @param obj1
* @param vtReloaded
* @param relation
* @param src
* @return the update part as { field : value }
*/
@SuppressWarnings("rawtypes")
protected static final BasicDBObject updateLink(final MetadataDocument obj1,
final MetadataDocument vtReloaded,
final VitamLinks relation, final boolean src) {
final String fieldname = src ? relation.field1to2 : relation.field2to1;
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 linkset (N link type)
*
* @param obj1
* @param vtReloaded
* @param relation
* @param src
* @return the update part as { field : {$each : [value] } }
*/
@SuppressWarnings("rawtypes")
protected static final BasicDBObject updateLinkset(final MetadataDocument obj1,
final MetadataDocument vtReloaded,
final VitamLinks relation, final boolean src) {
final String fieldname = src ? relation.field1to2 : relation.field2to1;
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(UPDATEACTIONARGS.EACH.exactToken(), targetList));
}
} else {
// nothing since save will be done just after, except checking array exists
if (!obj1.containsKey(fieldname)) {
obj1.put(fieldname, new ArrayList<>());
}
}
return null;
}
/**
* Add a single relation (1) from Obj1 to Obj2 (used in N-1 link)
*
* @param db
* @param obj1
* @param obj1ToObj2
* @param obj2
* @return a {@link BasicDBObject} for update as { field : value }
*/
@SuppressWarnings("rawtypes")
private static final BasicDBObject setAsymmetricLink(
final MetadataDocument obj1, final String obj1ToObj2,
final MetadataDocument obj2) {
final String refChild = obj2.getId();
if (obj1.containsKey(obj1ToObj2) && obj1.get(obj1ToObj2).equals(refChild)) {
return null;
}
obj1.put(obj1ToObj2, refChild);
return new BasicDBObject(UPDATEACTION.SET.exactToken(), new BasicDBObject(obj1ToObj2, refChild));
}
/**
* Add a one way relation (n) from Obj1 to Obj2 (used in N-(N) and N-1 links)
*
* @param obj1
* @param obj1ToObj2
* @param obj2
* @param toUpdate True if this element will be updated through $addToSet only
* @return a {@link BasicDBObject} for update as { $addToSet : { field : value } }
*/
@SuppressWarnings("rawtypes")
private static final BasicDBObject addAsymmetricLinkset(final MetadataDocument obj1,
final String obj1ToObj2,
final MetadataDocument obj2, final boolean toUpdate) {
@SuppressWarnings("unchecked")
ArrayList<String> relation12 = (ArrayList<String>) obj1.get(obj1ToObj2);
final String oid2 = obj2.getId();
if (relation12 == null) {
if (toUpdate) {
return new BasicDBObject(ADD_TO_SET,
new BasicDBObject(obj1ToObj2, oid2));
}
relation12 = new ArrayList<>();
}
if (relation12.contains(oid2)) {
return null;
}
if (toUpdate) {
return new BasicDBObject(ADD_TO_SET, new BasicDBObject(obj1ToObj2, oid2));
} else {
relation12.add(oid2);
obj1.put(obj1ToObj2, relation12);
return null;
}
}
/**
* @param type to use
* @return a new Result
*/
public static Result createOneResult(FILTERARGS type) {
return new ResultDefault(type);
}
/**
* @param type
* @param set
* @return a new Result
*/
public static Result createOneResult(FILTERARGS type, Set<String> set) {
return new ResultDefault(type, set);
}
}