001/* 002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Antoine Taillefer <ataillefer@nuxeo.com> 016 */ 017package org.nuxeo.drive.service.impl; 018 019import java.security.Principal; 020import java.util.Calendar; 021import java.util.Map; 022 023import org.apache.commons.lang.StringUtils; 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.nuxeo.drive.adapter.FileSystemItem; 027import org.nuxeo.drive.adapter.FolderItem; 028import org.nuxeo.drive.adapter.impl.DocumentBackedFileItem; 029import org.nuxeo.drive.adapter.impl.DocumentBackedFolderItem; 030import org.nuxeo.drive.service.FileSystemItemFactory; 031import org.nuxeo.drive.service.NuxeoDriveManager; 032import org.nuxeo.drive.service.VersioningFileSystemItemFactory; 033import org.nuxeo.ecm.collections.api.CollectionConstants; 034import org.nuxeo.ecm.core.api.Blob; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.LifeCycleConstants; 037import org.nuxeo.ecm.core.api.VersioningOption; 038import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 039import org.nuxeo.ecm.core.blob.BlobManager; 040import org.nuxeo.ecm.core.blob.BlobProvider; 041import org.nuxeo.runtime.api.Framework; 042 043/** 044 * Default implementation of a {@link FileSystemItemFactory}. It is {@link DocumentModel} backed and is the one used by 045 * Nuxeo Drive. 046 * 047 * @author Antoine Taillefer 048 */ 049public class DefaultFileSystemItemFactory extends AbstractFileSystemItemFactory implements 050 VersioningFileSystemItemFactory { 051 052 private static final Log log = LogFactory.getLog(DefaultFileSystemItemFactory.class); 053 054 protected static final String RENDITION_FACET = "Rendition"; 055 056 protected static final String VERSIONING_DELAY_PARAM = "versioningDelay"; 057 058 protected static final String VERSIONING_OPTION_PARAM = "versioningOption"; 059 060 // Versioning delay in seconds, default value: 1 hour 061 protected double versioningDelay = 3600; 062 063 // Versioning option, default value: MINOR 064 protected VersioningOption versioningOption = VersioningOption.MINOR; 065 066 /*--------------------------- AbstractFileSystemItemFactory -------------------------*/ 067 @Override 068 public void handleParameters(Map<String, String> parameters) { 069 String versioningDelayParam = parameters.get(VERSIONING_DELAY_PARAM); 070 if (!StringUtils.isEmpty(versioningDelayParam)) { 071 versioningDelay = Double.parseDouble(versioningDelayParam); 072 } 073 String versioningOptionParam = parameters.get(DefaultFileSystemItemFactory.VERSIONING_OPTION_PARAM); 074 if (!StringUtils.isEmpty(versioningOptionParam)) { 075 versioningOption = VersioningOption.valueOf(versioningOptionParam); 076 } 077 } 078 079 /** 080 * The default factory considers that a {@link DocumentModel} is adaptable as a {@link FileSystemItem} if: 081 * <ul> 082 * <li>It is not a version nor a proxy nor a rendition</li> 083 * <li>AND it is not HiddenInNavigation</li> 084 * <li>AND it is not in the "deleted" life cycle state, unless {@code includeDeleted} is true</li> 085 * <li>AND it is Folderish or it can be adapted as a {@link BlobHolder} with a blob</li> 086 * <li>AND it is not a synchronization root registered for the current user, unless {@code relaxSyncRootConstraint} 087 * is true</li> 088 * </ul> 089 */ 090 @Override 091 public boolean isFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint) { 092 // Check version 093 if (doc.isVersion()) { 094 if (log.isDebugEnabled()) { 095 log.debug(String.format("Document %s is a version, it cannot be adapted as a FileSystemItem.", 096 doc.getId())); 097 } 098 return false; 099 } 100 // Check proxy 101 if (doc.isProxy()) { 102 if (log.isDebugEnabled()) { 103 log.debug(String.format("Document %s is a proxy, it cannot be adapted as a FileSystemItem.", 104 doc.getId())); 105 } 106 return false; 107 } 108 // Check rendition 109 if (doc.hasFacet(RENDITION_FACET)) { 110 if (log.isDebugEnabled()) { 111 log.debug(String.format("Document %s is a rendition, it cannot be adapted as a FileSystemItem.", 112 doc.getId())); 113 } 114 return false; 115 } 116 // Check Collections 117 if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) { 118 if (log.isDebugEnabled()) { 119 log.debug(String.format( 120 "Document %s is the collection root folder (type=%s, path=%s), it cannot be adapted as a FileSystemItem.", 121 doc.getId(), CollectionConstants.COLLECTIONS_TYPE, doc.getPathAsString())); 122 } 123 return false; 124 } 125 // Check HiddenInNavigation 126 if (doc.hasFacet("HiddenInNavigation")) { 127 if (log.isDebugEnabled()) { 128 log.debug(String.format("Document %s is HiddenInNavigation, it cannot be adapted as a FileSystemItem.", 129 doc.getId())); 130 } 131 return false; 132 } 133 // Check "deleted" life cycle state 134 if (!includeDeleted && LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) { 135 if (log.isDebugEnabled()) { 136 log.debug(String.format( 137 "Document %s is in the '%s' life cycle state, it cannot be adapted as a FileSystemItem.", 138 doc.getId(), LifeCycleConstants.DELETED_STATE)); 139 } 140 return false; 141 } 142 // Check Folderish or BlobHolder with a blob 143 if (!doc.isFolder() && !hasBlob(doc)) { 144 if (log.isDebugEnabled()) { 145 log.debug(String.format( 146 "Document %s is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.", 147 doc.getId())); 148 } 149 return false; 150 } 151 152 // Check for blobs backed by extended blob providers (ex: Google Drive) 153 if (!doc.isFolder()) { 154 BlobManager blobManager = Framework.getService(BlobManager.class); 155 BlobHolder bh = doc.getAdapter(BlobHolder.class); 156 BlobProvider blobProvider = blobManager.getBlobProvider(bh.getBlob()); 157 if (blobProvider != null && !blobProvider.supportsWrite()) { 158 if (log.isDebugEnabled()) { 159 log.debug(String.format( 160 "Blob for Document %s is backed by an ExtendedBlobProvider, it cannot be adapted as a FileSystemItem.", 161 doc.getId())); 162 } 163 return false; 164 } 165 } 166 167 if (!relaxSyncRootConstraint) { 168 // Check not a synchronization root registered for the current user 169 NuxeoDriveManager nuxeoDriveManager = Framework.getLocalService(NuxeoDriveManager.class); 170 Principal principal = doc.getCoreSession().getPrincipal(); 171 boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc); 172 if (isSyncRoot) { 173 if (log.isDebugEnabled()) { 174 log.debug(String.format( 175 "Document %s is a registered synchronization root for user %s, it cannot be adapted as a DefaultFileSystemItem.", 176 doc.getId(), principal.getName())); 177 } 178 return false; 179 } 180 } 181 return true; 182 } 183 184 @Override 185 protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 186 boolean relaxSyncRootConstraint) { 187 // Doc is either Folderish 188 if (doc.isFolder()) { 189 if (forceParentItem) { 190 return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint); 191 } else { 192 return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint); 193 } 194 } 195 // or a BlobHolder with a blob 196 else { 197 if (forceParentItem) { 198 return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint); 199 } else { 200 return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint); 201 } 202 } 203 } 204 205 /*--------------------------- FileSystemItemVersioning -------------------------*/ 206 /** 207 * Need to version the doc if the current contributor is different from the last contributor or if the last 208 * modification was done more than {@link #versioningDelay} seconds ago. 209 */ 210 @Override 211 public boolean needsVersioning(DocumentModel doc) { 212 213 String lastContributor = (String) doc.getPropertyValue("dc:lastContributor"); 214 Principal principal = doc.getCoreSession().getPrincipal(); 215 boolean contributorChanged = !principal.getName().equals(lastContributor); 216 if (contributorChanged) { 217 if (log.isDebugEnabled()) { 218 log.debug(String.format( 219 "Contributor %s is different from the last contributor %s => will create a version of the document.", 220 principal.getName(), lastContributor)); 221 } 222 return true; 223 } 224 Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified"); 225 if (lastModificationDate == null) { 226 log.debug("Last modification date is null => will not create a version of the document."); 227 return true; 228 } 229 long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis(); 230 long versioningDelayMillis = (long) getVersioningDelay() * 1000; 231 if (lastModified > versioningDelayMillis) { 232 if (log.isDebugEnabled()) { 233 log.debug(String.format( 234 "Last modification was done %d milliseconds ago, this is more than the versioning delay %d milliseconds => will create a version of the document.", 235 lastModified, versioningDelayMillis)); 236 } 237 return true; 238 } 239 if (log.isDebugEnabled()) { 240 log.debug(String.format( 241 "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.", 242 principal.getName(), lastModified, versioningDelayMillis)); 243 } 244 return false; 245 } 246 247 @Override 248 public double getVersioningDelay() { 249 return versioningDelay; 250 } 251 252 @Override 253 public void setVersioningDelay(double versioningDelay) { 254 this.versioningDelay = versioningDelay; 255 } 256 257 @Override 258 public VersioningOption getVersioningOption() { 259 return versioningOption; 260 } 261 262 @Override 263 public void setVersioningOption(VersioningOption versioningOption) { 264 this.versioningOption = versioningOption; 265 } 266 267 /*--------------------------- Protected ---------------------------------*/ 268 protected boolean hasBlob(DocumentModel doc) { 269 BlobHolder bh = doc.getAdapter(BlobHolder.class); 270 if (bh == null) { 271 if (log.isDebugEnabled()) { 272 log.debug(String.format("Document %s is not a BlobHolder.", doc.getId())); 273 } 274 return false; 275 } 276 Blob blob = bh.getBlob(); 277 if (blob == null) { 278 if (log.isDebugEnabled()) { 279 log.debug(String.format("Document %s is a BlobHolder without a blob.", doc.getId())); 280 } 281 return false; 282 } 283 return true; 284 } 285 286}