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
141        // Check Folderish or BlobHolder with a blob
142        if (!doc.isFolder()) {
143            if (blob == null) {
144                log.debug(
145                        "Document {} is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.",
146                        doc::getId);
147                return false;
148            }
149
150            // Check for blobs backed by extended blob providers (ex: Google Drive)
151            BlobManager blobManager = Framework.getService(BlobManager.class);
152            BlobProvider blobProvider = blobManager.getBlobProvider(blob);
153            if (blobProvider != null && !blobProvider.supportsSync()) {
154                log.debug(
155                        "Blob for Document {} is backed by a BlobProvider preventing sync, it cannot be adapted as a FileSystemItem.",
156                        doc::getId);
157                return false;
158            } else if (blobProvider == null && !doc.getType().equals("Note")) {
159                log.debug(
160                        "Document {} has no BlobProvider and is not a Note, it cannot be adapted as a FileSystemItem.",
161                        doc::getId);
162                return false;
163            }
164        }
165
166        if (!relaxSyncRootConstraint && doc.isFolder()) {
167            // Check not a synchronization root registered for the current user
168            NuxeoDriveManager nuxeoDriveManager = Framework.getService(NuxeoDriveManager.class);
169            NuxeoPrincipal principal = doc.getPrincipal();
170            boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc);
171            if (isSyncRoot) {
172                log.debug(
173                        "Document {} is a registered synchronization root for user {}, it cannot be adapted as a DefaultFileSystemItem.",
174                        doc::getId, principal::getName);
175                return false;
176            }
177        }
178        return true;
179    }
180
181    @Override
182    protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem,
183            boolean relaxSyncRootConstraint, boolean getLockInfo) {
184        // Doc is either Folderish
185        if (doc.isFolder()) {
186            if (forceParentItem) {
187                return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
188            } else {
189                return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint, getLockInfo);
190            }
191        }
192        // or a BlobHolder with a blob
193        else {
194            if (forceParentItem) {
195                return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
196            } else {
197                return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint, getLockInfo);
198            }
199        }
200    }
201
202    /*--------------------------- FileSystemItemVersioning -------------------------*/
203    /**
204     * Need to version the doc if the current contributor is different from the last contributor or if the last
205     * modification was done more than {@link #versioningDelay} seconds ago.
206     *
207     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
208     *             drive level, this method is not used anymore
209     */
210    @Override
211    @Deprecated
212    public boolean needsVersioning(DocumentModel doc) {
213
214        String lastContributor = (String) doc.getPropertyValue("dc:lastContributor");
215        NuxeoPrincipal principal = doc.getPrincipal();
216        boolean contributorChanged = !principal.getName().equals(lastContributor);
217        if (contributorChanged) {
218            log.debug(
219                    "Contributor {} is different from the last contributor {} => will create a version of the document.",
220                    principal, lastContributor);
221            return true;
222        }
223        Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified");
224        if (lastModificationDate == null) {
225            log.debug("Last modification date is null => will create a version of the document.");
226            return true;
227        }
228        long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis();
229        long versioningDelayMillis = (long) getVersioningDelay() * 1000;
230        if (lastModified > versioningDelayMillis) {
231            log.debug(
232                    "Last modification was done {} milliseconds ago, this is more than the versioning delay {} milliseconds => will create a version of the document.",
233                    lastModified, versioningDelayMillis);
234            return true;
235        }
236        log.debug(
237                "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.",
238                principal, lastModified, versioningDelayMillis);
239        return false;
240    }
241
242    /**
243     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
244     */
245    @Override
246    @Deprecated
247    public double getVersioningDelay() {
248        return versioningDelay;
249    }
250
251    /**
252     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
253     */
254    @Override
255    @Deprecated
256    public void setVersioningDelay(double versioningDelay) {
257        this.versioningDelay = versioningDelay;
258    }
259
260    /**
261     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
262     */
263    @Override
264    @Deprecated
265    public VersioningOption getVersioningOption() {
266        return versioningOption;
267    }
268
269    /**
270     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
271     */
272    @Override
273    @Deprecated
274    public void setVersioningOption(VersioningOption versioningOption) {
275        this.versioningOption = versioningOption;
276    }
277
278    /*--------------------------- Protected ---------------------------------*/
279    protected Blob getBlob(DocumentModel doc) {
280        BlobHolder bh = doc.getAdapter(BlobHolder.class);
281        if (bh == null) {
282            log.debug("Document {} is not a BlobHolder.", doc::getId);
283            return null;
284        }
285        Blob blob = bh.getBlob();
286        if (blob == null) {
287            log.debug("Document {} is a BlobHolder without a blob.", doc::getId);
288        }
289        return blob;
290    }
291
292}