001/*
002 * (C) Copyright 2012 Nuxeo SA (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.hierarchy.permission.adapter;
020
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.logging.log4j.LogManager;
029import org.apache.logging.log4j.Logger;
030import org.nuxeo.drive.adapter.FileSystemItem;
031import org.nuxeo.drive.adapter.FolderItem;
032import org.nuxeo.drive.adapter.ScrollFileSystemItemList;
033import org.nuxeo.drive.adapter.impl.DocumentBackedFolderItem;
034import org.nuxeo.drive.service.NuxeoDriveManager;
035import org.nuxeo.drive.service.SynchronizationRoots;
036import org.nuxeo.ecm.core.api.CoreInstance;
037import org.nuxeo.ecm.core.api.CoreSession;
038import org.nuxeo.ecm.core.api.DocumentModel;
039import org.nuxeo.ecm.core.api.IdRef;
040import org.nuxeo.ecm.core.api.security.SecurityConstants;
041import org.nuxeo.runtime.api.Framework;
042
043/**
044 * User workspace based implementation of the parent {@link FolderItem} of the user's synchronization roots.
045 *
046 * @author Antoine Taillefer
047 */
048public class UserSyncRootParentFolderItem extends DocumentBackedFolderItem {
049
050    private static final Logger log = LogManager.getLogger(UserSyncRootParentFolderItem.class);
051
052    protected boolean isUserWorkspaceSyncRoot = false;
053
054    public UserSyncRootParentFolderItem(String factoryName, DocumentModel doc, FolderItem parentItem,
055            String folderName) {
056        this(factoryName, doc, parentItem, folderName, false);
057    }
058
059    public UserSyncRootParentFolderItem(String factoryName, DocumentModel doc, FolderItem parentItem, String folderName,
060            boolean relaxSyncRootConstraint) {
061        this(factoryName, doc, parentItem, folderName, relaxSyncRootConstraint, true);
062    }
063
064    public UserSyncRootParentFolderItem(String factoryName, DocumentModel doc, FolderItem parentItem, String folderName,
065            boolean relaxSyncRootConstraint, boolean getLockInfo) {
066        super(factoryName, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
067        name = folderName;
068        canRename = false;
069        canDelete = false;
070        isUserWorkspaceSyncRoot = isUserWorkspaceSyncRoot(doc);
071        canCreateChild = isUserWorkspaceSyncRoot;
072        canScrollDescendants = isUserWorkspaceSyncRoot;
073    }
074
075    protected UserSyncRootParentFolderItem() {
076        // Needed for JSON deserialization
077    }
078
079    @Override
080    public void rename(String name) {
081        throw new UnsupportedOperationException("Cannot rename a virtual folder item.");
082    }
083
084    @Override
085    public void delete() {
086        throw new UnsupportedOperationException("Cannot delete a virtual folder item.");
087    }
088
089    @Override
090    public FileSystemItem move(FolderItem dest) {
091        throw new UnsupportedOperationException("Cannot move a virtual folder item.");
092    }
093
094    @Override
095    public List<FileSystemItem> getChildren() {
096
097        if (isUserWorkspaceSyncRoot) {
098            return super.getChildren();
099        } else {
100            List<FileSystemItem> children = new ArrayList<>();
101            Map<String, SynchronizationRoots> syncRootsByRepo = Framework.getService(NuxeoDriveManager.class)
102                                                                         .getSynchronizationRoots(principal);
103            for (Map.Entry<String, SynchronizationRoots> entry : syncRootsByRepo.entrySet()) {
104                CoreSession session = CoreInstance.getCoreSession(entry.getKey(), principal);
105                Set<IdRef> syncRootRefs = entry.getValue().getRefs();
106                Iterator<IdRef> syncRootRefsIt = syncRootRefs.iterator();
107                while (syncRootRefsIt.hasNext()) {
108                    IdRef idRef = syncRootRefsIt.next();
109                    // TODO: ensure sync roots cache is up-to-date if ACL
110                    // change, for now need to check permission
111                    // See https://jira.nuxeo.com/browse/NXP-11146
112                    if (!session.hasPermission(idRef, SecurityConstants.READ)) {
113                        log.debug(
114                                "User {} has no READ access on synchronization root {}, not including it in children.",
115                                session::getPrincipal, () -> idRef);
116                        continue;
117                    }
118                    DocumentModel doc = session.getDocument(idRef);
119                    // Filter by creator
120                    // TODO: allow filtering by dc:creator in
121                    // NuxeoDriveManager#getSynchronizationRoots(NuxeoPrincipal
122                    // principal)
123                    if (session.getPrincipal().getName().equals(doc.getPropertyValue("dc:creator"))) {
124                        // NXP-19442: Avoid useless and costly call to DocumentModel#getLockInfo
125                        FileSystemItem child = getFileSystemItemAdapterService().getFileSystemItem(doc, this, false,
126                                false, false);
127                        if (child == null) {
128                            log.debug(
129                                    "Synchronization root {} cannot be adapted as a FileSystemItem, maybe because user {} doesn't have the required permission on it (default required permission is ReadWrite). Not including it in children.",
130                                    () -> idRef, session::getPrincipal);
131                            continue;
132                        }
133                        log.debug("Including synchronization root {} in children.", idRef);
134                        children.add(child);
135                    }
136                }
137            }
138            Collections.sort(children);
139            return children;
140        }
141    }
142
143    @Override
144    public ScrollFileSystemItemList scrollDescendants(String scrollId, int batchSize, long keepAlive) {
145        if (getCanScrollDescendants()) {
146            return super.scrollDescendants(scrollId, batchSize, keepAlive);
147        } else {
148            throw new UnsupportedOperationException(
149                    "Cannot scroll through the descendants of the user sync root parent folder item, please call getChildren() instead.");
150        }
151    }
152
153    // Override equals and hashCode to explicitly show that their implementation rely on the parent class and doesn't
154    // depend on the fields added to this class.
155    @Override
156    public boolean equals(Object obj) {
157        return super.equals(obj);
158    }
159
160    @Override
161    public int hashCode() {
162        return super.hashCode();
163    }
164
165    private boolean isUserWorkspaceSyncRoot(DocumentModel doc) {
166        NuxeoDriveManager nuxeoDriveManager = Framework.getService(NuxeoDriveManager.class);
167        return nuxeoDriveManager.isSynchronizationRoot(principal, doc);
168    }
169
170}