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}