001/* 002 * (C) Copyright 2008 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 * 019 * $Id$ 020 */ 021package org.nuxeo.ecm.platform.filemanager.core.listener; 022 023import java.io.Serializable; 024import java.util.Iterator; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.nuxeo.ecm.core.api.Blob; 029import org.nuxeo.ecm.core.api.DocumentModel; 030import org.nuxeo.ecm.core.api.PropertyException; 031import org.nuxeo.ecm.core.api.model.Property; 032import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; 033import org.nuxeo.ecm.core.event.Event; 034import org.nuxeo.ecm.core.event.EventContext; 035import org.nuxeo.ecm.core.event.EventListener; 036import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 037import org.nuxeo.ecm.core.schema.FacetNames; 038import org.nuxeo.ecm.core.utils.BlobsExtractor; 039import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeEntry; 040import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; 041import org.nuxeo.ecm.platform.types.Type; 042import org.nuxeo.ecm.platform.types.TypeManager; 043import org.nuxeo.runtime.api.Framework; 044 045/** 046 * Listener responsible for computing the mimetype of a new or edited blob and the common:icon field if necessary. 047 * <p> 048 * The common:size is also maintained as the length of the main blob to preserve backward compatibility. 049 * <p> 050 * The logic of this event listener is divided into static public methods to make it easy to override this event 051 * listener with a custom implementation. 052 * 053 * @author ogrisel 054 */ 055public class MimetypeIconUpdater implements EventListener { 056 057 protected Log log = LogFactory.getLog(MimetypeIconUpdater.class); 058 059 public static final String ICON_SCHEMA = "common"; 060 061 public static final String ICON_FIELD = ICON_SCHEMA + ":" + "icon"; 062 063 public static final String MAIN_BLOB_FIELD = "file:content"; 064 065 public static final String MAIN_BLOB_SCHEMA = "file"; 066 067 @Deprecated 068 // the length of the main blob is now stored inside the blob itself 069 private static final String SIZE_FIELD = "common:size"; 070 071 @Deprecated 072 // the filename should now be stored inside the main blob 073 public static final String MAIN_EXTERNAL_FILENAME_FIELD = "file:filename"; 074 075 protected static final String OCTET_STREAM_MT = "application/octet-stream"; 076 077 public final BlobsExtractor blobExtractor = new BlobsExtractor(); 078 079 MimetypeRegistry mimetypeService; 080 081 public MimetypeRegistry getMimetypeRegistry() { 082 if (mimetypeService == null) { 083 mimetypeService = Framework.getService(MimetypeRegistry.class); 084 } 085 086 return mimetypeService; 087 } 088 089 public void handleEvent(Event event) { 090 091 EventContext ctx = event.getContext(); 092 if (ctx instanceof DocumentEventContext) { 093 094 DocumentEventContext docCtx = (DocumentEventContext) ctx; 095 DocumentModel doc = docCtx.getSourceDocument(); 096 097 // Don't update icon for immutable documents 098 if (doc.hasFacet(FacetNames.IMMUTABLE)) { 099 return; 100 } 101 102 // BBB: handle old filename scheme 103 updateFilename(doc); 104 105 try { 106 // ensure the document main icon is not null 107 setDefaultIcon(doc); 108 109 // update mimetypes of blobs in the document 110 for (Property prop : blobExtractor.getBlobsProperties(doc)) { 111 if (prop.isDirty()) { 112 updateBlobProperty(doc, getMimetypeRegistry(), prop); 113 } 114 } 115 116 // update the document icon and size according to the main blob 117 if (doc.hasSchema(MAIN_BLOB_SCHEMA) && doc.getProperty(MAIN_BLOB_FIELD).isDirty()) { 118 updateIconAndSizeFields(doc, getMimetypeRegistry(), 119 doc.getProperty(MAIN_BLOB_FIELD).getValue(Blob.class)); 120 } 121 } catch (PropertyException e) { 122 e.addInfo("Error in MimetypeIconUpdater listener"); 123 throw e; 124 } 125 } 126 } 127 128 /** 129 * Recursively call updateBlobProperty on every dirty blob embedded as direct children or contained in one of the 130 * container children. 131 * 132 * @deprecated now we use {@link BlobsExtractor} that cache path fields. 133 */ 134 @Deprecated 135 // TODO: remove 136 public void recursivelyUpdateBlobs(DocumentModel doc, MimetypeRegistry mimetypeService, 137 Iterator<Property> dirtyChildren) { 138 while (dirtyChildren.hasNext()) { 139 Property dirtyProperty = dirtyChildren.next(); 140 if (dirtyProperty instanceof BlobProperty) { 141 updateBlobProperty(doc, mimetypeService, dirtyProperty); 142 } else if (dirtyProperty.isContainer()) { 143 recursivelyUpdateBlobs(doc, mimetypeService, dirtyProperty.getDirtyChildren()); 144 } 145 } 146 } 147 148 /** 149 * Update the mimetype of a blob along with the icon and size fields of the document if the blob is the main blob of 150 * the document. 151 */ 152 public void updateBlobProperty(DocumentModel doc, MimetypeRegistry mimetypeService, Property dirtyProperty) { 153 String fieldPath = dirtyProperty.getPath(); 154 // cas shema without prefix : we need to add schema name as prefix 155 if (!fieldPath.contains(":")) { 156 fieldPath = dirtyProperty.getSchema().getName() + ":" + fieldPath.substring(1); 157 } 158 159 Blob blob = dirtyProperty.getValue(Blob.class); 160 if (blob != null && (blob.getMimeType() == null || blob.getMimeType().equals(OCTET_STREAM_MT))) { 161 // update the mimetype (if not set) using the the mimetype registry 162 // service 163 blob = mimetypeService.updateMimetype(blob); 164 doc.setPropertyValue(fieldPath, (Serializable) blob); 165 } 166 } 167 168 private void updateIconAndSizeFields(DocumentModel doc, MimetypeRegistry mimetypeService, Blob blob) 169 throws PropertyException { 170 // update the icon field of the document 171 if (blob != null && !doc.isFolder()) { 172 MimetypeEntry mimetypeEntry = mimetypeService.getMimetypeEntryByMimeType(blob.getMimeType()); 173 updateIconField(mimetypeEntry, doc); 174 } else { 175 // reset to document type icon 176 updateIconField(null, doc); 177 } 178 179 // BBB: update the deprecated common:size field to preserver 180 // backward compatibility (we should only use 181 // file:content/length instead) 182 doc.setPropertyValue(SIZE_FIELD, blob != null ? blob.getLength() : 0); 183 } 184 185 /** 186 * Backward compatibility for external filename field: if edited, it might affect the main blob mimetype 187 */ 188 public void updateFilename(DocumentModel doc) throws PropertyException { 189 190 if (doc.hasSchema(MAIN_BLOB_FIELD.split(":")[0])) { 191 Property filenameProperty = doc.getProperty(MAIN_EXTERNAL_FILENAME_FIELD); 192 if (filenameProperty.isDirty()) { 193 String filename = filenameProperty.getValue(String.class); 194 if (doc.getProperty(MAIN_BLOB_FIELD).getValue() != null) { 195 Blob blob = doc.getProperty(MAIN_BLOB_FIELD).getValue(Blob.class); 196 blob.setFilename(filename); 197 doc.setPropertyValue(MAIN_BLOB_FIELD, (Serializable) blob); 198 } 199 } 200 } 201 } 202 203 /** 204 * If the icon field is empty, initialize it to the document type icon 205 */ 206 public void setDefaultIcon(DocumentModel doc) { 207 if (doc.hasSchema(ICON_SCHEMA) && doc.getProperty(ICON_FIELD).getValue(String.class) == null) { 208 updateIconField(null, doc); 209 } 210 } 211 212 /** 213 * Compute the main icon of a Nuxeo document based on the mimetype of the main attached blob with of fallback on the 214 * document type generic icon. 215 */ 216 public void updateIconField(MimetypeEntry mimetypeEntry, DocumentModel doc) { 217 String iconPath = null; 218 if (mimetypeEntry != null && mimetypeEntry.getIconPath() != null) { 219 iconPath = "/icons/" + mimetypeEntry.getIconPath(); 220 } else { 221 TypeManager typeManager = Framework.getService(TypeManager.class); 222 if (typeManager == null) { 223 return; 224 } 225 Type uiType = typeManager.getType(doc.getType()); 226 if (uiType != null) { 227 iconPath = uiType.getIcon(); 228 } 229 } 230 if (iconPath != null && doc.hasSchema(ICON_SCHEMA)) { 231 doc.setPropertyValue(ICON_FIELD, iconPath); 232 } 233 } 234 235}