001/* 002 * (C) Copyright 2020 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 */ 017package org.nuxeo.drive.mongodb; 018 019import static com.mongodb.client.model.Filters.and; 020import static com.mongodb.client.model.Filters.eq; 021import static com.mongodb.client.model.Filters.exists; 022import static com.mongodb.client.model.Filters.gt; 023import static com.mongodb.client.model.Filters.in; 024import static com.mongodb.client.model.Filters.lte; 025import static com.mongodb.client.model.Filters.ne; 026import static com.mongodb.client.model.Filters.or; 027import static com.mongodb.client.model.Filters.regex; 028import static com.mongodb.client.model.Sorts.ascending; 029import static com.mongodb.client.model.Sorts.descending; 030import static com.mongodb.client.model.Sorts.orderBy; 031import static org.nuxeo.ecm.platform.audit.api.BuiltinLogEntryData.LOG_ID; 032 033import java.util.ArrayList; 034import java.util.Date; 035import java.util.List; 036import java.util.Set; 037 038import org.apache.logging.log4j.LogManager; 039import org.apache.logging.log4j.Logger; 040import org.bson.Document; 041import org.bson.conversions.Bson; 042import org.nuxeo.drive.service.SynchronizationRoots; 043import org.nuxeo.drive.service.impl.AuditChangeFinder; 044import org.nuxeo.ecm.core.api.CoreSession; 045import org.nuxeo.ecm.core.query.sql.model.OrderByExprs; 046import org.nuxeo.ecm.core.query.sql.model.Predicates; 047import org.nuxeo.ecm.core.query.sql.model.QueryBuilder; 048import org.nuxeo.ecm.platform.audit.api.AuditQueryBuilder; 049import org.nuxeo.ecm.platform.audit.api.AuditReader; 050import org.nuxeo.ecm.platform.audit.api.LogEntry; 051import org.nuxeo.mongodb.audit.MongoDBAuditBackend; 052import org.nuxeo.mongodb.audit.MongoDBAuditEntryReader; 053import org.nuxeo.runtime.api.Framework; 054 055import com.mongodb.client.MongoCollection; 056 057/** @since 11.3 */ 058public class MongoDBAuditChangeFinder extends AuditChangeFinder { 059 060 private static final Logger log = LogManager.getLogger(MongoDBAuditChangeFinder.class); 061 062 @Override 063 public long getUpperBound() { 064 MongoDBAuditBackend auditService = (MongoDBAuditBackend) Framework.getService(AuditReader.class); 065 // TODO remove this dummy predicate once we can query with no predicate at all 066 QueryBuilder queryBuilder = new AuditQueryBuilder().predicate(Predicates.gt("eventDate", new Date(0))); 067 queryBuilder.order(OrderByExprs.desc(LOG_ID)); 068 queryBuilder.limit(1); 069 List<LogEntry> entries = auditService.queryLogs(queryBuilder); 070 if (entries.isEmpty()) { 071 log.debug("Found no audit log entries, returning -1"); 072 return -1; 073 } 074 return entries.get(0).getId(); 075 } 076 077 @Override 078 protected List<LogEntry> queryAuditEntries(CoreSession session, SynchronizationRoots activeRoots, 079 Set<String> collectionSyncRootMemberIds, long lowerBound, long upperBound, int limit) { 080 MongoDBAuditBackend auditService = (MongoDBAuditBackend) Framework.getService(AuditReader.class); 081 MongoCollection<Document> auditCollection = auditService.getAuditCollection(); 082 083 // Build intermediate filters 084 Bson lifeCycleEvent = and(eq("category", "eventLifeCycleCategory"), eq("eventId", "lifecycle_transition_event"), 085 ne("docLifeCycle", "deleted")); 086 Bson documentEvent = and(eq("category", "eventDocumentCategory"), 087 in("eventId", "documentCreated", "documentModified", "documentMoved", "documentCreatedByCopy", 088 "documentRestored", "addedToCollection", "documentProxyPublished", "documentLocked", 089 "documentUnlocked", "documentUntrashed")); 090 Bson sessionRepository = eq("repositoryId", session.getRepositoryName()); 091 Bson driveCategory = eq("category", "NuxeoDrive"); 092 Bson notRootUnregistered = ne("eventId", "rootUnregistered"); 093 Bson inEvents = or(documentEvent, lifeCycleEvent); 094 Bson isGeneral = addRoots(inEvents, activeRoots.getPaths(), collectionSyncRootMemberIds); 095 Bson isDrive = and(driveCategory, notRootUnregistered); 096 Bson idRange = and(gt("_id", lowerBound), lte("_id", upperBound)); 097 Bson isUser = or(exists("extended.impactedUserName", false), 098 eq("extended.impactedUserName", session.getPrincipal().getName())); 099 100 // Build final filter 101 Bson filter = and(sessionRepository, or(isGeneral, isDrive), idRange, isUser); 102 log.debug("Query on MongoDB-Audit: {}", 103 () -> filter.toBsonDocument(Document.class, auditCollection.getCodecRegistry()).toJson()); 104 Bson order = orderBy(ascending("repositoryId"), descending("eventDate")); 105 106 return auditCollection.find(filter).sort(order).map(MongoDBAuditEntryReader::read).into(new ArrayList<>()); 107 } 108 109 protected Bson addRoots(Bson baseFilter, Set<String> rootPaths, Set<String> collectionSyncRootMemberIds) { 110 List<Bson> rootFilters = new ArrayList<>(); 111 if (!rootPaths.isEmpty()) { 112 rootFilters.add(regex("docPath", "^" + String.join("|^", rootPaths))); 113 } 114 if (!collectionSyncRootMemberIds.isEmpty()) { 115 rootFilters.add(in("docUUID", collectionSyncRootMemberIds)); 116 } 117 if (rootFilters.isEmpty()) { 118 return baseFilter; 119 } else { 120 return and(baseFilter, or(rootFilters)); 121 } 122 } 123 124}