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.service.impl; 020 021import java.util.Calendar; 022import java.util.Map; 023 024import org.apache.commons.lang3.StringUtils; 025import org.apache.logging.log4j.LogManager; 026import org.apache.logging.log4j.Logger; 027import org.nuxeo.drive.adapter.FileSystemItem; 028import org.nuxeo.drive.adapter.FolderItem; 029import org.nuxeo.drive.adapter.impl.DocumentBackedFileItem; 030import org.nuxeo.drive.adapter.impl.DocumentBackedFolderItem; 031import org.nuxeo.drive.service.FileSystemItemFactory; 032import org.nuxeo.drive.service.NuxeoDriveManager; 033import org.nuxeo.drive.service.VersioningFileSystemItemFactory; 034import org.nuxeo.ecm.collections.api.CollectionConstants; 035import org.nuxeo.ecm.core.api.Blob; 036import org.nuxeo.ecm.core.api.DocumentModel; 037import org.nuxeo.ecm.core.api.NuxeoException; 038import org.nuxeo.ecm.core.api.NuxeoPrincipal; 039import org.nuxeo.ecm.core.api.VersioningOption; 040import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 041import org.nuxeo.ecm.core.blob.BlobManager; 042import org.nuxeo.ecm.core.blob.BlobProvider; 043import org.nuxeo.runtime.api.Framework; 044 045/** 046 * Default implementation of a {@link FileSystemItemFactory}. It is {@link DocumentModel} backed and is the one used by 047 * Nuxeo Drive. 048 * 049 * @author Antoine Taillefer 050 */ 051public class DefaultFileSystemItemFactory extends AbstractFileSystemItemFactory 052 implements VersioningFileSystemItemFactory { 053 054 private static final Logger log = LogManager.getLogger(DefaultFileSystemItemFactory.class); 055 056 /** 057 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 058 */ 059 @Deprecated 060 protected static final String VERSIONING_DELAY_PARAM = "versioningDelay"; 061 062 /** 063 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 064 */ 065 @Deprecated 066 protected static final String VERSIONING_OPTION_PARAM = "versioningOption"; 067 068 /** 069 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 070 */ 071 @Deprecated 072 // Versioning delay in seconds, default value: 1 hour 073 protected double versioningDelay = 3600; 074 075 /** 076 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 077 */ 078 @Deprecated 079 // Versioning option, default value: MINOR 080 protected VersioningOption versioningOption = VersioningOption.MINOR; 081 082 /*--------------------------- AbstractFileSystemItemFactory -------------------------*/ 083 @Override 084 public void handleParameters(Map<String, String> parameters) { 085 String versioningDelayParam = parameters.get(VERSIONING_DELAY_PARAM); 086 if (!StringUtils.isEmpty(versioningDelayParam)) { 087 versioningDelay = Double.parseDouble(versioningDelayParam); 088 } 089 String versioningOptionParam = parameters.get(DefaultFileSystemItemFactory.VERSIONING_OPTION_PARAM); 090 if (!StringUtils.isEmpty(versioningOptionParam)) { 091 versioningOption = VersioningOption.valueOf(versioningOptionParam); 092 } 093 } 094 095 /** 096 * The default factory considers that a {@link DocumentModel} is adaptable as a {@link FileSystemItem} if: 097 * <ul> 098 * <li>It is not a version</li> 099 * <li>AND it is not HiddenInNavigation</li> 100 * <li>AND it is not in the trash, unless {@code includeDeleted} is true</li> 101 * <li>AND it is Folderish or it can be adapted as a {@link BlobHolder} with a blob</li> 102 * <li>AND its blob is not backed by an extended blob provider</li> 103 * <li>AND it is not a synchronization root registered for the current user, unless {@code relaxSyncRootConstraint} 104 * is true</li> 105 * </ul> 106 */ 107 @Override 108 public boolean isFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint) { 109 // Check version 110 if (doc.isVersion()) { 111 log.debug("Document {} is a version, it cannot be adapted as a FileSystemItem.", doc::getId); 112 return false; 113 } 114 // Check Collections 115 if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) { 116 log.debug( 117 "Document {} is the collection root folder (type={}, path={}), it cannot be adapted as a FileSystemItem.", 118 doc::getId, () -> CollectionConstants.COLLECTIONS_TYPE, doc::getPathAsString); 119 return false; 120 } 121 // Check HiddenInNavigation 122 if (doc.hasFacet("HiddenInNavigation")) { 123 log.debug("Document {} is HiddenInNavigation, it cannot be adapted as a FileSystemItem.", doc::getId); 124 return false; 125 } 126 // Check if document is in the trash 127 if (!includeDeleted && doc.isTrashed()) { 128 log.debug("Document {} is trashed, it cannot be adapted as a FileSystemItem.", doc::getId); 129 return false; 130 } 131 // Try to fetch blob 132 Blob blob = null; 133 try { 134 blob = getBlob(doc); 135 } catch (NuxeoException e) { 136 log.error("Error while fetching blob for document {}, it cannot be adapted as a FileSystemItem.", 137 doc::getId, () -> e); 138 return false; 139 } 140 // Check Folderish or BlobHolder with a blob 141 if (!doc.isFolder() && blob == null) { 142 log.debug( 143 "Document {} is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.", 144 doc::getId); 145 return false; 146 } 147 148 // Check for blobs backed by extended blob providers (ex: Google Drive) 149 if (!doc.isFolder()) { 150 BlobManager blobManager = Framework.getService(BlobManager.class); 151 BlobProvider blobProvider = blobManager.getBlobProvider(blob); 152 if (blobProvider != null 153 && (!blobProvider.supportsUserUpdate() || blobProvider.getBinaryManager() == null)) { 154 log.debug( 155 "Blob for Document {} is backed by a BlobProvider preventing updates, it cannot be adapted as a FileSystemItem.", 156 doc::getId); 157 return false; 158 } 159 } 160 161 if (!relaxSyncRootConstraint && doc.isFolder()) { 162 // Check not a synchronization root registered for the current user 163 NuxeoDriveManager nuxeoDriveManager = Framework.getService(NuxeoDriveManager.class); 164 NuxeoPrincipal principal = doc.getCoreSession().getPrincipal(); 165 boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc); 166 if (isSyncRoot) { 167 log.debug( 168 "Document {} is a registered synchronization root for user {}, it cannot be adapted as a DefaultFileSystemItem.", 169 doc::getId, principal::getName); 170 return false; 171 } 172 } 173 return true; 174 } 175 176 @Override 177 protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 178 boolean relaxSyncRootConstraint, boolean getLockInfo) { 179 // Doc is either Folderish 180 if (doc.isFolder()) { 181 if (forceParentItem) { 182 return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint, getLockInfo); 183 } else { 184 return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint, getLockInfo); 185 } 186 } 187 // or a BlobHolder with a blob 188 else { 189 if (forceParentItem) { 190 return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint, getLockInfo); 191 } else { 192 return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint, getLockInfo); 193 } 194 } 195 } 196 197 /*--------------------------- FileSystemItemVersioning -------------------------*/ 198 /** 199 * Need to version the doc if the current contributor is different from the last contributor or if the last 200 * modification was done more than {@link #versioningDelay} seconds ago. 201 * 202 * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at 203 * drive level, this method is not used anymore 204 */ 205 @Override 206 @Deprecated 207 public boolean needsVersioning(DocumentModel doc) { 208 209 String lastContributor = (String) doc.getPropertyValue("dc:lastContributor"); 210 NuxeoPrincipal principal = doc.getCoreSession().getPrincipal(); 211 boolean contributorChanged = !principal.getName().equals(lastContributor); 212 if (contributorChanged) { 213 log.debug( 214 "Contributor {} is different from the last contributor {} => will create a version of the document.", 215 principal, lastContributor); 216 return true; 217 } 218 Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified"); 219 if (lastModificationDate == null) { 220 log.debug("Last modification date is null => will create a version of the document."); 221 return true; 222 } 223 long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis(); 224 long versioningDelayMillis = (long) getVersioningDelay() * 1000; 225 if (lastModified > versioningDelayMillis) { 226 log.debug( 227 "Last modification was done {} milliseconds ago, this is more than the versioning delay {} milliseconds => will create a version of the document.", 228 lastModified, versioningDelayMillis); 229 return true; 230 } 231 log.debug( 232 "Contributor {} is the last contributor and last modification was done {} milliseconds ago, this is less than the versioning delay {} milliseconds => will not create a version of the document.", 233 principal, lastModified, versioningDelayMillis); 234 return false; 235 } 236 237 /** 238 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 239 */ 240 @Override 241 @Deprecated 242 public double getVersioningDelay() { 243 return versioningDelay; 244 } 245 246 /** 247 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 248 */ 249 @Override 250 @Deprecated 251 public void setVersioningDelay(double versioningDelay) { 252 this.versioningDelay = versioningDelay; 253 } 254 255 /** 256 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 257 */ 258 @Override 259 @Deprecated 260 public VersioningOption getVersioningOption() { 261 return versioningOption; 262 } 263 264 /** 265 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 266 */ 267 @Override 268 @Deprecated 269 public void setVersioningOption(VersioningOption versioningOption) { 270 this.versioningOption = versioningOption; 271 } 272 273 /*--------------------------- Protected ---------------------------------*/ 274 protected Blob getBlob(DocumentModel doc) { 275 BlobHolder bh = doc.getAdapter(BlobHolder.class); 276 if (bh == null) { 277 log.debug("Document {} is not a BlobHolder.", doc::getId); 278 return null; 279 } 280 Blob blob = bh.getBlob(); 281 if (blob == null) { 282 log.debug("Document {} is a BlobHolder without a blob.", doc::getId); 283 } 284 return blob; 285 } 286 287}