Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.javers.repository.mongo.MongoRepository Maven / Gradle / Ivy
package org.javers.repository.mongo;
import com.google.gson.JsonObject;
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.string.RegexEscape;
import org.javers.common.validation.Validate;
import org.javers.core.CommitIdGenerator;
import org.javers.core.JaversCoreConfiguration;
import org.javers.core.commit.Commit;
import org.javers.core.commit.CommitId;
import org.javers.core.json.JsonConverter;
import org.javers.core.json.typeadapter.util.UtilTypeCoreAdapters;
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.*;
import org.javers.repository.mongo.model.MongoHeadId;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import static org.javers.common.collections.Lists.toImmutableList;
import static org.javers.common.validation.Validate.conditionFulfilled;
import static org.javers.repository.mongo.DocumentConverter.fromDocument;
import static org.javers.repository.mongo.DocumentConverter.toDocument;
import static org.javers.repository.mongo.MongoDialect.DOCUMENT_DB;
import static org.javers.repository.mongo.MongoDialect.MONGO_DB;
import static org.javers.repository.mongo.MongoSchemaManager.*;
/**
* @author pawel szymczyk
*/
public class MongoRepository implements JaversRepository, ConfigurationAware {
private final static int DEFAULT_CACHE_SIZE = 5000;
private static final int DESC = -1;
private final MongoSchemaManager mongoSchemaManager;
private JsonConverter jsonConverter;
private JaversCoreConfiguration coreConfiguration;
private final MapKeyDotReplacer mapKeyDotReplacer = new MapKeyDotReplacer();
private final LatestSnapshotCache cache;
private MongoDialect mongoDialect;
public MongoRepository(MongoDatabase mongo) {
this(mongo, DEFAULT_CACHE_SIZE, MONGO_DB);
}
/**
* MongoRepository compatible with Amazon DocumentDB.
*
*
* Compound index on commitProperties
isn't created.
*
*
* See functional differences .
*/
public static MongoRepository mongoRepositoryWithDocumentDBCompatibility(MongoDatabase mongo) {
return new MongoRepository(mongo, DEFAULT_CACHE_SIZE, DOCUMENT_DB);
}
/**
* @param cacheSize Size of the latest snapshots cache, default is 5000. Set 0 to disable.
*/
public MongoRepository(MongoDatabase mongo, int cacheSize) {
this(mongo, cacheSize, MONGO_DB);
}
MongoRepository(MongoDatabase mongo, int cacheSize, MongoDialect dialect) {
Validate.argumentsAreNotNull(mongo, dialect);
this.mongoDialect = dialect;
this.mongoSchemaManager = new MongoSchemaManager(mongo);
cache = new LatestSnapshotCache(cacheSize, input -> getLatest(createIdQuery(input)));
}
@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) {
Bson query;
if (queryParams.isAggregate()){
query = createIdQueryWithAggregate(globalId);
} else {
query = createIdQuery(globalId);
}
return queryForSnapshots(query, Optional.of(queryParams));
}
@Override
public Optional getLatest(GlobalId globalId) {
return cache.getLatest(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 getStateHistory(Set givenClasses, QueryParams queryParams) {
Bson query = createManagedTypeQuery(givenClasses, queryParams.isAggregate());
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 setConfiguration(JaversCoreConfiguration coreConfiguration) {
this.coreConfiguration = coreConfiguration;
}
@Override
public void ensureSchema() {
mongoSchemaManager.ensureSchema(mongoDialect);
}
private Bson createIdQuery(GlobalId id) {
return new BasicDBObject(GLOBAL_ID_KEY, id.value());
}
private Bson createIdQueryWithAggregate(GlobalId id) {
return Filters.or(createIdQuery(id), prefixQuery(GLOBAL_ID_KEY, id.value() + "#"));
}
private Bson createVersionQuery(Long version) {
return new BasicDBObject(SNAPSHOT_VERSION, version);
}
private Bson createSnapshotIdentifiersQuery(Collection snapshotIdentifiers) {
List descFilters = snapshotIdentifiers.stream().map(
snapshotIdentifier -> Filters.and(
createIdQuery(snapshotIdentifier.getGlobalId()),
createVersionQuery(snapshotIdentifier.getVersion())
)).collect(toImmutableList());
return Filters.or(descFilters);
}
private Bson createManagedTypeQuery(Set managedTypes, boolean aggregate) {
List classFilters = managedTypes.stream().map( managedType -> {
if (managedType instanceof ValueObjectType) {
return createValueObjectTypeQuery(managedType);
} else {
return createEntityTypeQuery(aggregate, managedType);
}
}).collect(toImmutableList());
return Filters.or(classFilters);
}
private Bson createValueObjectTypeQuery(ManagedType managedType) {
return new BasicDBObject(GLOBAL_ID_VALUE_OBJECT, managedType.getName());
}
private Bson createEntityTypeQuery(boolean aggregate, ManagedType managedType) {
Bson entityTypeQuery = prefixQuery(GLOBAL_ID_KEY, managedType.getName() + "/");
if (!aggregate) {
entityTypeQuery = Filters.and(entityTypeQuery, Filters.exists(GLOBAL_ID_ENTITY));
}
return entityTypeQuery;
}
private CdoSnapshot readFromDBObject(Document dbObject) {
return jsonConverter.fromJson(fromDocument(mapKeyDotReplacer.back(dbObject)), CdoSnapshot.class);
}
private Document writeToDBObject(CdoSnapshot snapshot){
conditionFulfilled(jsonConverter != null, "MongoRepository: jsonConverter is null");
Document dbObject = toDocument((JsonObject)jsonConverter.toJsonElement(snapshot));
dbObject = mapKeyDotReplacer.replaceInSnapshotState(dbObject);
dbObject.append(GLOBAL_ID_KEY,snapshot.getGlobalId().value());
return dbObject;
}
private MongoCollection snapshotsCollection() {
return mongoSchemaManager.snapshotsCollection();
}
private MongoCollection headCollection() {
return mongoSchemaManager.headCollection();
}
private void persistSnapshots(Commit commit) {
MongoCollection collection = snapshotsCollection();
commit.getSnapshots().forEach(snapshot -> {
collection.insertOne(writeToDBObject(snapshot));
cache.put(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));
if (coreConfiguration.getCommitIdGenerator() == CommitIdGenerator.SYNCHRONIZED_SEQUENCE) {
findIterable.sort(new Document(COMMIT_ID, DESC));
}
else {
findIterable.sort(new Document(COMMIT_DATE_INSTANT, 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 = Filters.and(query, Filters.gte(COMMIT_DATE, UtilTypeCoreAdapters.serialize(params.from().get())));
}
if (params.to().isPresent()) {
query = Filters.and(query, Filters.lte(COMMIT_DATE, UtilTypeCoreAdapters.serialize(params.to().get())));
}
if (params.toCommitId().isPresent()) {
BigDecimal commitId = params.toCommitId().get().valueAsNumber();
query = Filters.and(query, Filters.lte(COMMIT_ID, commitId));
}
if (params.commitIds().size() > 0) {
query = Filters.in(COMMIT_ID, params.commitIds().stream()
.map(CommitId::valueAsNumber).collect(Collectors.toSet()));
}
if (params.version().isPresent()) {
query = Filters.and(query, createVersionQuery(params.version().get()));
}
if (params.author().isPresent()) {
query = Filters.and(query, new BasicDBObject(COMMIT_AUTHOR, params.author().get()));
}
if (!params.commitProperties().isEmpty()) {
query = addCommitPropertiesFilter(query, params.commitProperties());
}
if (params.changedProperty().isPresent()) {
query = Filters.and(query, new BasicDBObject(CHANGED_PROPERTIES, params.changedProperty().get()));
}
if (params.snapshotType().isPresent()) {
query = Filters.and(query, new BasicDBObject(SNAPSHOT_TYPE, params.snapshotType().get().name()));
}
}
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 addCommitPropertiesFilter(Bson query, Map commitProperties) {
List propertyFilters = commitProperties.entrySet().stream().map( commitProperty ->
new BasicDBObject(COMMIT_PROPERTIES,
new BasicDBObject("$elemMatch",
new BasicDBObject("key", commitProperty.getKey()).append(
"value", commitProperty.getValue())))
).collect(toImmutableList());
return Filters.and(query, Filters.and(propertyFilters.toArray(new Bson[]{})));
}
private Optional getLatest(Bson idQuery) {
QueryParams queryParams = QueryParamsBuilder.withLimit(1).build();
MongoCursor mongoLatest = getMongoSnapshotsCursor(idQuery, Optional.of(queryParams));
return getOne(mongoLatest).map(d -> readFromDBObject(d));
}
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 static Optional getOne(MongoCursor mongoCursor){
try{
if (!mongoCursor.hasNext()) {
return Optional.empty();
}
return Optional.of(mongoCursor.next());
}
finally {
mongoCursor.close();
}
}
//enables index range scan
private static Bson prefixQuery(String fieldName, String prefix){
return Filters.regex(fieldName, "^" + RegexEscape.escape(prefix) + ".*");
}
}