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.security.Principal;
022import java.util.Calendar;
023import java.util.Map;
024
025import org.apache.commons.lang3.StringUtils;
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.drive.adapter.FileSystemItem;
029import org.nuxeo.drive.adapter.FolderItem;
030import org.nuxeo.drive.adapter.impl.DocumentBackedFileItem;
031import org.nuxeo.drive.adapter.impl.DocumentBackedFolderItem;
032import org.nuxeo.drive.service.FileSystemItemFactory;
033import org.nuxeo.drive.service.NuxeoDriveManager;
034import org.nuxeo.drive.service.VersioningFileSystemItemFactory;
035import org.nuxeo.ecm.collections.api.CollectionConstants;
036import org.nuxeo.ecm.core.api.Blob;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.core.api.NuxeoException;
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 Log log = LogFactory.getLog(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            if (log.isDebugEnabled()) {
112                log.debug(String.format("Document %s is a version, it cannot be adapted as a FileSystemItem.",
113                        doc.getId()));
114            }
115            return false;
116        }
117        // Check Collections
118        if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) {
119            if (log.isDebugEnabled()) {
120                log.debug(String.format(
121                        "Document %s is the collection root folder (type=%s, path=%s), it cannot be adapted as a FileSystemItem.",
122                        doc.getId(), CollectionConstants.COLLECTIONS_TYPE, doc.getPathAsString()));
123            }
124            return false;
125        }
126        // Check HiddenInNavigation
127        if (doc.hasFacet("HiddenInNavigation")) {
128            if (log.isDebugEnabled()) {
129                log.debug(String.format("Document %s is HiddenInNavigation, it cannot be adapted as a FileSystemItem.",
130                        doc.getId()));
131            }
132            return false;
133        }
134        // Check if document is in the trash
135        if (!includeDeleted && doc.isTrashed()) {
136            if (log.isDebugEnabled()) {
137                log.debug(String.format("Document %s is trashed, it cannot be adapted as a FileSystemItem.",
138                        doc.getId()));
139            }
140            return false;
141        }
142        // Try to fetch blob
143        Blob blob = null;
144        try {
145            blob = getBlob(doc);
146        } catch (NuxeoException e) {
147            log.error(String.format(
148                    "Error while fetching blob for document %s, it cannot be adapted as a FileSystemItem.",
149                    doc.getId()), e);
150            return false;
151        }
152        // Check Folderish or BlobHolder with a blob
153        if (!doc.isFolder() && blob == null) {
154            if (log.isDebugEnabled()) {
155                log.debug(String.format(
156                        "Document %s is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.",
157                        doc.getId()));
158            }
159            return false;
160        }
161
162        // Check for blobs backed by extended blob providers (ex: Google Drive)
163        if (!doc.isFolder()) {
164            BlobManager blobManager = Framework.getService(BlobManager.class);
165            BlobProvider blobProvider = blobManager.getBlobProvider(blob);
166            if (blobProvider != null
167                    && (!blobProvider.supportsUserUpdate() || blobProvider.getBinaryManager() == null)) {
168                if (log.isDebugEnabled()) {
169                    log.debug(String.format(
170                            "Blob for Document %s is backed by a BlobProvider preventing updates, it cannot be adapted as a FileSystemItem.",
171                            doc.getId()));
172                }
173                return false;
174            }
175        }
176
177        if (!relaxSyncRootConstraint && doc.isFolder()) {
178            // Check not a synchronization root registered for the current user
179            NuxeoDriveManager nuxeoDriveManager = Framework.getService(NuxeoDriveManager.class);
180            Principal principal = doc.getCoreSession().getPrincipal();
181            boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc);
182            if (isSyncRoot) {
183                if (log.isDebugEnabled()) {
184                    log.debug(String.format(
185                            "Document %s is a registered synchronization root for user %s, it cannot be adapted as a DefaultFileSystemItem.",
186                            doc.getId(), principal.getName()));
187                }
188                return false;
189            }
190        }
191        return true;
192    }
193
194    @Override
195    protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem,
196            boolean relaxSyncRootConstraint, boolean getLockInfo) {
197        // Doc is either Folderish
198        if (doc.isFolder()) {
199            if (forceParentItem) {
200                return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
201            } else {
202                return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint, getLockInfo);
203            }
204        }
205        // or a BlobHolder with a blob
206        else {
207            if (forceParentItem) {
208                return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
209            } else {
210                return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint, getLockInfo);
211            }
212        }
213    }
214
215    /*--------------------------- FileSystemItemVersioning -------------------------*/
216    /**
217     * Need to version the doc if the current contributor is different from the last contributor or if the last
218     * modification was done more than {@link #versioningDelay} seconds ago.
219     *
220     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
221     *             drive level, this method is not used anymore
222     */
223    @Override
224    @Deprecated
225    public boolean needsVersioning(DocumentModel doc) {
226
227        String lastContributor = (String) doc.getPropertyValue("dc:lastContributor");
228        Principal principal = doc.getCoreSession().getPrincipal();
229        boolean contributorChanged = !principal.getName().equals(lastContributor);
230        if (contributorChanged) {
231            if (log.isDebugEnabled()) {
232                log.debug(String.format(
233                        "Contributor %s is different from the last contributor %s => will create a version of the document.",
234                        principal.getName(), lastContributor));
235            }
236            return true;
237        }
238        Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified");
239        if (lastModificationDate == null) {
240            log.debug("Last modification date is null => will create a version of the document.");
241            return true;
242        }
243        long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis();
244        long versioningDelayMillis = (long) getVersioningDelay() * 1000;
245        if (lastModified > versioningDelayMillis) {
246            if (log.isDebugEnabled()) {
247                log.debug(String.format(
248                        "Last modification was done %d milliseconds ago, this is more than the versioning delay %d milliseconds => will create a version of the document.",
249                        lastModified, versioningDelayMillis));
250            }
251            return true;
252        }
253        if (log.isDebugEnabled()) {
254            log.debug(String.format(
255                    "Contributor %s is the last contributor and last modification was done %d milliseconds ago, this is less than the versioning delay %d milliseconds => will not create a version of the document.",
256                    principal.getName(), lastModified, versioningDelayMillis));
257        }
258        return false;
259    }
260
261    /**
262     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
263     */
264    @Override
265    @Deprecated
266    public double getVersioningDelay() {
267        return versioningDelay;
268    }
269
270    /**
271     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
272     */
273    @Override
274    @Deprecated
275    public void setVersioningDelay(double versioningDelay) {
276        this.versioningDelay = versioningDelay;
277    }
278
279    /**
280     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
281     */
282    @Override
283    @Deprecated
284    public VersioningOption getVersioningOption() {
285        return versioningOption;
286    }
287
288    /**
289     * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies
290     */
291    @Override
292    @Deprecated
293    public void setVersioningOption(VersioningOption versioningOption) {
294        this.versioningOption = versioningOption;
295    }
296
297    /*--------------------------- Protected ---------------------------------*/
298    protected Blob getBlob(DocumentModel doc) {
299        BlobHolder bh = doc.getAdapter(BlobHolder.class);
300        if (bh == null) {
301            if (log.isDebugEnabled()) {
302                log.debug(String.format("Document %s is not a BlobHolder.", doc.getId()));
303            }
304            return null;
305        }
306        Blob blob = bh.getBlob();
307        if (blob == null) {
308            if (log.isDebugEnabled()) {
309                log.debug(String.format("Document %s is a BlobHolder without a blob.", doc.getId()));
310            }
311        }
312        return blob;
313    }
314
315}