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.service.impl;
020
021import java.security.Principal;
022import java.util.Calendar;
023import java.util.Map;
024
025import org.apache.commons.lang.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.LifeCycleConstants;
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 implements
052        VersioningFileSystemItemFactory {
053
054    private static final Log log = LogFactory.getLog(DefaultFileSystemItemFactory.class);
055
056    protected static final String VERSIONING_DELAY_PARAM = "versioningDelay";
057
058    protected static final String VERSIONING_OPTION_PARAM = "versioningOption";
059
060    // Versioning delay in seconds, default value: 1 hour
061    protected double versioningDelay = 3600;
062
063    // Versioning option, default value: MINOR
064    protected VersioningOption versioningOption = VersioningOption.MINOR;
065
066    /*--------------------------- AbstractFileSystemItemFactory -------------------------*/
067    @Override
068    public void handleParameters(Map<String, String> parameters) {
069        String versioningDelayParam = parameters.get(VERSIONING_DELAY_PARAM);
070        if (!StringUtils.isEmpty(versioningDelayParam)) {
071            versioningDelay = Double.parseDouble(versioningDelayParam);
072        }
073        String versioningOptionParam = parameters.get(DefaultFileSystemItemFactory.VERSIONING_OPTION_PARAM);
074        if (!StringUtils.isEmpty(versioningOptionParam)) {
075            versioningOption = VersioningOption.valueOf(versioningOptionParam);
076        }
077    }
078
079    /**
080     * The default factory considers that a {@link DocumentModel} is adaptable as a {@link FileSystemItem} if:
081     * <ul>
082     * <li>It is not a version</li>
083     * <li>AND it is not HiddenInNavigation</li>
084     * <li>AND it is not in the "deleted" life cycle state, unless {@code includeDeleted} is true</li>
085     * <li>AND it is Folderish or it can be adapted as a {@link BlobHolder} with a blob</li>
086     * <li>AND it is not a synchronization root registered for the current user, unless {@code relaxSyncRootConstraint}
087     * is true</li>
088     * </ul>
089     */
090    @Override
091    public boolean isFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint) {
092        // Check version
093        if (doc.isVersion()) {
094            if (log.isDebugEnabled()) {
095                log.debug(String.format("Document %s is a version, it cannot be adapted as a FileSystemItem.",
096                        doc.getId()));
097            }
098            return false;
099        }
100        // Check Collections
101        if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) {
102            if (log.isDebugEnabled()) {
103                log.debug(String.format(
104                        "Document %s is the collection root folder (type=%s, path=%s), it cannot be adapted as a FileSystemItem.",
105                        doc.getId(), CollectionConstants.COLLECTIONS_TYPE, doc.getPathAsString()));
106            }
107            return false;
108        }
109        // Check HiddenInNavigation
110        if (doc.hasFacet("HiddenInNavigation")) {
111            if (log.isDebugEnabled()) {
112                log.debug(String.format("Document %s is HiddenInNavigation, it cannot be adapted as a FileSystemItem.",
113                        doc.getId()));
114            }
115            return false;
116        }
117        // Check "deleted" life cycle state
118        if (!includeDeleted && LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) {
119            if (log.isDebugEnabled()) {
120                log.debug(String.format(
121                        "Document %s is in the '%s' life cycle state, it cannot be adapted as a FileSystemItem.",
122                        doc.getId(), LifeCycleConstants.DELETED_STATE));
123            }
124            return false;
125        }
126        // Check Folderish or BlobHolder with a blob
127        if (!doc.isFolder() && !hasBlob(doc)) {
128            if (log.isDebugEnabled()) {
129                log.debug(String.format(
130                        "Document %s is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.",
131                        doc.getId()));
132            }
133            return false;
134        }
135
136        // Check for blobs backed by extended blob providers (ex: Google Drive)
137        if (!doc.isFolder()) {
138            BlobManager blobManager = Framework.getService(BlobManager.class);
139            BlobHolder bh = doc.getAdapter(BlobHolder.class);
140            BlobProvider blobProvider = blobManager.getBlobProvider(bh.getBlob());
141            if (blobProvider != null && (!blobProvider.supportsUserUpdate() || blobProvider.getBinaryManager() == null)) {
142                if (log.isDebugEnabled()) {
143                    log.debug(String.format(
144                            "Blob for Document %s is backed by a BlobProvider preventing updates, it cannot be adapted as a FileSystemItem.",
145                            doc.getId()));
146                }
147                return false;
148            }
149        }
150
151        if (!relaxSyncRootConstraint && doc.isFolder()) {
152            // Check not a synchronization root registered for the current user
153            NuxeoDriveManager nuxeoDriveManager = Framework.getLocalService(NuxeoDriveManager.class);
154            Principal principal = doc.getCoreSession().getPrincipal();
155            boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc);
156            if (isSyncRoot) {
157                if (log.isDebugEnabled()) {
158                    log.debug(String.format(
159                            "Document %s is a registered synchronization root for user %s, it cannot be adapted as a DefaultFileSystemItem.",
160                            doc.getId(), principal.getName()));
161                }
162                return false;
163            }
164        }
165        return true;
166    }
167
168    @Override
169    protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem,
170            boolean relaxSyncRootConstraint, boolean getLockInfo) {
171        // Doc is either Folderish
172        if (doc.isFolder()) {
173            if (forceParentItem) {
174                return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
175            } else {
176                return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint, getLockInfo);
177            }
178        }
179        // or a BlobHolder with a blob
180        else {
181            if (forceParentItem) {
182                return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint, getLockInfo);
183            } else {
184                return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint, getLockInfo);
185            }
186        }
187    }
188
189    /*--------------------------- FileSystemItemVersioning -------------------------*/
190    /**
191     * Need to version the doc if the current contributor is different from the last contributor or if the last
192     * modification was done more than {@link #versioningDelay} seconds ago.
193     */
194    @Override
195    public boolean needsVersioning(DocumentModel doc) {
196
197        String lastContributor = (String) doc.getPropertyValue("dc:lastContributor");
198        Principal principal = doc.getCoreSession().getPrincipal();
199        boolean contributorChanged = !principal.getName().equals(lastContributor);
200        if (contributorChanged) {
201            if (log.isDebugEnabled()) {
202                log.debug(String.format(
203                        "Contributor %s is different from the last contributor %s => will create a version of the document.",
204                        principal.getName(), lastContributor));
205            }
206            return true;
207        }
208        Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified");
209        if (lastModificationDate == null) {
210            log.debug("Last modification date is null => will not create a version of the document.");
211            return true;
212        }
213        long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis();
214        long versioningDelayMillis = (long) getVersioningDelay() * 1000;
215        if (lastModified > versioningDelayMillis) {
216            if (log.isDebugEnabled()) {
217                log.debug(String.format(
218                        "Last modification was done %d milliseconds ago, this is more than the versioning delay %d milliseconds => will create a version of the document.",
219                        lastModified, versioningDelayMillis));
220            }
221            return true;
222        }
223        if (log.isDebugEnabled()) {
224            log.debug(String.format(
225                    "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.",
226                    principal.getName(), lastModified, versioningDelayMillis));
227        }
228        return false;
229    }
230
231    @Override
232    public double getVersioningDelay() {
233        return versioningDelay;
234    }
235
236    @Override
237    public void setVersioningDelay(double versioningDelay) {
238        this.versioningDelay = versioningDelay;
239    }
240
241    @Override
242    public VersioningOption getVersioningOption() {
243        return versioningOption;
244    }
245
246    @Override
247    public void setVersioningOption(VersioningOption versioningOption) {
248        this.versioningOption = versioningOption;
249    }
250
251    /*--------------------------- Protected ---------------------------------*/
252    protected boolean hasBlob(DocumentModel doc) {
253        BlobHolder bh = doc.getAdapter(BlobHolder.class);
254        if (bh == null) {
255            if (log.isDebugEnabled()) {
256                log.debug(String.format("Document %s is not a BlobHolder.", doc.getId()));
257            }
258            return false;
259        }
260        Blob blob = bh.getBlob();
261        if (blob == null) {
262            if (log.isDebugEnabled()) {
263                log.debug(String.format("Document %s is a BlobHolder without a blob.", doc.getId()));
264            }
265            return false;
266        }
267        return true;
268    }
269
270}