001/*
002 * (C) Copyright 2012-2018 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.service.impl;
020
021import java.util.Calendar;
022import java.util.Map;
023
024import org.apache.commons.lang3.StringUtils;
025import org.apache.logging.log4j.LogManager;
026import org.apache.logging.log4j.Logger;
027import org.nuxeo.drive.adapter.FileSystemItem;
028import org.nuxeo.drive.adapter.FolderItem;
029import org.nuxeo.drive.adapter.impl.DocumentBackedFileItem;
030import org.nuxeo.drive.adapter.impl.DocumentBackedFolderItem;
031import org.nuxeo.drive.service.FileSystemItemFactory;
032import org.nuxeo.drive.service.NuxeoDriveManager;
033import org.nuxeo.drive.service.VersioningFileSystemItemFactory;
034import org.nuxeo.ecm.collections.api.CollectionConstants;
035import org.nuxeo.ecm.core.api.Blob;
036import org.nuxeo.ecm.core.api.DocumentModel;
037import org.nuxeo.ecm.core.api.NuxeoException;
038import org.nuxeo.ecm.core.api.NuxeoPrincipal;
039import org.nuxeo.ecm.core.api.VersioningOption;
040import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
041import org.nuxeo.ecm.core.blob.BlobManager;
042import org.nuxeo.ecm.core.blob.BlobProvider;
043import org.nuxeo.runtime.api.Framework;
044
045/**
046 * Default implementation of a {@link FileSystemItemFactory}. It is {@link DocumentModel} backed and is the one used by
047 * Nuxeo Drive.
048 *
049 * @author Antoine Taillefer
050 */
051public class DefaultFileSystemItemFactory extends AbstractFileSystemItemFactory
052        implements VersioningFileSystemItemFactory {
053
054    private static final Logger log = LogManager.getLogger(DefaultFileSystemItemFactory.class);
055
056    /**
057     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
058     */
059    @Deprecated
060    protected static final String VERSIONING_DELAY_PARAM = "versioningDelay";
061
062    /**
063     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
064     */
065    @Deprecated
066    protected static final String VERSIONING_OPTION_PARAM = "versioningOption";
067
068    /**
069     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
070     */
071    @Deprecated
072    // Versioning delay in seconds, default value: 1 hour
073    protected double versioningDelay = 3600;
074
075    /**
076     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
077     */
078    @Deprecated
079    // Versioning option, default value: MINOR
080    protected VersioningOption versioningOption = VersioningOption.MINOR;
081
082    /*--------------------------- AbstractFileSystemItemFactory -------------------------*/
083    @Override
084    public void handleParameters(Map<String, String> parameters) {
085        String versioningDelayParam = parameters.get(VERSIONING_DELAY_PARAM);
086        if (!StringUtils.isEmpty(versioningDelayParam)) {
087            versioningDelay = Double.parseDouble(versioningDelayParam);
088        }
089        String versioningOptionParam = parameters.get(DefaultFileSystemItemFactory.VERSIONING_OPTION_PARAM);
090        if (!StringUtils.isEmpty(versioningOptionParam)) {
091            versioningOption = VersioningOption.valueOf(versioningOptionParam);
092        }
093    }
094
095    /**
096     * The default factory considers that a {@link DocumentModel} is adaptable as a {@link FileSystemItem} if:
097     * <ul>
098     * <li>It is not a version</li>
099     * <li>AND it is not HiddenInNavigation</li>
100     * <li>AND it is not in the trash, unless {@code includeDeleted} is true</li>
101     * <li>AND it is Folderish or it can be adapted as a {@link BlobHolder} with a blob</li>
102     * <li>AND its blob is not backed by an extended blob provider</li>
103     * <li>AND it is not a synchronization root registered for the current user, unless {@code relaxSyncRootConstraint}
104     * is true</li>
105     * </ul>
106     */
107    @Override
108    public boolean isFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint) {
109        // Check version
110        if (doc.isVersion()) {
111            log.debug("Document {} is a version, it cannot be adapted as a FileSystemItem.", doc::getId);
112            return false;
113        }
114        // Check Collections
115        if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) {
116            log.debug(
117                    "Document {} is the collection root folder (type={}, path={}), it cannot be adapted as a FileSystemItem.",
118                    doc::getId, () -> CollectionConstants.COLLECTIONS_TYPE, doc::getPathAsString);
119            return false;
120        }
121        // Check HiddenInNavigation
122        if (doc.hasFacet("HiddenInNavigation")) {
123            log.debug("Document {} is HiddenInNavigation, it cannot be adapted as a FileSystemItem.", doc::getId);
124            return false;
125        }
126        // Check if document is in the trash
127        if (!includeDeleted && doc.isTrashed()) {
128            log.debug("Document {} is trashed, it cannot be adapted as a FileSystemItem.", doc::getId);
129            return false;
130        }
131        // Try to fetch blob
132        Blob blob = null;
133        try {
134            blob = getBlob(doc);
135        } catch (NuxeoException e) {
136            log.error("Error while fetching blob for document {}, it cannot be adapted as a FileSystemItem.",
137                    doc::getId, () -> e);
138            return false;
139        }
140        // Check Folderish or BlobHolder with a blob
141        if (!doc.isFolder() && blob == null) {
142            log.debug(
143                    "Document {} is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.",
144                    doc::getId);
145            return false;
146        }
147
148        // Check for blobs backed by extended blob providers (ex: Google Drive)
149        if (!doc.isFolder()) {
150            BlobManager blobManager = Framework.getService(BlobManager.class);
151            BlobProvider blobProvider = blobManager.getBlobProvider(blob);
152            if (blobProvider != null
153                    && (!blobProvider.supportsUserUpdate() || blobProvider.getBinaryManager() == null)) {
154                log.debug(
155                        "Blob for Document {} is backed by a BlobProvider preventing updates, it cannot be adapted as a FileSystemItem.",
156                        doc::getId);
157                return false;
158            }
159        }
160
161        if (!relaxSyncRootConstraint && doc.isFolder()) {
162            // Check not a synchronization root registered for the current user
163            NuxeoDriveManager nuxeoDriveManager = Framework.getService(NuxeoDriveManager.class);
164            NuxeoPrincipal principal = doc.getCoreSession().getPrincipal();
165            boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc);
166            if (isSyncRoot) {
167                log.debug(
168                        "Document {} is a registered synchronization root for user {}, it cannot be adapted as a DefaultFileSystemItem.",
169                        doc::getId, principal::getName);
170                return false;
171            }
172        }
173        return true;
174    }
175
176    @Override
177    protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem,
178            boolean relaxSyncRootConstraint, boolean getLockInfo) {
179        // Doc is either Folderish
180        if (doc.isFolder()) {
181            if (forceParentItem) {
182                return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
183            } else {
184                return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint, getLockInfo);
185            }
186        }
187        // or a BlobHolder with a blob
188        else {
189            if (forceParentItem) {
190                return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
191            } else {
192                return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint, getLockInfo);
193            }
194        }
195    }
196
197    /*--------------------------- FileSystemItemVersioning -------------------------*/
198    /**
199     * Need to version the doc if the current contributor is different from the last contributor or if the last
200     * modification was done more than {@link #versioningDelay} seconds ago.
201     *
202     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
203     *             drive level, this method is not used anymore
204     */
205    @Override
206    @Deprecated
207    public boolean needsVersioning(DocumentModel doc) {
208
209        String lastContributor = (String) doc.getPropertyValue("dc:lastContributor");
210        NuxeoPrincipal principal = doc.getCoreSession().getPrincipal();
211        boolean contributorChanged = !principal.getName().equals(lastContributor);
212        if (contributorChanged) {
213            log.debug(
214                    "Contributor {} is different from the last contributor {} => will create a version of the document.",
215                    principal, lastContributor);
216            return true;
217        }
218        Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified");
219        if (lastModificationDate == null) {
220            log.debug("Last modification date is null => will create a version of the document.");
221            return true;
222        }
223        long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis();
224        long versioningDelayMillis = (long) getVersioningDelay() * 1000;
225        if (lastModified > versioningDelayMillis) {
226            log.debug(
227                    "Last modification was done {} milliseconds ago, this is more than the versioning delay {} milliseconds => will create a version of the document.",
228                    lastModified, versioningDelayMillis);
229            return true;
230        }
231        log.debug(
232                "Contributor {} is the last contributor and last modification was done {} milliseconds ago, this is less than the versioning delay {} milliseconds => will not create a version of the document.",
233                principal, lastModified, versioningDelayMillis);
234        return false;
235    }
236
237    /**
238     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
239     */
240    @Override
241    @Deprecated
242    public double getVersioningDelay() {
243        return versioningDelay;
244    }
245
246    /**
247     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
248     */
249    @Override
250    @Deprecated
251    public void setVersioningDelay(double versioningDelay) {
252        this.versioningDelay = versioningDelay;
253    }
254
255    /**
256     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
257     */
258    @Override
259    @Deprecated
260    public VersioningOption getVersioningOption() {
261        return versioningOption;
262    }
263
264    /**
265     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
266     */
267    @Override
268    @Deprecated
269    public void setVersioningOption(VersioningOption versioningOption) {
270        this.versioningOption = versioningOption;
271    }
272
273    /*--------------------------- Protected ---------------------------------*/
274    protected Blob getBlob(DocumentModel doc) {
275        BlobHolder bh = doc.getAdapter(BlobHolder.class);
276        if (bh == null) {
277            log.debug("Document {} is not a BlobHolder.", doc::getId);
278            return null;
279        }
280        Blob blob = bh.getBlob();
281        if (blob == null) {
282            log.debug("Document {} is a BlobHolder without a blob.", doc::getId);
283        }
284        return blob;
285    }
286
287}