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

org.javers.repository.mongo.MongoRepository Maven / Gradle / Ivy

There is a newer version: 7.7.0
Show newest version
package org.javers.repository.mongo;

import com.mongodb.BasicDBObject;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.javers.common.collections.Function;
import org.javers.common.collections.Lists;
import org.javers.common.collections.Optional;
import org.javers.core.commit.Commit;
import org.javers.core.commit.CommitId;
import org.javers.core.json.JsonConverter;
import org.javers.core.json.typeadapter.date.DateTypeCoreAdapters;
import org.javers.core.metamodel.object.CdoSnapshot;
import org.javers.core.metamodel.object.GlobalId;
import org.javers.core.metamodel.type.EntityType;
import org.javers.core.metamodel.type.ManagedType;
import org.javers.core.metamodel.type.ValueObjectType;
import org.javers.repository.api.JaversRepository;
import org.javers.repository.api.QueryParams;
import org.javers.repository.api.QueryParamsBuilder;
import org.javers.repository.api.SnapshotIdentifier;
import org.javers.repository.mongo.model.MongoHeadId;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import static org.javers.common.validation.Validate.conditionFulfilled;

/**
 * @author pawel szymczyk
 */
public class MongoRepository implements JaversRepository {
    private static final Logger logger = LoggerFactory.getLogger(MongoRepository.class);

    private static final int DESC = -1;
    private static final int ASC = 1;

    private static final String SNAPSHOTS = "jv_snapshots";
    private static final String COMMIT_ID = "commitMetadata.id";
    private static final String COMMIT_DATE = "commitMetadata.commitDate";
    private static final String COMMIT_AUTHOR = "commitMetadata.author";
    private static final String COMMIT_PROPERTIES = "commitMetadata.properties";
    private static final String GLOBAL_ID_KEY = "globalId_key";
    private static final String GLOBAL_ID_ENTITY = "globalId.entity";
    private static final String GLOBAL_ID_OWNER_ID_ENTITY = "globalId.ownerId.entity";
    private static final String GLOBAL_ID_FRAGMENT = "globalId.fragment";
    private static final String GLOBAL_ID_VALUE_OBJECT = "globalId.valueObject";
    private static final String SNAPSHOT_VERSION = "version";
    private static final String CHANGED_PROPERTIES = "changedProperties";
    private static final String OBJECT_ID = "_id";

    private MongoDatabase mongo;
    private JsonConverter jsonConverter;

    public MongoRepository(MongoDatabase mongo) {
        this.mongo = mongo;
    }

    MongoRepository(MongoDatabase mongo, JsonConverter jsonConverter) {
        this.mongo = mongo;
        this.jsonConverter = jsonConverter;
    }

    @Override
    public void persist(Commit commit) {
        persistSnapshots(commit);
        persistHeadId(commit);
    }

    void clean(){
        snapshotsCollection().deleteMany(new Document());
        headCollection().deleteMany(new Document());
    }

    @Override
    public List getStateHistory(GlobalId globalId, QueryParams queryParams) {
        return queryForSnapshots(createIdQuery(globalId), Optional.of(queryParams));
    }

    @Override
    public Optional getLatest(GlobalId globalId) {
        return getLatest(createIdQuery(globalId));
    }

    @Override
    public List getSnapshots(QueryParams queryParams) {
        return queryForSnapshots(new BasicDBObject(), Optional.of(queryParams));
    }

    @Override
    public List getSnapshots(Collection snapshotIdentifiers) {
        return snapshotIdentifiers.isEmpty() ? Collections.emptyList() :
            queryForSnapshots(createSnapshotIdentifiersQuery(snapshotIdentifiers), Optional.empty());
    }

    @Override
    public List getValueObjectStateHistory(EntityType ownerEntity, String path, QueryParams queryParams) {
        BasicDBObject query = new BasicDBObject(GLOBAL_ID_OWNER_ID_ENTITY, ownerEntity.getName());
        query.append(GLOBAL_ID_FRAGMENT, path);

        return queryForSnapshots(query, Optional.of(queryParams));
    }

    @Override
    public List getPropertyStateHistory(GlobalId globalId, String propertyName, QueryParams queryParams) {
        BasicDBObject query = createIdQuery(globalId);

        query.append(CHANGED_PROPERTIES, propertyName);

        return queryForSnapshots(query, Optional.of(queryParams));
    }

    @Override
    public List getPropertyStateHistory(ManagedType givenClass, String propertyName, QueryParams queryParams) {
        BasicDBObject query = createGlobalIdClassQuery(givenClass);

        query.append(CHANGED_PROPERTIES, propertyName);

        return queryForSnapshots(query, Optional.of(queryParams));
    }

