001/* 002 * (C) Copyright 2017 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 * Contributors: 017 * Antoine Taillefer <ataillefer@nuxeo.com> 018 */ 019package org.nuxeo.drive.listener; 020 021import java.util.ArrayList; 022import java.util.List; 023import java.util.stream.Collectors; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027import org.nuxeo.drive.service.NuxeoDriveEvents; 028import org.nuxeo.ecm.core.api.CoreInstance; 029import org.nuxeo.ecm.core.api.CoreSession; 030import org.nuxeo.ecm.core.api.DocumentModel; 031import org.nuxeo.ecm.core.api.DocumentModelList; 032import org.nuxeo.ecm.core.api.repository.RepositoryManager; 033import org.nuxeo.ecm.core.event.Event; 034import org.nuxeo.ecm.core.event.EventBundle; 035import org.nuxeo.ecm.core.event.EventContext; 036import org.nuxeo.ecm.core.event.EventService; 037import org.nuxeo.ecm.core.event.PostCommitFilteringEventListener; 038import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 039import org.nuxeo.ecm.core.query.sql.NXQL; 040import org.nuxeo.ecm.platform.usermanager.UserManager; 041import org.nuxeo.ecm.platform.usermanager.UserManagerImpl; 042import org.nuxeo.runtime.api.Framework; 043 044/** 045 * Post-commit asynchronous listener that handles group change events fired by the {@link UserManager}. 046 * <p> 047 * For all the documents carrying an ACL impacted by a changed group or one of its ancestors it fires the 048 * {@link NuxeoDriveEvents#GROUP_UPDATED} event that is handled by the synchronous 049 * {@link NuxeoDriveFileSystemDeletionListener}. 050 * 051 * @since 9.2 052 */ 053public class NuxeoDriveGroupUpdateListener implements PostCommitFilteringEventListener { 054 055 protected static final Log log = LogFactory.getLog(NuxeoDriveFileSystemDeletionListener.class); 056 057 @Override 058 public boolean acceptEvent(Event event) { 059 return event.getContext() != null && UserManagerImpl.USER_GROUP_CATEGORY.equals( 060 event.getContext().getProperty(DocumentEventContext.CATEGORY_PROPERTY_KEY)); 061 } 062 063 @Override 064 public void handleEvent(EventBundle events) { 065 for (Event event : events) { 066 EventContext context = event.getContext(); 067 if (context == null) { 068 continue; 069 } 070 String groupName = (String) context.getProperty(UserManagerImpl.ID_PROPERTY_KEY); 071 if (groupName == null) { 072 continue; 073 } 074 if (log.isDebugEnabled()) { 075 log.debug(String.format("NuxeoDriveGroupUpdateListener handling %s event for group %s", event.getName(), 076 groupName)); 077 } 078 List<String> groupNames = getAllGroupNames(groupName, context); 079 handleUpdatedGroups(groupNames); 080 } 081 } 082 083 /** 084 * Returns a list containing the names of the given group and all its ancestor groups. 085 */ 086 @SuppressWarnings("unchecked") 087 protected List<String> getAllGroupNames(String groupName, EventContext context) { 088 List<String> groupNames = new ArrayList<>(); 089 groupNames.add(groupName); 090 // Get ancestor groups from the event context or compute them if not provided 091 List<String> ancestorGroups = (List<String>) context.getProperty(UserManagerImpl.ANCESTOR_GROUPS_PROPERTY_KEY); 092 if (ancestorGroups != null) { 093 groupNames.addAll(ancestorGroups); 094 } else { 095 groupNames.addAll(Framework.getService(UserManager.class).getAncestorGroups(groupName)); 096 } 097 return groupNames; 098 } 099 100 protected void handleUpdatedGroups(List<String> groupNames) { 101 RepositoryManager repositoryManager = Framework.getService(RepositoryManager.class); 102 for (String repositoryName : repositoryManager.getRepositoryNames()) { 103 CoreInstance.doPrivileged(repositoryName, (CoreSession session) -> { 104 DocumentModelList impactedDocuments = getImpactedDocuments(session, groupNames); 105 impactedDocuments.forEach(doc -> fireGroupUpdatedEvent(session, doc)); 106 }); 107 } 108 } 109 110 /** 111 * Returns the list of documents carrying an ACL impacted by one of the given group names. 112 */ 113 protected DocumentModelList getImpactedDocuments(CoreSession session, List<String> groupNames) { 114 String groups = groupNames.stream().map(NXQL::escapeString).collect(Collectors.joining(",")); 115 String query = "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'deleted' AND ecm:isVersion = 0 AND ecm:acl/*/principal IN (" 116 + groups + ")"; 117 return session.query(query); 118 } 119 120 protected void fireGroupUpdatedEvent(CoreSession session, DocumentModel source) { 121 EventContext context = new DocumentEventContext(session, session.getPrincipal(), source); 122 Event event = context.newEvent(NuxeoDriveEvents.GROUP_UPDATED); 123 Framework.getService(EventService.class).fireEvent(event); 124 } 125 126}