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}