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