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 <ataillefer@nuxeo.com> 018 */ 019package org.nuxeo.drive.service.impl; 020 021import java.security.Principal; 022import java.util.Calendar; 023import java.util.Map; 024 025import org.apache.commons.lang3.StringUtils; 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.nuxeo.drive.adapter.FileSystemItem; 029import org.nuxeo.drive.adapter.FolderItem; 030import org.nuxeo.drive.adapter.impl.DocumentBackedFileItem; 031import org.nuxeo.drive.adapter.impl.DocumentBackedFolderItem; 032import org.nuxeo.drive.service.FileSystemItemFactory; 033import org.nuxeo.drive.service.NuxeoDriveManager; 034import org.nuxeo.drive.service.VersioningFileSystemItemFactory; 035import org.nuxeo.ecm.collections.api.CollectionConstants; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.DocumentModel; 038import org.nuxeo.ecm.core.api.NuxeoException; 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 Log log = LogFactory.getLog(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 if (log.isDebugEnabled()) { 112 log.debug(String.format("Document %s is a version, it cannot be adapted as a FileSystemItem.", 113 doc.getId())); 114 } 115 return false; 116 } 117 // Check Collections 118 if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) { 119 if (log.isDebugEnabled()) { 120 log.debug(String.format( 121 "Document %s is the collection root folder (type=%s, path=%s), it cannot be adapted as a FileSystemItem.", 122 doc.getId(), CollectionConstants.COLLECTIONS_TYPE, doc.getPathAsString())); 123 } 124 return false; 125 } 126 // Check HiddenInNavigation 127 if (doc.hasFacet("HiddenInNavigation")) { 128 if (log.isDebugEnabled()) { 129 log.debug(String.format("Document %s is HiddenInNavigation, it cannot be adapted as a FileSystemItem.", 130 doc.getId())); 131 } 132 return false; 133 } 134 // Check if document is in the trash 135 if (!includeDeleted && doc.isTrashed()) { 136 if (log.isDebugEnabled()) { 137 log.debug(String.format("Document %s is trashed, it cannot be adapted as a FileSystemItem.", 138 doc.getId())); 139 } 140 return false; 141 } 142 // Try to fetch blob 143 Blob blob = null; 144 try { 145 blob = getBlob(doc); 146 } catch (NuxeoException e) { 147 log.error(String.format( 148 "Error while fetching blob for document %s, it cannot be adapted as a FileSystemItem.", 149 doc.getId()), e); 150 return false; 151 } 152 // Check Folderish or BlobHolder with a blob 153 if (!doc.isFolder() && blob == null) { 154 if (log.isDebugEnabled()) { 155 log.debug(String.format( 156 "Document %s is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.", 157 doc.getId())); 158 } 159 return false; 160 } 161 162 // Check for blobs backed by extended blob providers (ex: Google Drive) 163 if (!doc.isFolder()) { 164 BlobManager blobManager = Framework.getService(BlobManager.class); 165 BlobProvider blobProvider = blobManager.getBlobProvider(blob); 166 if (blobProvider != null 167 && (!blobProvider.supportsUserUpdate() || blobProvider.getBinaryManager() == null)) { 168 if (log.isDebugEnabled()) { 169 log.debug(String.format( 170 "Blob for Document %s is backed by a BlobProvider preventing updates, it cannot be adapted as a FileSystemItem.", 171 doc.getId())); 172 } 173 return false; 174 } 175 } 176 177 if (!relaxSyncRootConstraint && doc.isFolder()) { 178 // Check not a synchronization root registered for the current user 179 NuxeoDriveManager nuxeoDriveManager = Framework.getService(NuxeoDriveManager.class); 180 Principal principal = doc.getCoreSession().getPrincipal(); 181 boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc); 182 if (isSyncRoot) { 183 if (log.isDebugEnabled()) { 184 log.debug(String.format( 185 "Document %s is a registered synchronization root for user %s, it cannot be adapted as a DefaultFileSystemItem.", 186 doc.getId(), principal.getName())); 187 } 188 return false; 189 } 190 } 191 return true; 192 } 193 194 @Override 195 protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 196 boolean relaxSyncRootConstraint, boolean getLockInfo) { 197 // Doc is either Folderish 198 if (doc.isFolder()) { 199 if (forceParentItem) { 200 return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint, getLockInfo); 201 } else { 202 return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint, getLockInfo); 203 } 204 } 205 // or a BlobHolder with a blob 206 else { 207 if (forceParentItem) { 208 return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint, getLockInfo); 209 } else { 210 return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint, getLockInfo); 211 } 212 } 213 } 214 215 /*--------------------------- FileSystemItemVersioning -------------------------*/ 216 /** 217 * Need to version the doc if the current contributor is different from the last contributor or if the last 218 * modification was done more than {@link #versioningDelay} seconds ago. 219 * 220 * @deprecated since 9.1 versioning policy is now handled at versioning service level, as versioning is removed at 221 * drive level, this method is not used anymore 222 */ 223 @Override 224 @Deprecated 225 public boolean needsVersioning(DocumentModel doc) { 226 227 String lastContributor = (String) doc.getPropertyValue("dc:lastContributor"); 228 Principal principal = doc.getCoreSession().getPrincipal(); 229 boolean contributorChanged = !principal.getName().equals(lastContributor); 230 if (contributorChanged) { 231 if (log.isDebugEnabled()) { 232 log.debug(String.format( 233 "Contributor %s is different from the last contributor %s => will create a version of the document.", 234 principal.getName(), lastContributor)); 235 } 236 return true; 237 } 238 Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified"); 239 if (lastModificationDate == null) { 240 log.debug("Last modification date is null => will create a version of the document."); 241 return true; 242 } 243 long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis(); 244 long versioningDelayMillis = (long) getVersioningDelay() * 1000; 245 if (lastModified > versioningDelayMillis) { 246 if (log.isDebugEnabled()) { 247 log.debug(String.format( 248 "Last modification was done %d milliseconds ago, this is more than the versioning delay %d milliseconds => will create a version of the document.", 249 lastModified, versioningDelayMillis)); 250 } 251 return true; 252 } 253 if (log.isDebugEnabled()) { 254 log.debug(String.format( 255 "Contributor %s is the last contributor and last modification was done %d milliseconds ago, this is less than the versioning delay %d milliseconds => will not create a version of the document.", 256 principal.getName(), lastModified, versioningDelayMillis)); 257 } 258 return false; 259 } 260 261 /** 262 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 263 */ 264 @Override 265 @Deprecated 266 public double getVersioningDelay() { 267 return versioningDelay; 268 } 269 270 /** 271 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 272 */ 273 @Override 274 @Deprecated 275 public void setVersioningDelay(double versioningDelay) { 276 this.versioningDelay = versioningDelay; 277 } 278 279 /** 280 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 281 */ 282 @Override 283 @Deprecated 284 public VersioningOption getVersioningOption() { 285 return versioningOption; 286 } 287 288 /** 289 * @deprecated since 9.1 automatic versioning is directly done by versioning system which holds the policies 290 */ 291 @Override 292 @Deprecated 293 public void setVersioningOption(VersioningOption versioningOption) { 294 this.versioningOption = versioningOption; 295 } 296 297 /*--------------------------- Protected ---------------------------------*/ 298 protected Blob getBlob(DocumentModel doc) { 299 BlobHolder bh = doc.getAdapter(BlobHolder.class); 300 if (bh == null) { 301 if (log.isDebugEnabled()) { 302 log.debug(String.format("Document %s is not a BlobHolder.", doc.getId())); 303 } 304 return null; 305 } 306 Blob blob = bh.getBlob(); 307 if (blob == null) { 308 if (log.isDebugEnabled()) { 309 log.debug(String.format("Document %s is a BlobHolder without a blob.", doc.getId())); 310 } 311 } 312 return blob; 313 } 314 315}