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