001/* 002 * (C) Copyright 2002 - 2006 Nuxeo SARL <http://nuxeo.com> and others 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Nuxeo - initial API and implementation 011 * 012 * 013 * $Id: AbstractPlugin.java 4105 2006-10-15 12:29:25Z sfermigier $ 014 */ 015 016package org.nuxeo.ecm.platform.filemanager.service.extension; 017 018import static org.nuxeo.ecm.core.api.security.SecurityConstants.ADD_CHILDREN; 019import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ_PROPERTIES; 020 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.regex.Pattern; 025 026import org.nuxeo.ecm.core.api.Blob; 027import org.nuxeo.ecm.core.api.CoreSession; 028import org.nuxeo.ecm.core.api.DocumentModel; 029import org.nuxeo.ecm.core.api.DocumentSecurityException; 030import org.nuxeo.ecm.core.api.NuxeoException; 031import org.nuxeo.ecm.core.api.PathRef; 032import org.nuxeo.ecm.core.api.VersioningOption; 033import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 034import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 035import org.nuxeo.ecm.core.blob.BlobManager; 036import org.nuxeo.ecm.core.blob.BlobProvider; 037import org.nuxeo.ecm.core.versioning.VersioningService; 038import org.nuxeo.ecm.platform.filemanager.service.FileManagerService; 039import org.nuxeo.ecm.platform.filemanager.utils.FileManagerUtils; 040import org.nuxeo.ecm.platform.types.Type; 041import org.nuxeo.ecm.platform.types.TypeManager; 042import org.nuxeo.runtime.api.Framework; 043 044/** 045 * File importer abstract class. 046 * <p> 047 * Default file importer behavior. 048 * 049 * @see FileImporter 050 * @author <a href="mailto:akalogeropoulos@nuxeo.com">Andreas Kalogeropolos</a> 051 */ 052public abstract class AbstractFileImporter implements FileImporter { 053 054 private static final long serialVersionUID = 1L; 055 056 protected String name = ""; 057 058 protected String docType; 059 060 protected List<String> filters = new ArrayList<String>(); 061 062 protected List<Pattern> patterns; 063 064 protected boolean enabled = true; 065 066 protected Integer order = 0; 067 068 public static final String SKIP_UPDATE_AUDIT_LOGGING = "org.nuxeo.filemanager.skip.audit.logging.forupdates"; 069 070 // duplicated from Audit module to avoid circular dependency 071 public static final String DISABLE_AUDIT_LOGGER = "disableAuditLogger"; 072 073 // to be used by plugin implementation to gain access to standard file 074 // creation utility methods without having to lookup the service 075 protected FileManagerService fileManagerService; 076 077 public List<String> getFilters() { 078 return filters; 079 } 080 081 public void setFilters(List<String> filters) { 082 this.filters = filters; 083 patterns = new ArrayList<Pattern>(); 084 for (String filter : filters) { 085 patterns.add(Pattern.compile(filter)); 086 } 087 } 088 089 public boolean matches(String mimeType) { 090 for (Pattern pattern : patterns) { 091 if (pattern.matcher(mimeType).matches()) { 092 return true; 093 } 094 } 095 return false; 096 } 097 098 public String getName() { 099 return name; 100 } 101 102 public void setName(String name) { 103 this.name = name; 104 } 105 106 public String getDocType() { 107 return docType; 108 } 109 110 public void setDocType(String docType) { 111 this.docType = docType; 112 } 113 114 /** 115 * Gets the doc type to use in the given container. 116 */ 117 public String getDocType(DocumentModel container) { 118 return getDocType(); // use XML configuration 119 } 120 121 /** 122 * Default document type to use when the plugin XML configuration does not specify one. 123 * <p> 124 * To implement when the default {@link #create} method is used. 125 */ 126 public String getDefaultDocType() { 127 throw new UnsupportedOperationException(); 128 } 129 130 /** 131 * Whether document overwrite is detected by checking title or filename. 132 * <p> 133 * To implement when the default {@link #create} method is used. 134 */ 135 public boolean isOverwriteByTitle() { 136 throw new UnsupportedOperationException(); 137 } 138 139 /** 140 * Creates the document (sets its properties). {@link #updateDocument} will be called after this. 141 * <p> 142 * Default implementation sets the title. 143 */ 144 public void createDocument(DocumentModel doc, Blob content, String title) { 145 doc.setPropertyValue("dc:title", title); 146 } 147 148 /** 149 * Tries to update the document <code>doc</code> with the blob <code>content</code>. 150 * <p> 151 * Returns <code>true</code> if the document is really updated. 152 * 153 * @since 7.1 154 */ 155 public boolean updateDocumentIfPossible(DocumentModel doc, Blob content) { 156 updateDocument(doc, content); 157 return true; 158 } 159 160 /** 161 * Updates the document (sets its properties). 162 * <p> 163 * Default implementation sets the content. 164 */ 165 public void updateDocument(DocumentModel doc, Blob content) { 166 doc.getAdapter(BlobHolder.class).setBlob(content); 167 } 168 169 public Blob getBlob(DocumentModel doc) { 170 return doc.getAdapter(BlobHolder.class).getBlob(); 171 } 172 173 @Override 174 public DocumentModel create(CoreSession session, Blob content, String path, boolean overwrite, String fullname, 175 TypeManager typeService) throws IOException { 176 path = getNearestContainerPath(session, path); 177 DocumentModel container = session.getDocument(new PathRef(path)); 178 String docType = getDocType(container); // from override or descriptor 179 if (docType == null) { 180 docType = getDefaultDocType(); 181 } 182 doSecurityCheck(session, path, docType, typeService); 183 String filename = FileManagerUtils.fetchFileName(fullname); 184 String title = FileManagerUtils.fetchTitle(filename); 185 content.setFilename(filename); 186 // look for an existing document with same title or filename 187 DocumentModel doc; 188 if (isOverwriteByTitle()) { 189 doc = FileManagerUtils.getExistingDocByTitle(session, path, title); 190 } else { 191 doc = FileManagerUtils.getExistingDocByFileName(session, path, filename); 192 } 193 boolean skipCheckInAfterAdd = false; 194 if (overwrite && doc != null) { 195 Blob previousBlob = getBlob(doc); 196 // check that previous blob allows overwrite 197 if (previousBlob != null) { 198 BlobProvider blobProvider = Framework.getService(BlobManager.class).getBlobProvider(previousBlob); 199 if (blobProvider != null && !blobProvider.supportsUserUpdate()) { 200 throw new DocumentSecurityException("Cannot overwrite blob"); 201 } 202 } 203 // make sure we save any existing data 204 if (!skipCheckInForBlob(previousBlob)) { 205 checkIn(doc); 206 } 207 // update data 208 boolean isDocumentUpdated = updateDocumentIfPossible(doc, content); 209 if (!isDocumentUpdated) { 210 return null; 211 } 212 if (Framework.isBooleanPropertyTrue(SKIP_UPDATE_AUDIT_LOGGING)) { 213 // skip the update event if configured to do so 214 doc.putContextData(DISABLE_AUDIT_LOGGER, true); 215 } 216 // save 217 doc = doc.getCoreSession().saveDocument(doc); 218 } else { 219 // create document model 220 doc = session.createDocumentModel(docType); 221 createDocument(doc, content, title); 222 // set path 223 PathSegmentService pss = Framework.getLocalService(PathSegmentService.class); 224 doc.setPathInfo(path, pss.generatePathSegment(doc)); 225 // update data 226 updateDocument(doc, content); 227 skipCheckInAfterAdd = skipCheckInForBlob(content); 228 // create 229 doc = session.createDocument(doc); 230 } 231 // check in if requested 232 if (!skipCheckInAfterAdd) { 233 checkInAfterAdd(doc); 234 } 235 session.save(); 236 return doc; 237 } 238 239 /** 240 * Avoid checkin for a 0-length blob. Microsoft-WebDAV-MiniRedir first creates a 0-length file and then locks it 241 * before putting the real file. But we don't want this first placeholder to cause a versioning event. 242 */ 243 protected boolean skipCheckInForBlob(Blob blob) { 244 return blob == null || blob.getLength() == 0; 245 } 246 247 public FileManagerService getFileManagerService() { 248 return fileManagerService; 249 } 250 251 public void setFileManagerService(FileManagerService fileManagerService) { 252 this.fileManagerService = fileManagerService; 253 } 254 255 public void setEnabled(boolean enabled) { 256 this.enabled = enabled; 257 } 258 259 public boolean isEnabled() { 260 return enabled; 261 } 262 263 public Integer getOrder() { 264 return order; 265 } 266 267 public void setOrder(Integer order) { 268 this.order = order; 269 } 270 271 public int compareTo(FileImporter other) { 272 Integer otherOrder = other.getOrder(); 273 if (order == null && otherOrder == null) { 274 return 0; 275 } else if (order == null) { 276 return 1; 277 } else if (otherOrder == null) { 278 return -1; 279 } 280 return order.compareTo(otherOrder); 281 } 282 283 /** 284 * Returns nearest container path 285 * <p> 286 * If given path points to a folderish document, return it. Else, return parent path. 287 */ 288 protected String getNearestContainerPath(CoreSession documentManager, String path) { 289 DocumentModel currentDocument = documentManager.getDocument(new PathRef(path)); 290 if (!currentDocument.isFolder()) { 291 path = path.substring(0, path.lastIndexOf('/')); 292 } 293 return path; 294 } 295 296 protected void checkIn(DocumentModel doc) { 297 VersioningOption option = fileManagerService.getVersioningOption(); 298 if (option != null && option != VersioningOption.NONE) { 299 if (doc.isCheckedOut()) { 300 doc.checkIn(option, null); 301 } 302 } 303 } 304 305 protected void checkInAfterAdd(DocumentModel doc) { 306 if (fileManagerService.doVersioningAfterAdd()) { 307 checkIn(doc); 308 } 309 } 310 311 /** 312 * @deprecated use {@link #checkIn} instead, noting that it does not save the document 313 */ 314 @Deprecated 315 protected DocumentModel overwriteAndIncrementversion(CoreSession documentManager, DocumentModel doc) 316 { 317 doc.putContextData(VersioningService.VERSIONING_OPTION, fileManagerService.getVersioningOption()); 318 return documentManager.saveDocument(doc); 319 } 320 321 protected void doSecurityCheck(CoreSession documentManager, String path, String typeName, TypeManager typeService) 322 throws DocumentSecurityException { 323 // perform the security checks 324 PathRef containerRef = new PathRef(path); 325 if (!documentManager.hasPermission(containerRef, READ_PROPERTIES) 326 || !documentManager.hasPermission(containerRef, ADD_CHILDREN)) { 327 throw new DocumentSecurityException("Not enough rights to create folder"); 328 } 329 DocumentModel container = documentManager.getDocument(containerRef); 330 331 Type containerType = typeService.getType(container.getType()); 332 if (containerType == null) { 333 return; 334 } 335 336 if (!typeService.isAllowedSubType(typeName, container.getType(), container)) { 337 throw new NuxeoException(String.format("Cannot create document of type %s in container with type %s", 338 typeName, containerType.getId())); 339 } 340 } 341 342}