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 <[email protected]> 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}