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