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