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