001/*
002 * (C) Copyright 2008-2019 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 */
019package org.nuxeo.ecm.platform.filemanager.core.listener;
020
021import static org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry.DEFAULT_MIMETYPE;
022
023import java.io.Serializable;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027import org.nuxeo.ecm.core.api.Blob;
028import org.nuxeo.ecm.core.api.DocumentModel;
029import org.nuxeo.ecm.core.api.PropertyException;
030import org.nuxeo.ecm.core.api.model.Property;
031import org.nuxeo.ecm.core.event.Event;
032import org.nuxeo.ecm.core.event.EventContext;
033import org.nuxeo.ecm.core.event.EventListener;
034import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
035import org.nuxeo.ecm.core.schema.FacetNames;
036import org.nuxeo.ecm.core.utils.BlobsExtractor;
037import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeEntry;
038import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
039import org.nuxeo.ecm.platform.types.Type;
040import org.nuxeo.ecm.platform.types.TypeManager;
041import org.nuxeo.runtime.api.Framework;
042
043/**
044 * Listener responsible for computing the mimetype of a new or edited blob and the {@code common:icon} field if
045 * necessary.
046 * <p>
047 * The logic of this event listener is divided into static public methods to make it easy to override this event
048 * listener with a custom implementation.
049 *
050 * @author ogrisel
051 */
052public class MimetypeIconUpdater implements EventListener {
053
054    protected Log log = LogFactory.getLog(MimetypeIconUpdater.class);
055
056    public static final String ICON_SCHEMA = "common";
057
058    public static final String ICON_FIELD = ICON_SCHEMA + ":" + "icon";
059
060    public static final String MAIN_BLOB_FIELD = "file:content";
061
062    public static final String MAIN_BLOB_SCHEMA = "file";
063
064    /**
065     * @deprecated since 11.1. Use {@link MimetypeRegistry#DEFAULT_MIMETYPE} instead.
066     */
067    @Deprecated(since = "11.1", forRemoval = true)
068    protected static final String OCTET_STREAM_MT = DEFAULT_MIMETYPE;
069
070    /**
071     * @deprecated since 11.1. Create a new instance of {@link BlobsExtractor} when needed.
072     */
073    @Deprecated(since = "11.1", forRemoval = true)
074    public final BlobsExtractor blobExtractor = new BlobsExtractor();
075
076    /**
077     * @deprecated since 11.1. Use {@link Framework#getService(Class)} with {@link MimetypeRegistry} instead.
078     */
079    @Deprecated(since = "11.1", forRemoval = true)
080    MimetypeRegistry mimetypeService;
081
082    /**
083     * @deprecated since 11.1. Use {@link Framework#getService(Class)} with {@link MimetypeRegistry} instead.
084     */
085    @Deprecated(since = "11.1", forRemoval = true)
086    public MimetypeRegistry getMimetypeRegistry() {
087        if (mimetypeService == null) {
088            mimetypeService = Framework.getService(MimetypeRegistry.class);
089        }
090
091        return mimetypeService;
092    }
093
094    @Override
095    public void handleEvent(Event event) {
096
097        EventContext ctx = event.getContext();
098        if (ctx instanceof DocumentEventContext) {
099
100            DocumentEventContext docCtx = (DocumentEventContext) ctx;
101            DocumentModel doc = docCtx.getSourceDocument();
102
103            // Don't update icon for immutable documents
104            if (doc.hasFacet(FacetNames.IMMUTABLE)) {
105                return;
106            }
107
108            try {
109                // ensure the document main icon is not null
110                setDefaultIcon(doc);
111
112                // update mimetypes of blobs in the document
113
114                MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class);
115                BlobsExtractor extractor = new BlobsExtractor();
116                for (Property prop : extractor.getBlobsProperties(doc)) {
117                    if (prop.isDirty()) {
118                        updateBlobProperty(doc, mimetypeRegistry, prop);
119                    }
120                }
121
122                // update the document icon and size according to the main blob
123                if (doc.hasSchema(MAIN_BLOB_SCHEMA) && doc.getProperty(MAIN_BLOB_FIELD).isDirty()) {
124                    updateIconAndSizeFields(doc, mimetypeRegistry,
125                            doc.getProperty(MAIN_BLOB_FIELD).getValue(Blob.class));
126                }
127            } catch (PropertyException e) {
128                e.addInfo("Error in MimetypeIconUpdater listener");
129                throw e;
130            }
131        }
132    }
133
134    /**
135     * Updates the mimetype of a blob along with the icon and size fields of the document if the blob is the main blob
136     * of the document.
137     */
138    public void updateBlobProperty(DocumentModel doc, MimetypeRegistry mimetypeService, Property dirtyProperty) {
139        String fieldPath = dirtyProperty.getXPath();
140        if (!fieldPath.contains(":")) {
141            // for schema without prefix: we need to add schema name as prefix
142            fieldPath = dirtyProperty.getSchema().getName() + ":" + fieldPath;
143        }
144
145        Blob blob = dirtyProperty.getValue(Blob.class);
146        if (blob == null) {
147            return;
148        }
149        if (blob.getMimeType() == null || blob.getMimeType().startsWith(DEFAULT_MIMETYPE)) {
150            // update the mime type (if not set) using the mimetype registry service
151            blob = mimetypeService.updateMimetype(blob);
152            doc.setPropertyValue(fieldPath, (Serializable) blob);
153        } else if (!mimetypeService.isMimeTypeNormalized(blob.getMimeType())) {
154            // normalize the mime type if not yet normalized
155            mimetypeService.getNormalizedMimeType(blob.getMimeType()).ifPresent(blob::setMimeType);
156        }
157    }
158
159    private void updateIconAndSizeFields(DocumentModel doc, MimetypeRegistry mimetypeService, Blob blob) {
160        // update the icon field of the document
161        if (blob != null && !doc.isFolder()) {
162            MimetypeEntry mimetypeEntry = mimetypeService.getMimetypeEntryByMimeType(blob.getMimeType());
163            updateIconField(mimetypeEntry, doc);
164        } else {
165            // reset to document type icon
166            updateIconField(null, doc);
167        }
168    }
169
170    /**
171     * If the icon field is empty, initialize it to the document type icon
172     */
173    public void setDefaultIcon(DocumentModel doc) {
174        if (doc.hasSchema(ICON_SCHEMA) && doc.getProperty(ICON_FIELD).getValue(String.class) == null) {
175            updateIconField(null, doc);
176        }
177    }
178
179    /**
180     * Computes the main icon of a Nuxeo document based on the mime type of the main attached blob with of fallback on
181     * the document type generic icon.
182     */
183    public void updateIconField(MimetypeEntry mimetypeEntry, DocumentModel doc) {
184        String iconPath = null;
185        if (mimetypeEntry != null && mimetypeEntry.getIconPath() != null) {
186            iconPath = "/icons/" + mimetypeEntry.getIconPath();
187        } else {
188            TypeManager typeManager = Framework.getService(TypeManager.class);
189            if (typeManager == null) {
190                return;
191            }
192            Type uiType = typeManager.getType(doc.getType());
193            if (uiType != null) {
194                iconPath = uiType.getIcon();
195            }
196        }
197        if (iconPath != null && doc.hasSchema(ICON_SCHEMA)) {
198            doc.setPropertyValue(ICON_FIELD, iconPath);
199        }
200    }
201
202}