    @Override
    public List getStateHistory(ManagedType givenClass, QueryParams queryParams) {
        BasicDBObject query = createGlobalIdClassQuery(givenClass);
        return queryForSnapshots(query, Optional.of(queryParams));
    }

    @Override
    public CommitId getHeadId() {
        Document headId = headCollection().find().first();

        if (headId == null) {
            return null;
        }

        return new MongoHeadId(headId).toCommitId();
    }

    @Override
    public void setJsonConverter(JsonConverter jsonConverter) {
        this.jsonConverter = jsonConverter;
    }

    @Override
    public void ensureSchema() {
        //ensures collections and indexes
        MongoCollection snapshots = snapshotsCollection();
        snapshots.createIndex(new BasicDBObject(GLOBAL_ID_KEY, ASC));
        snapshots.createIndex(new BasicDBObject(GLOBAL_ID_ENTITY, ASC));
        snapshots.createIndex(new BasicDBObject(GLOBAL_ID_VALUE_OBJECT, ASC));
        snapshots.createIndex(new BasicDBObject(GLOBAL_ID_OWNER_ID_ENTITY, ASC));
        snapshots.createIndex(new BasicDBObject(CHANGED_PROPERTIES, ASC));
        snapshots.createIndex(new BasicDBObject(COMMIT_PROPERTIES + ".key", ASC).append(COMMIT_PROPERTIES + ".value", ASC));
        headCollection();

        //schema migration script from JaVers 1.1 to 1.2
        Document doc = snapshots.find().first();
        if (doc != null) {
            Object stringCommitId = ((Map)doc.get("commitMetadata")).get("id");
            if (stringCommitId instanceof String) {
                logger.info("executing db migration script, from JaVers 1.1 to 1.2 ...");

                Document update = new Document("eval",
                    "function() { \n"+
                            "    db.jv_snapshots.find().forEach( \n"+
                            "      function(snapshot) { \n"+
                            "        snapshot.commitMetadata.id = Number(snapshot.commitMetadata.id); \n"+
                            "        db.jv_snapshots.save(snapshot); } \n" +
                            "    ); "+
                            "    return 'ok'; \n"+
                            "}"
                    );

                Document ret = mongo.runCommand(update);
                logger.info("result: \n "+ ret.toJson());
            }
        }
    }

    private BasicDBObject createIdQuery(GlobalId id) {
        return new BasicDBObject (GLOBAL_ID_KEY, id.value());
    }

    private Bson createVersionQuery(Long version) {
        return new BasicDBObject(SNAPSHOT_VERSION, version);
    }

    private Bson createSnapshotIdentifiersQuery(Collection snapshotIdentifiers) {
        Collection descFilters = Lists.transform(new ArrayList<>(snapshotIdentifiers), new Function() {
            @Override
            public Bson apply(SnapshotIdentifier snapshotIdentifier) {
                return Filters.and(
                    createIdQuery(snapshotIdentifier.getGlobalId()),
                    createVersionQuery(snapshotIdentifier.getVersion())
                );
            }
        });
        return Filters.or(descFilters);
    }

    private BasicDBObject createGlobalIdClassQuery(ManagedType givenClass) {
        String cName = givenClass.getName();

        BasicDBObject query = null;
        if (givenClass instanceof EntityType) {
            query = new BasicDBObject(GLOBAL_ID_ENTITY, cName);
        }
        if (givenClass instanceof ValueObjectType) {
            query = new BasicDBObject(GLOBAL_ID_VALUE_OBJECT, cName);
        }
        return query;
    }

    private CdoSnapshot readFromDBObject(Document dbObject) {
        return jsonConverter.fromJson(dbObject.toJson(), CdoSnapshot.class);
    }

    private Document writeToDBObject(CdoSnapshot snapshot){
        conditionFulfilled(jsonConverter != null, "MongoRepository: jsonConverter is null");
        Document dbObject = Document.parse(jsonConverter.toJson(snapshot));
        dbObject.append(GLOBAL_ID_KEY,snapshot.getGlobalId().value());
        return dbObject;
    }

    private MongoCollection snapshotsCollection() {
        return mongo.getCollection(SNAPSHOTS);
    }

    private MongoCollection headCollection() {
        return mongo.getCollection(MongoHeadId.COLLECTION_NAME);
    }

    private void persistSnapshots(Commit commit) {
        MongoCollection collection = snapshotsCollection();
        for (CdoSnapshot snapshot: commit.getSnapshots()) {
            collection.insertOne(writeToDBObject(snapshot));
        }
    }

