/* * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nuxeo - initial API and implementation */ package org.nuxeo.ecm.platform.filemanager.service.extension; import static org.nuxeo.ecm.core.api.security.SecurityConstants.ADD_CHILDREN; import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ_PROPERTIES; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentSecurityException; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.ecm.core.api.VersioningOption; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; import org.nuxeo.ecm.core.blob.BlobManager; import org.nuxeo.ecm.core.blob.BlobProvider; import org.nuxeo.ecm.platform.filemanager.service.FileManagerService; import org.nuxeo.ecm.platform.filemanager.utils.FileManagerUtils; import org.nuxeo.ecm.platform.types.Type; import org.nuxeo.ecm.platform.types.TypeManager; import org.nuxeo.runtime.api.Framework; /** * File importer abstract class. * <p> * Default file importer behavior. * * @see FileImporter * @author <a href="mailto:akalogeropoulos@nuxeo.com">Andreas Kalogeropolos</a> */ public abstract class AbstractFileImporter implements FileImporter { private static final long serialVersionUID = 1L; protected String name = ""; protected String docType; protected List<String> filters = new ArrayList<>(); protected List<Pattern> patterns; protected boolean enabled = true; protected Integer order = 0; public static final String SKIP_UPDATE_AUDIT_LOGGING = "org.nuxeo.filemanager.skip.audit.logging.forupdates"; // duplicated from Audit module to avoid circular dependency public static final String DISABLE_AUDIT_LOGGER = "disableAuditLogger"; // to be used by plugin implementation to gain access to standard file // creation utility methods without having to lookup the service protected FileManagerService fileManagerService; public List<String> getFilters() { return filters; } public void setFilters(List<String> filters) { this.filters = filters; patterns = new ArrayList<>(); for (String filter : filters) { patterns.add(Pattern.compile(filter)); } } public boolean matches(String mimeType) { for (Pattern pattern : patterns) { if (pattern.matcher(mimeType).matches()) { return true; } } return false; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDocType() { return docType; } public void setDocType(String docType) { this.docType = docType; } /** * Gets the doc type to use in the given container. */ public String getDocType(DocumentModel container) { return getDocType(); // use XML configuration } /** * Default document type to use when the plugin XML configuration does not specify one. * <p> * To implement when the default {@link #create} method is used. */ public String getDefaultDocType() { throw new UnsupportedOperationException(); } /** * Whether document overwrite is detected by checking title or filename. * <p> * To implement when the default {@link #create} method is used. */ public boolean isOverwriteByTitle() { throw new UnsupportedOperationException(); } /** * Creates the document (sets its properties). {@link #updateDocument} will be called after this. * <p> * Default implementation sets the title. */ public void createDocument(DocumentModel doc, Blob content, String title) { doc.setPropertyValue("dc:title", title); } /** * Tries to update the document <code>doc</code> with the blob <code>content</code>. * <p> * Returns <code>true</code> if the document is really updated. * * @since 7.1 */ public boolean updateDocumentIfPossible(DocumentModel doc, Blob content) { updateDocument(doc, content); return true; } /** * Updates the document (sets its properties). * <p> * Default implementation sets the content. */ public void updateDocument(DocumentModel doc, Blob content) { doc.getAdapter(BlobHolder.class).setBlob(content); } public Blob getBlob(DocumentModel doc) { return doc.getAdapter(BlobHolder.class).getBlob(); } @Override public DocumentModel create(CoreSession session, Blob content, String path, boolean overwrite, String fullname, TypeManager typeService) throws IOException { path = getNearestContainerPath(session, path); DocumentModel container = session.getDocument(new PathRef(path)); String docType = getDocType(container); // from override or descriptor if (docType == null) { docType = getDefaultDocType(); } doSecurityCheck(session, path, docType, typeService); String filename = FileManagerUtils.fetchFileName(fullname); String title = FileManagerUtils.fetchTitle(filename); content.setFilename(filename); // look for an existing document with same title or filename DocumentModel doc; if (isOverwriteByTitle()) { doc = FileManagerUtils.getExistingDocByTitle(session, path, title); } else { doc = FileManagerUtils.getExistingDocByFileName(session, path, filename); } if (overwrite && doc != null) { Blob previousBlob = getBlob(doc); // check that previous blob allows overwrite if (previousBlob != null) { BlobProvider blobProvider = Framework.getService(BlobManager.class).getBlobProvider(previousBlob); if (blobProvider != null && !blobProvider.supportsUserUpdate()) { throw new DocumentSecurityException("Cannot overwrite blob"); } } // update data boolean isDocumentUpdated = updateDocumentIfPossible(doc, content); if (!isDocumentUpdated) { return null; } if (Framework.isBooleanPropertyTrue(SKIP_UPDATE_AUDIT_LOGGING)) { // skip the update event if configured to do so doc.putContextData(DISABLE_AUDIT_LOGGER, true); } // save doc.putContextData(CoreSession.SOURCE, "fileimporter-" + getName()); doc = doc.getCoreSession().saveDocument(doc); } else { // create document model doc = session.createDocumentModel(docType); createDocument(doc, content, title); // set path PathSegmentService pss = Framework.getLocalService(PathSegmentService.class); doc.setPathInfo(path, pss.generatePathSegment(doc)); // update data updateDocument(doc, content); // create doc.putContextData(CoreSession.SOURCE, "fileimporter-" + getName()); doc = session.createDocument(doc); } session.save(); return doc; } /** * Avoid checkin for a 0-length blob. Microsoft-WebDAV-MiniRedir first creates a 0-length file and then locks it * before putting the real file. But we don't want this first placeholder to cause a versioning event. * * @deprecated since 9.1 automatic versioning is now handled at versioning service level, remove versioning * behaviors from importers */ @Deprecated protected boolean skipCheckInForBlob(Blob blob) { return blob == null || blob.getLength() == 0; } public FileManagerService getFileManagerService() { return fileManagerService; } public void setFileManagerService(FileManagerService fileManagerService) { this.fileManagerService = fileManagerService; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public boolean isEnabled() { return enabled; } public Integer getOrder() { return order; } public void setOrder(Integer order) { this.order = order; } public int compareTo(FileImporter other) { Integer otherOrder = other.getOrder(); if (order == null && otherOrder == null) { return 0; } else if (order == null) { return 1; } else if (otherOrder == null) { return -1; } return order.compareTo(otherOrder); } /** * Returns nearest container path * <p> * If given path points to a folderish document, return it. Else, return parent path. */ protected String getNearestContainerPath(CoreSession documentManager, String path) { DocumentModel currentDocument = documentManager.getDocument(new PathRef(path)); if (!currentDocument.isFolder()) { path = path.substring(0, path.lastIndexOf('/')); } return path; } /** * @deprecated since 9.1 automatic versioning is now handled at versioning service level, remove versioning * behaviors from importers */ @Deprecated protected void checkIn(DocumentModel doc) { VersioningOption option = fileManagerService.getVersioningOption(); if (option != null && option != VersioningOption.NONE) { if (doc.isCheckedOut()) { doc.checkIn(option, null); } } } /** * @deprecated since 9.1 automatic versioning is now handled at versioning service level, remove versioning * behaviors from importers */ @Deprecated protected void checkInAfterAdd(DocumentModel doc) { if (fileManagerService.doVersioningAfterAdd()) { checkIn(doc); } } protected void doSecurityCheck(CoreSession documentManager, String path, String typeName, TypeManager typeService) throws DocumentSecurityException { // perform the security checks PathRef containerRef = new PathRef(path); if (!documentManager.hasPermission(containerRef, READ_PROPERTIES) || !documentManager.hasPermission(containerRef, ADD_CHILDREN)) { throw new DocumentSecurityException("Not enough rights to create folder"); } DocumentModel container = documentManager.getDocument(containerRef); Type containerType = typeService.getType(container.getType()); if (containerType == null) { return; } if (!typeService.isAllowedSubType(typeName, container.getType(), container)) { throw new NuxeoException(String.format("Cannot create document of type %s in container with type %s", typeName, containerType.getId())); } } }