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 *     Mickaƫl Schoentgen <mschoentgen@nuxeo.com>
019 */
020package org.nuxeo.drive.adapter.impl;
021
022import org.apache.commons.lang3.StringUtils;
023import org.nuxeo.drive.adapter.FileItem;
024import org.nuxeo.drive.adapter.FolderItem;
025import org.nuxeo.drive.service.FileSystemItemFactory;
026import org.nuxeo.drive.service.NuxeoDriveManager;
027import org.nuxeo.drive.service.VersioningFileSystemItemFactory;
028import org.nuxeo.ecm.core.api.Blob;
029import org.nuxeo.ecm.core.api.CoreInstance;
030import org.nuxeo.ecm.core.api.CoreSession;
031import org.nuxeo.ecm.core.api.DocumentModel;
032import org.nuxeo.ecm.core.api.NuxeoException;
033import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
034import org.nuxeo.ecm.core.io.download.DownloadService;
035import org.nuxeo.runtime.api.Framework;
036
037/**
038 * {@link DocumentModel} backed implementation of a {@link FileItem}.
039 *
040 * @author Antoine Taillefer
041 */
042public class DocumentBackedFileItem extends AbstractDocumentBackedFileSystemItem implements FileItem {
043
044    protected String downloadURL;
045
046    protected String digestAlgorithm;
047
048    protected String digest;
049
050    /** @since 11.1 */
051    protected long size;
052
053    protected boolean canUpdate;
054
055    protected FileSystemItemFactory factory;
056
057    public DocumentBackedFileItem(FileSystemItemFactory factory, DocumentModel doc) {
058        this(factory, doc, false);
059    }
060
061    public DocumentBackedFileItem(FileSystemItemFactory factory, DocumentModel doc, boolean relaxSyncRootConstraint) {
062        this(factory, doc, relaxSyncRootConstraint, true);
063    }
064
065    public DocumentBackedFileItem(FileSystemItemFactory factory, DocumentModel doc, boolean relaxSyncRootConstraint,
066            boolean getLockInfo) {
067        super(factory.getName(), doc, relaxSyncRootConstraint, getLockInfo);
068        initialize(factory, doc);
069    }
070
071    public DocumentBackedFileItem(FileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc) {
072        this(factory, parentItem, doc, false);
073    }
074
075    public DocumentBackedFileItem(FileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc,
076            boolean relaxSyncRootConstraint) {
077        this(factory, parentItem, doc, relaxSyncRootConstraint, true);
078    }
079
080    public DocumentBackedFileItem(FileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc,
081            boolean relaxSyncRootConstraint, boolean getLockInfo) {
082        super(factory.getName(), parentItem, doc, relaxSyncRootConstraint, getLockInfo);
083        initialize(factory, doc);
084    }
085
086    /**
087     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
088     *             drive level, the {@link VersioningFileSystemItemFactory} is not used anymore
089     */
090    @Deprecated
091    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, DocumentModel doc) {
092        this(factory, doc, false);
093    }
094
095    /**
096     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
097     *             drive level, the {@link VersioningFileSystemItemFactory} is not used anymore
098     */
099    @Deprecated
100    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, DocumentModel doc,
101            boolean relaxSyncRootConstraint) {
102        this(factory, doc, relaxSyncRootConstraint, true);
103    }
104
105    /**
106     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
107     *             drive level, the {@link VersioningFileSystemItemFactory} is not used anymore
108     */
109    @Deprecated
110    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, DocumentModel doc,
111            boolean relaxSyncRootConstraint, boolean getLockInfo) {
112        super(factory.getName(), doc, relaxSyncRootConstraint, getLockInfo);
113        initialize(factory, doc);
114    }
115
116    /**
117     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
118     *             drive level, the {@link VersioningFileSystemItemFactory} is not used anymore
119     */
120    @Deprecated
121    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc) {
122        this(factory, parentItem, doc, false);
123    }
124
125    /**
126     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
127     *             drive level, the {@link VersioningFileSystemItemFactory} is not used anymore
128     */
129    @Deprecated
130    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc,
131            boolean relaxSyncRootConstraint) {
132        this(factory, parentItem, doc, relaxSyncRootConstraint, true);
133    }
134
135    /**
136     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
137     *             drive level, the {@link VersioningFileSystemItemFactory} is not used anymore
138     */
139    @Deprecated
140    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc,
141            boolean relaxSyncRootConstraint, boolean getLockInfo) {
142        super(factory.getName(), parentItem, doc, relaxSyncRootConstraint, getLockInfo);
143        initialize(factory, doc);
144    }
145
146    protected DocumentBackedFileItem() {
147        // Needed for JSON deserialization
148    }
149
150    /*--------------------- FileSystemItem ---------------------*/
151    @Override
152    public void rename(String name) {
153        CoreSession session = CoreInstance.getCoreSession(repositoryName, principal);
154        /* Update doc properties */
155        DocumentModel doc = getDocument(session);
156        BlobHolder bh = getBlobHolder(doc);
157        Blob blob = getBlob(bh);
158        blob.setFilename(name);
159        bh.setBlob(blob);
160        updateDocTitleIfNeeded(doc, name);
161        doc.putContextData(CoreSession.SOURCE, "drive");
162        doc = session.saveDocument(doc);
163        session.save();
164        /* Update FileSystemItem attributes */
165        this.name = name;
166        updateDownloadURL();
167        updateLastModificationDate(doc);
168    }
169
170    /*--------------------- FileItem -----------------*/
171    @Override
172    public Blob getBlob() {
173        CoreSession session = CoreInstance.getCoreSession(repositoryName, principal);
174        DocumentModel doc = getDocument(session);
175        return getBlob(doc);
176    }
177
178    @Override
179    public String getDownloadURL() {
180        return downloadURL;
181    }
182
183    @Override
184    public String getDigestAlgorithm() {
185        return digestAlgorithm;
186    }
187
188    @Override
189    public String getDigest() {
190        return digest;
191    }
192
193    @Override
194    public boolean getCanUpdate() {
195        return canUpdate;
196    }
197
198    /** @since 11.1 */
199    @Override
200    public long getSize() {
201        return size;
202    }
203
204    @Override
205    public void setBlob(Blob blob) {
206        CoreSession session = CoreInstance.getCoreSession(repositoryName, principal);
207        /* Update doc properties */
208        DocumentModel doc = getDocument(session);
209        // If blob's filename is empty, set it to the current name
210        String blobFileName = blob.getFilename();
211        if (StringUtils.isEmpty(blobFileName)) {
212            blob.setFilename(name);
213        } else {
214            updateDocTitleIfNeeded(doc, blobFileName);
215            name = blobFileName;
216            updateDownloadURL();
217        }
218        BlobHolder bh = getBlobHolder(doc);
219        bh.setBlob(blob);
220        doc.putContextData(CoreSession.SOURCE, "drive");
221        doc = session.saveDocument(doc);
222        session.save();
223        /* Update FileSystemItem attributes */
224        updateLastModificationDate(doc);
225        updateDigest(getBlob(doc));
226        updateSize(blob);
227    }
228
229    /*--------------------- Object -----------------*/
230    // Override equals and hashCode to explicitly show that their implementation rely on the parent class and doesn't
231    // depend on the fields added to this class.
232    @Override
233    public boolean equals(Object obj) {
234        return super.equals(obj);
235    }
236
237    @Override
238    public int hashCode() {
239        return super.hashCode();
240    }
241
242    /*--------------------- Protected -----------------*/
243    /**
244     * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at
245     *             drive level, the {@link VersioningFileSystemItemFactory} is not used anymore
246     */
247    @Deprecated
248    protected final void initialize(VersioningFileSystemItemFactory factory, DocumentModel doc) {
249        initialize((FileSystemItemFactory) factory, doc);
250    }
251
252    protected final void initialize(FileSystemItemFactory factory, DocumentModel doc) {
253        this.factory = factory;
254        Blob blob = getBlob(doc);
255        name = getFileName(blob, doc.getTitle());
256        folder = false;
257        updateDownloadURL();
258        updateDigest(blob);
259        updateSize(blob);
260        if (digest == null) {
261            digestAlgorithm = null;
262        }
263        canUpdate = canRename;
264    }
265
266    protected BlobHolder getBlobHolder(DocumentModel doc) {
267        BlobHolder bh = doc.getAdapter(BlobHolder.class);
268        if (bh == null) {
269            throw new NuxeoException(String.format(
270                    "Document %s is not a BlobHolder, it is not adaptable as a FileItem and therefore it cannot not be part of the items to synchronize.",
271                    doc.getId()));
272        }
273        return bh;
274    }
275
276    protected Blob getBlob(BlobHolder blobHolder) {
277        Blob blob = blobHolder.getBlob();
278        if (blob == null) {
279            throw new NuxeoException(
280                    "Document has no blob, it is not adaptable as a FileItem and therefore it cannot not be part of the items to synchronize.");
281        }
282        return blob;
283    }
284
285    protected Blob getBlob(DocumentModel doc) {
286        BlobHolder bh = getBlobHolder(doc);
287        return getBlob(bh);
288    }
289
290    protected String getFileName(Blob blob, String docTitle) {
291        String filename = blob.getFilename();
292        return filename != null ? filename : docTitle;
293    }
294
295    protected void updateDocTitleIfNeeded(DocumentModel doc, String name) {
296        // TODO: not sure about the behavior for the doc title
297        if (this.name.equals(docTitle)) {
298            doc.setPropertyValue("dc:title", name);
299            docTitle = name;
300        }
301    }
302
303    protected void updateDownloadURL() {
304        DownloadService downloadService = Framework.getService(DownloadService.class);
305        // Remove chars that are invalid in filesystem names
306        String escapedFilename = name.replaceAll("(/|\\\\|\\*|<|>|\\?|\"|:|\\|)", "-");
307        downloadURL = downloadService.getDownloadUrl(repositoryName, docId, DownloadService.BLOBHOLDER_0,
308                escapedFilename);
309    }
310
311    protected void updateDigest(Blob blob) {
312        String blobDigest = blob.getDigest();
313        if (StringUtils.isEmpty(blobDigest)) {
314            // Force md5 digest algorithm and digest computation for a StringBlob,
315            // typically the note:note property of a Note document
316            digestAlgorithm = FileSystemItemHelper.MD5_DIGEST_ALGORITHM;
317            digest = FileSystemItemHelper.getMD5Digest(blob);
318        } else {
319            digestAlgorithm = blob.getDigestAlgorithm();
320            digest = blobDigest;
321        }
322    }
323
324    /** @since 11.1 */
325    protected void updateSize(Blob blob) {
326        long length = blob.getLength();
327        size = length > 0 ? length : 0;
328    }
329
330    protected NuxeoDriveManager getNuxeoDriveManager() {
331        return Framework.getService(NuxeoDriveManager.class);
332    }
333
334    /*---------- Needed for JSON deserialization ----------*/
335    protected void setDownloadURL(String downloadURL) {
336        this.downloadURL = downloadURL;
337    }
338
339    protected void setDigestAlgorithm(String digestAlgorithm) {
340        this.digestAlgorithm = digestAlgorithm;
341    }
342
343    protected void setDigest(String digest) {
344        this.digest = digest;
345    }
346
347    protected void setCanUpdate(boolean canUpdate) {
348        this.canUpdate = canUpdate;
349    }
350
351    /** @since 11.1 */
352    protected void setSize(long size) {
353        this.size = size;
354    }
355
356}