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