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