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.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 implements 052 VersioningFileSystemItemFactory { 053 054 private static final Log log = LogFactory.getLog(DefaultFileSystemItemFactory.class); 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</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 Collections 101 if (CollectionConstants.COLLECTIONS_TYPE.equals(doc.getType())) { 102 if (log.isDebugEnabled()) { 103 log.debug(String.format( 104 "Document %s is the collection root folder (type=%s, path=%s), it cannot be adapted as a FileSystemItem.", 105 doc.getId(), CollectionConstants.COLLECTIONS_TYPE, doc.getPathAsString())); 106 } 107 return false; 108 } 109 // Check HiddenInNavigation 110 if (doc.hasFacet("HiddenInNavigation")) { 111 if (log.isDebugEnabled()) { 112 log.debug(String.format("Document %s is HiddenInNavigation, it cannot be adapted as a FileSystemItem.", 113 doc.getId())); 114 } 115 return false; 116 } 117 // Check "deleted" life cycle state 118 if (!includeDeleted && LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) { 119 if (log.isDebugEnabled()) { 120 log.debug(String.format( 121 "Document %s is in the '%s' life cycle state, it cannot be adapted as a FileSystemItem.", 122 doc.getId(), LifeCycleConstants.DELETED_STATE)); 123 } 124 return false; 125 } 126 // Check Folderish or BlobHolder with a blob 127 if (!doc.isFolder() && !hasBlob(doc)) { 128 if (log.isDebugEnabled()) { 129 log.debug(String.format( 130 "Document %s is not Folderish nor a BlobHolder with a blob, it cannot be adapted as a FileSystemItem.", 131 doc.getId())); 132 } 133 return false; 134 } 135 136 // Check for blobs backed by extended blob providers (ex: Google Drive) 137 if (!doc.isFolder()) { 138 BlobManager blobManager = Framework.getService(BlobManager.class); 139 BlobHolder bh = doc.getAdapter(BlobHolder.class); 140 BlobProvider blobProvider = blobManager.getBlobProvider(bh.getBlob()); 141 if (blobProvider != null && (!blobProvider.supportsUserUpdate() || blobProvider.getBinaryManager() == null)) { 142 if (log.isDebugEnabled()) { 143 log.debug(String.format( 144 "Blob for Document %s is backed by a BlobProvider preventing updates, it cannot be adapted as a FileSystemItem.", 145 doc.getId())); 146 } 147 return false; 148 } 149 } 150 151 if (!relaxSyncRootConstraint && doc.isFolder()) { 152 // Check not a synchronization root registered for the current user 153 NuxeoDriveManager nuxeoDriveManager = Framework.getLocalService(NuxeoDriveManager.class); 154 Principal principal = doc.getCoreSession().getPrincipal(); 155 boolean isSyncRoot = nuxeoDriveManager.isSynchronizationRoot(principal, doc); 156 if (isSyncRoot) { 157 if (log.isDebugEnabled()) { 158 log.debug(String.format( 159 "Document %s is a registered synchronization root for user %s, it cannot be adapted as a DefaultFileSystemItem.", 160 doc.getId(), principal.getName())); 161 } 162 return false; 163 } 164 } 165 return true; 166 } 167 168 @Override 169 protected FileSystemItem adaptDocument(DocumentModel doc, boolean forceParentItem, FolderItem parentItem, 170 boolean relaxSyncRootConstraint, boolean getLockInfo) { 171 // Doc is either Folderish 172 if (doc.isFolder()) { 173 if (forceParentItem) { 174 return new DocumentBackedFolderItem(name, parentItem, doc, relaxSyncRootConstraint, getLockInfo); 175 } else { 176 return new DocumentBackedFolderItem(name, doc, relaxSyncRootConstraint, getLockInfo); 177 } 178 } 179 // or a BlobHolder with a blob 180 else { 181 if (forceParentItem) { 182 return new DocumentBackedFileItem(this, parentItem, doc, relaxSyncRootConstraint, getLockInfo); 183 } else { 184 return new DocumentBackedFileItem(this, doc, relaxSyncRootConstraint, getLockInfo); 185 } 186 } 187 } 188 189 /*--------------------------- FileSystemItemVersioning -------------------------*/ 190 /** 191 * Need to version the doc if the current contributor is different from the last contributor or if the last 192 * modification was done more than {@link #versioningDelay} seconds ago. 193 */ 194 @Override 195 public boolean needsVersioning(DocumentModel doc) { 196 197 String lastContributor = (String) doc.getPropertyValue("dc:lastContributor"); 198 Principal principal = doc.getCoreSession().getPrincipal(); 199 boolean contributorChanged = !principal.getName().equals(lastContributor); 200 if (contributorChanged) { 201 if (log.isDebugEnabled()) { 202 log.debug(String.format( 203 "Contributor %s is different from the last contributor %s => will create a version of the document.", 204 principal.getName(), lastContributor)); 205 } 206 return true; 207 } 208 Calendar lastModificationDate = (Calendar) doc.getPropertyValue("dc:modified"); 209 if (lastModificationDate == null) { 210 log.debug("Last modification date is null => will not create a version of the document."); 211 return true; 212 } 213 long lastModified = System.currentTimeMillis() - lastModificationDate.getTimeInMillis(); 214 long versioningDelayMillis = (long) getVersioningDelay() * 1000; 215 if (lastModified > versioningDelayMillis) { 216 if (log.isDebugEnabled()) { 217 log.debug(String.format( 218 "Last modification was done %d milliseconds ago, this is more than the versioning delay %d milliseconds => will create a version of the document.", 219 lastModified, versioningDelayMillis)); 220 } 221 return true; 222 } 223 if (log.isDebugEnabled()) { 224 log.debug(String.format( 225 "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.", 226 principal.getName(), lastModified, versioningDelayMillis)); 227 } 228 return false; 229 } 230 231 @Override 232 public double getVersioningDelay() { 233 return versioningDelay; 234 } 235 236 @Override 237 public void setVersioningDelay(double versioningDelay) { 238 this.versioningDelay = versioningDelay; 239 } 240 241 @Override 242 public VersioningOption getVersioningOption() { 243 return versioningOption; 244 } 245 246 @Override 247 public void setVersioningOption(VersioningOption versioningOption) { 248 this.versioningOption = versioningOption; 249 } 250 251 /*--------------------------- Protected ---------------------------------*/ 252 protected boolean hasBlob(DocumentModel doc) { 253 BlobHolder bh = doc.getAdapter(BlobHolder.class); 254 if (bh == null) { 255 if (log.isDebugEnabled()) { 256 log.debug(String.format("Document %s is not a BlobHolder.", doc.getId())); 257 } 258 return false; 259 } 260 Blob blob = bh.getBlob(); 261 if (blob == null) { 262 if (log.isDebugEnabled()) { 263 log.debug(String.format("Document %s is a BlobHolder without a blob.", doc.getId())); 264 } 265 return false; 266 } 267 return true; 268 } 269 270}