    private void persistHeadId(Commit commit) {
        MongoCollection headIdCollection = headCollection();

        Document oldHead = headIdCollection.find().first();
        MongoHeadId newHeadId = new MongoHeadId(commit.getId());

        if (oldHead == null) {
            headIdCollection.insertOne(newHeadId.toDocument());
        } else {
            headIdCollection.updateOne(objectIdFiler(oldHead), newHeadId.getUpdateCommand());
        }
    }

    private Bson objectIdFiler(Document document) {
        return Filters.eq(OBJECT_ID, document.getObjectId("_id"));
    }

    private MongoCursor getMongoSnapshotsCursor(Bson query, Optional queryParams) {
        FindIterable findIterable = snapshotsCollection()
            .find(applyQueryParams(query, queryParams))
            .sort(new Document(COMMIT_ID, DESC));
        return applyQueryParams(findIterable, queryParams).iterator();
    }

    private Bson applyQueryParams(Bson query, Optional queryParams) {
        if (queryParams.isPresent()) {
            QueryParams params = queryParams.get();

            if (params.from().isPresent()) {
                query = addFromDateFiler(query, params.from().get());
            }
            if (params.to().isPresent()) {
                query = addToDateFilter(query, params.to().get());
            }
            if (params.commitId().isPresent()) {
                query = addCommitIdFilter(query, params.commitId().get());
            }
            if (params.version().isPresent()) {
                query = addVersionFilter(query, params.version().get());
            }
            if (params.author().isPresent()) {
                query = addAuthorFilter(query, params.author().get());
            }
            if (!params.commitProperties().isEmpty()) {
                query = addCommitPropertiesFilter(query, params.commitProperties());
            }
        }
        return query;
    }

    private FindIterable applyQueryParams(FindIterable findIterable, Optional queryParams) {
        if (queryParams.isPresent()) {
            QueryParams params = queryParams.get();
            findIterable = findIterable
                .limit(params.limit())
                .skip(params.skip());
        }
        return  findIterable;
    }

    private Bson addFromDateFiler(Bson query, LocalDateTime from) {
        return Filters.and(query, Filters.gte(COMMIT_DATE, DateTypeCoreAdapters.serialize(from)));
    }

    private Bson addToDateFilter(Bson query, LocalDateTime to) {
        return Filters.and(query, Filters.lte(COMMIT_DATE, DateTypeCoreAdapters.serialize(to)));
    }

    private Bson addCommitIdFilter(Bson query, CommitId commitId) {
        return Filters.and(query, new BasicDBObject(COMMIT_ID, commitId.valueAsNumber().doubleValue()));
    }

    private Bson addVersionFilter(Bson query, Long version) {
        return Filters.and(query, createVersionQuery(version));
    }

    private Bson addCommitPropertiesFilter(Bson query, Map commitProperties) {

        List propertyFilters = new ArrayList<>();
        for (Map.Entry commitProperty : commitProperties.entrySet()) {
            BasicDBObject propertyFilter = new BasicDBObject(COMMIT_PROPERTIES,
                new BasicDBObject("$elemMatch",
                    new BasicDBObject("key", commitProperty.getKey()).append(
                        "value", commitProperty.getValue())));
            propertyFilters.add(propertyFilter);
        }

        return Filters.and(query, Filters.and(propertyFilters.toArray(new Bson[]{})));
    }

    private Bson addAuthorFilter(Bson query, String author) {
        return Filters.and(query, new BasicDBObject(COMMIT_AUTHOR, author));
    }

    private Optional getLatest(Bson idQuery) {
        QueryParams queryParams = QueryParamsBuilder.withLimit(1).build();
        MongoCursor mongoLatest = getMongoSnapshotsCursor(idQuery, Optional.of(queryParams));

        if (!mongoLatest.hasNext()) {
            return Optional.empty();
        }

        Document dbObject = getOne(mongoLatest);
        return Optional.of(readFromDBObject(dbObject));
    }

    private List queryForSnapshots(Bson query, Optional queryParams) {
        List snapshots = new ArrayList<>();
        try (MongoCursor mongoSnapshots = getMongoSnapshotsCursor(query, queryParams)) {
            while (mongoSnapshots.hasNext()) {
                Document dbObject = mongoSnapshots.next();
                snapshots.add(readFromDBObject(dbObject));
            }
            return snapshots;
        }
    }

    private  T getOne(MongoCursor mongoCursor){
        try{
            return mongoCursor.next();
        }
        finally {
            mongoCursor.close();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy