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 VERSIONING_DELAY_PARAM = "versioningDelay"; 055 056 protected static final String VERSIONING_OPTION_PARAM = "versioningOption"; 057 058 // Versioning delay in seconds, default value: 1 hour 059 protected double versioningDelay = 3600; 060 061 // Versioning option, default value: MINOR 062 protected VersioningOption versioningOption = VersioningOption.MINOR; 063 064 /*--------------------------- AbstractFileSystemItemFactory -------------------------*/ 065 @Override 066 public void handleParameters(Map<String, String> parameters) { 067 String versioningDelayParam = parameters.get(VERSIONING_DELAY_PARAM); 068 if (!StringUtils.isEmpty(versioningDelayParam)) { 069 versioningDelay = Double.parseDouble(versioningDelayParam); 070 } 071 String versioningOptionParam = parameters.get(DefaultFileSystemItemFactory.VERSIONING_OPTION_PARAM); 072 if (!StringUtils.isEmpty(versioningOptionParam)) { 073 versioningOption = VersioningOption.valueOf(versioningOptionParam); 074 } 075 } 076 077 /** 078 * The default factory considers that a {@link DocumentModel} is adaptable as a {@link FileSystemItem} if: 079 * <ul> 080 * <li>It is not a version</li> 081 * <li>AND it is not HiddenInNavigation</li> 082 * <li>AND it is not in the "deleted" life cycle state, unless {@code includeDeleted} is true</li> 083 * <li>AND it is Folderish or it can be adapted as a {@link BlobHolder} with a blob</li> 084 * <li>AND it is not a synchronization root registered for the current user, unless {@code relaxSyncRootConstraint} 085 * is true</li> 086 * </ul> 087 */ 088 @Override 089 public boolean isFileSystemItem(DocumentModel doc, boolean includeDeleted, boolean relaxSyncRootConstraint) { 090 // Check version 091 if (doc.isVersion()) { 092 if (log.isDebugEnabled()) { 093 log.debug(String.format("Document %s is a version, it cannot be adapted as a FileSystemItem.", 094 doc.getId())); 095 } 096 return false; 097 } 098 // Check Collections 099 if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) { 100 if (log.isDebugEnabled()) { 101 log.debug(String.format( 102 "Document %s is the collection root folder (type=%s, path=%s), it cannot be adapted as a FileSystemItem.", 103 doc.getId(), CollectionConstants.COLLECTIONS_TYPE, doc.getPathAsString())); 104 } 105 return false; 106 } 107 // Check HiddenInNavigation 108 if (doc.hasFacet("HiddenInNavigation")) { 109 if (log.isDebugEnabled()) { 110 log.debug(String.format("Document %s is HiddenInNavigation, it cannot be adapted as a FileSystemItem.", 111 doc.getId())); 112 } 113 return false; 114 } 115 // Check "deleted" life cycle state 116 if (!includeDeleted && LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) { 117 if (log.isDebugEnabled()) { 118 log.debug(String.format( 119 "Document %s is in the '%s' life cycle state, it cannot be adapted as a FileSystemItem.", 120 doc.getId(), LifeCycleConstants.DELETED_STATE)); 121 } 122 return false; 123 } 124 // Check Folderish or BlobHolder with a blob 125 if (!doc.isFolder() && !hasBlob(doc)) { 126 if (log.isDebugEnabled()) { 127 log.debug(String.format( 128 "Document %s is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.", 129 doc.getId())); 130 } 131 return false; 132 } 133 134 // Check for blobs backed by extended blob providers (ex: Google Drive) 135 if (!doc.isFolder()) { 136 BlobManager blobManager = Framework.getService(BlobManager.class); 137 BlobHolder bh = doc.getAdapter(BlobHolder.class); 138 BlobProvider blobProvider = blobManager.getBlobProvider(bh.getBlob()); 139 if (blobProvider != null && !blobProvider.supportsUserUpdate()) { 140 if (log.isDebugEnabled()) { 141 log.debug(String.format( 142 "Blob for Document %s is backed by a BlobProvider preventing updates, it cannot be adapted as a FileSystemItem.", 143 doc.getId())); 144 } 145 return false; 146 } 147 } 148 149 if (!relaxSyncRootConstraint) { 150 // Check not a synchronization root registered for the current user 151 NuxeoDriveManager nuxeoDriveManager = Framework.getLocalService(NuxeoDriveManager.class); 152 Principal principal = doc.getCoreSession().getPrincipal(); 153 boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc); 154 if (isSyncRoot) { 155 if (log.isDebugEnabled()) { 156 log.debug(String.format( 157 "Document %s is a registered synchronization root for user %s, it cannot be adapted as a DefaultFileSystemItem.", 158 doc.getId(), principal.getName())); 159 } 160 return false; 161 } 162 } 163 return true; 164 } 165 166 @Override 167 protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 168 boolean relaxSyncRootConstraint) { 169 // Doc is either Folderish 170 if (doc.isFolder()) { 171 if (forceParentItem) { 172 return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint); 173 } else { 174 return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint); 175 } 176 } 177 // or a BlobHolder with a blob 178 else { 179 if (forceParentItem) { 180 return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint); 181 } else { 182 return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint); 183 } 184 } 185 } 186 187 /*--------------------------- FileSystemItemVersioning -------------------------*/ 188 /** 189 * Need to version the doc if the current contributor is different from the last contributor or if the last 190 * modification was done more than {@link #versioningDelay} seconds ago. 191 */ 192 @Override 193 public boolean needsVersioning(DocumentModel doc) { 194 195 String lastContributor = (String) doc.getPropertyValue("dc:lastContributor"); 196 Principal principal = doc.getCoreSession().getPrincipal(); 197 boolean contributorChanged = !principal.getName().equals(lastContributor); 198 if (contributorChanged) { 199 if (log.isDebugEnabled()) { 200 log.debug(String.format( 201 "Contributor %s is different from the last contributor %s => will create a version of the document.", 202 principal.getName(), lastContributor)); 203 } 204 return true; 205 } 206 Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified"); 207 if (lastModificationDate == null) { 208 log.debug("Last modification date is null => will not create a version of the document."); 209 return true; 210 } 211 long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis(); 212 long versioningDelayMillis = (long) getVersioningDelay() * 1000; 213 if (lastModified > versioningDelayMillis) { 214 if (log.isDebugEnabled()) { 215 log.debug(String.format( 216 "Last modification was done %d milliseconds ago, this is more than the versioning delay %d milliseconds => will create a version of the document.", 217 lastModified, versioningDelayMillis)); 218 } 219 return true; 220 } 221 if (log.isDebugEnabled()) { 222 log.debug(String.format( 223 "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.", 224 principal.getName(), lastModified, versioningDelayMillis)); 225 } 226 return false; 227 } 228 229 @Override 230 public double getVersioningDelay() { 231 return versioningDelay; 232 } 233 234 @Override 235 public void setVersioningDelay(double versioningDelay) { 236 this.versioningDelay = versioningDelay; 237 } 238 239 @Override 240 public VersioningOption getVersioningOption() { 241 return versioningOption; 242 } 243 244 @Override 245 public void setVersioningOption(VersioningOption versioningOption) { 246 this.versioningOption = versioningOption; 247 } 248 249 /*--------------------------- Protected ---------------------------------*/ 250 protected boolean hasBlob(DocumentModel doc) { 251 BlobHolder bh = doc.getAdapter(BlobHolder.class); 252 if (bh == null) { 253 if (log.isDebugEnabled()) { 254 log.debug(String.format("Document %s is not a BlobHolder.", doc.getId())); 255 } 256 return false; 257 } 258 Blob blob = bh.getBlob(); 259 if (blob == null) { 260 if (log.isDebugEnabled()) { 261 log.debug(String.format("Document %s is a BlobHolder without a blob.", doc.getId())); 262 } 263 return false; 264 } 265 return true; 266 } 267 268}