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