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