001/*
002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Antoine Taillefer <ataillefer@nuxeo.com>
016 */
017package org.nuxeo.drive.adapter.impl;
018
019import org.apache.commons.lang.StringUtils;
020import org.nuxeo.drive.adapter.FileItem;
021import org.nuxeo.drive.adapter.FolderItem;
022import org.nuxeo.drive.service.NuxeoDriveManager;
023import org.nuxeo.drive.service.VersioningFileSystemItemFactory;
024import org.nuxeo.ecm.core.api.Blob;
025import org.nuxeo.ecm.core.api.CoreInstance;
026import org.nuxeo.ecm.core.api.CoreSession;
027import org.nuxeo.ecm.core.api.DocumentModel;
028import org.nuxeo.ecm.core.api.NuxeoException;
029import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
030import org.nuxeo.ecm.core.io.download.DownloadService;
031import org.nuxeo.runtime.api.Framework;
032
033/**
034 * {@link DocumentModel} backed implementation of a {@link FileItem}.
035 *
036 * @author Antoine Taillefer
037 */
038public class DocumentBackedFileItem extends AbstractDocumentBackedFileSystemItem implements FileItem {
039
040    private static final long serialVersionUID = 1L;
041
042    protected String downloadURL;
043
044    protected String digestAlgorithm;
045
046    protected String digest;
047
048    protected boolean canUpdate;
049
050    protected VersioningFileSystemItemFactory factory;
051
052    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, DocumentModel doc) {
053        this(factory, doc, false);
054    }
055
056    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, DocumentModel doc,
057            boolean relaxSyncRootConstraint) {
058        super(factory.getName(), doc, relaxSyncRootConstraint);
059        initialize(factory, doc);
060    }
061
062    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc) {
063        this(factory, parentItem, doc, false);
064    }
065
066    public DocumentBackedFileItem(VersioningFileSystemItemFactory factory, FolderItem parentItem, DocumentModel doc,
067            boolean relaxSyncRootConstraint) {
068        super(factory.getName(), parentItem, doc, relaxSyncRootConstraint);
069        initialize(factory, doc);
070    }
071
072    protected DocumentBackedFileItem() {
073        // Needed for JSON deserialization
074    }
075
076    /*--------------------- FileSystemItem ---------------------*/
077    @Override
078    public void rename(String name) {
079        try (CoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) {
080            /* Update doc properties */
081            DocumentModel doc = getDocument(session);
082            // Handle versioning
083            FileSystemItemHelper.versionIfNeeded(factory, doc, session);
084            BlobHolder bh = getBlobHolder(doc);
085            Blob blob = getBlob(bh);
086            blob.setFilename(name);
087            bh.setBlob(blob);
088            updateDocTitleIfNeeded(doc, name);
089            doc = session.saveDocument(doc);
090            session.save();
091            /* Update FileSystemItem attributes */
092            this.name = name;
093            updateDownloadURL();
094            updateLastModificationDate(doc);
095        }
096    }
097
098    /*--------------------- FileItem -----------------*/
099    @Override
100    public Blob getBlob() {
101        try (CoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) {
102            DocumentModel doc = getDocument(session);
103            return getBlob(doc);
104        }
105    }
106
107    @Override
108    public String getDownloadURL() {
109        return downloadURL;
110    }
111
112    @Override
113    public String getDigestAlgorithm() {
114        return digestAlgorithm;
115    }
116
117    @Override
118    public String getDigest() {
119        return digest;
120    }
121
122    @Override
123    public boolean getCanUpdate() {
124        return canUpdate;
125    }
126
127    @Override
128    public void setBlob(Blob blob) {
129        try (CoreSession session = CoreInstance.openCoreSession(repositoryName, principal)) {
130            /* Update doc properties */
131            DocumentModel doc = getDocument(session);
132            // Handle versioning
133            FileSystemItemHelper.versionIfNeeded(factory, doc, session);
134            // If blob's filename is empty, set it to the current name
135            String blobFileName = blob.getFilename();
136            if (StringUtils.isEmpty(blobFileName)) {
137                blob.setFilename(name);
138            } else {
139                updateDocTitleIfNeeded(doc, blobFileName);
140                name = blobFileName;
141                updateDownloadURL();
142            }
143            BlobHolder bh = getBlobHolder(doc);
144            bh.setBlob(blob);
145            doc = session.saveDocument(doc);
146            session.save();
147            /* Update FileSystemItem attributes */
148            updateLastModificationDate(doc);
149            updateDigest(getBlob(doc));
150        }
151    }
152
153    /*--------------------- Protected -----------------*/
154    protected final void initialize(VersioningFileSystemItemFactory factory, DocumentModel doc) {
155        this.factory = factory;
156        Blob blob = getBlob(doc);
157        name = getFileName(blob, doc.getTitle());
158        folder = false;
159        updateDownloadURL();
160        updateDigest(blob);
161        if (digest == null) {
162            digestAlgorithm = null;
163        }
164        canUpdate = canRename;
165    }
166
167    protected BlobHolder getBlobHolder(DocumentModel doc) {
168        BlobHolder bh = doc.getAdapter(BlobHolder.class);
169        if (bh == null) {
170            throw new NuxeoException(
171                    String.format(
172                            "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.",
173                            doc.getId()));
174        }
175        return bh;
176    }
177
178    protected Blob getBlob(BlobHolder blobHolder) {
179        Blob blob = blobHolder.getBlob();
180        if (blob == null) {
181            throw new NuxeoException(
182                    "Document has no blob, it is not adaptable as a FileItem and therefore it cannot not be part of the items to synchronize.");
183        }
184        return blob;
185    }
186
187    protected Blob getBlob(DocumentModel doc) {
188        BlobHolder bh = getBlobHolder(doc);
189        return getBlob(bh);
190    }
191
192    protected String getFileName(Blob blob, String docTitle) {
193        String filename = blob.getFilename();
194        return filename != null ? filename : docTitle;
195    }
196
197    protected void updateDocTitleIfNeeded(DocumentModel doc, String name) {
198        // TODO: not sure about the behavior for the doc title
199        if (this.name.equals(docTitle)) {
200            doc.setPropertyValue("dc:title", name);
201            docTitle = name;
202        }
203    }
204
205    protected void updateDownloadURL() {
206        DownloadService downloadService = Framework.getService(DownloadService.class);
207        // Remove chars that are invalid in filesystem names
208        String escapedFilename = name.replaceAll("(/|\\\\|\\*|<|>|\\?|\"|:|\\|)", "-");
209        downloadURL = downloadService.getDownloadUrl(repositoryName, docId, DownloadService.BLOBHOLDER_0, escapedFilename);
210    }
211
212    protected void updateDigest(Blob blob) {
213        String blobDigest = blob.getDigest();
214        if (StringUtils.isEmpty(blobDigest)) {
215            // Force md5 digest algorithm and digest computation for a StringBlob,
216            // typically the note:note property of a Note document
217            digestAlgorithm = FileSystemItemHelper.MD5_DIGEST_ALGORITHM;
218            digest = FileSystemItemHelper.getMD5Digest(blob);
219        } else {
220            digestAlgorithm = blob.getDigestAlgorithm();
221            digest = blobDigest;
222        }
223    }
224
225    protected NuxeoDriveManager getNuxeoDriveManager() {
226        return Framework.getLocalService(NuxeoDriveManager.class);
227    }
228
229    /*---------- Needed for JSON deserialization ----------*/
230    protected void setDownloadURL(String downloadURL) {
231        this.downloadURL = downloadURL;
232    }
233
234    protected void setDigestAlgorithm(String digestAlgorithm) {
235        this.digestAlgorithm = digestAlgorithm;
236    }
237
238    protected void setDigest(String digest) {
239        this.digest = digest;
240    }
241
242    protected void setCanUpdate(boolean canUpdate) {
243        this.canUpdate = canUpdate;
244    }
245
246}