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