001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * vpasquier <vpasquier@nuxeo.com> 016 */ 017package org.nuxeo.binary.metadata.internals; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.jboss.el.ExpressionFactoryImpl; 032import org.nuxeo.binary.metadata.api.BinaryMetadataConstants; 033import org.nuxeo.binary.metadata.api.BinaryMetadataException; 034import org.nuxeo.binary.metadata.api.BinaryMetadataProcessor; 035import org.nuxeo.binary.metadata.api.BinaryMetadataService; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.CoreSession; 038import org.nuxeo.ecm.core.api.DocumentModel; 039import org.nuxeo.ecm.core.api.model.Property; 040import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 041import org.nuxeo.ecm.platform.actions.ActionContext; 042import org.nuxeo.ecm.platform.actions.ELActionContext; 043import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 044import org.nuxeo.ecm.platform.el.ExpressionContext; 045import org.nuxeo.runtime.api.Framework; 046 047import com.google.common.base.Predicate; 048import com.google.common.collect.Sets; 049 050/** 051 * @since 7.1 052 */ 053public class BinaryMetadataServiceImpl implements BinaryMetadataService { 054 055 private static final Log log = LogFactory.getLog(BinaryMetadataServiceImpl.class); 056 057 @Override 058 public Map<String, Object> readMetadata(String processorName, Blob blob, List<String> metadataNames, 059 boolean ignorePrefix) { 060 try { 061 BinaryMetadataProcessor processor = getProcessor(processorName); 062 return processor.readMetadata(blob, metadataNames, ignorePrefix); 063 } catch (NoSuchMethodException e) { 064 throw new BinaryMetadataException(e); 065 } 066 } 067 068 @Override 069 public Map<String, Object> readMetadata(Blob blob, List<String> 070 metadataNames, boolean ignorePrefix) { 071 try { 072 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 073 return processor.readMetadata(blob, metadataNames, ignorePrefix); 074 } catch (NoSuchMethodException e) { 075 throw new BinaryMetadataException(e); 076 } 077 } 078 079 @Override 080 public Map<String, Object> readMetadata(Blob blob, boolean ignorePrefix) { 081 try { 082 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 083 return processor.readMetadata(blob, ignorePrefix); 084 } catch (NoSuchMethodException e) { 085 throw new BinaryMetadataException(e); 086 } 087 } 088 089 @Override 090 public Map<String, Object> readMetadata(String processorName, Blob blob, boolean ignorePrefix) { 091 try { 092 BinaryMetadataProcessor processor = getProcessor(processorName); 093 return processor.readMetadata(blob, ignorePrefix); 094 } catch (NoSuchMethodException e) { 095 throw new BinaryMetadataException(e); 096 } 097 } 098 099 @Override 100 public Blob writeMetadata(String processorName, Blob blob, Map<String, Object> metadata, boolean ignorePrefix) { 101 try { 102 BinaryMetadataProcessor processor = getProcessor(processorName); 103 return processor.writeMetadata(blob, metadata, ignorePrefix); 104 } catch (NoSuchMethodException e) { 105 throw new BinaryMetadataException(e); 106 } 107 } 108 109 @Override 110 public Blob writeMetadata(Blob blob, Map<String, Object> metadata, boolean ignorePrefix) { 111 try { 112 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 113 return processor.writeMetadata(blob, metadata, ignorePrefix); 114 } catch (NoSuchMethodException e) { 115 throw new BinaryMetadataException(e); 116 } 117 } 118 119 @Override 120 public Blob writeMetadata(String processorName, Blob blob, String mappingDescriptorId, DocumentModel doc) { 121 try { 122 // Creating mapping properties Map. 123 Map<String, Object> metadataMapping = new HashMap<>(); 124 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 125 mappingDescriptorId); 126 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 127 metadataMapping.put(metadataDescriptor.getName(), doc.getPropertyValue(metadataDescriptor.getXpath())); 128 } 129 BinaryMetadataProcessor processor = getProcessor(processorName); 130 return processor.writeMetadata(blob, metadataMapping, mappingDescriptor.getIgnorePrefix()); 131 } catch (NoSuchMethodException e) { 132 throw new BinaryMetadataException(e); 133 } 134 135 } 136 137 @Override 138 public Blob writeMetadata(Blob blob, String mappingDescriptorId, DocumentModel doc) { 139 try { 140 // Creating mapping properties Map. 141 Map<String, Object> metadataMapping = new HashMap<>(); 142 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 143 mappingDescriptorId); 144 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 145 metadataMapping.put(metadataDescriptor.getName(), doc.getPropertyValue(metadataDescriptor.getXpath())); 146 } 147 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 148 return processor.writeMetadata(blob, metadataMapping, mappingDescriptor.getIgnorePrefix()); 149 } catch (NoSuchMethodException e) { 150 throw new BinaryMetadataException(e); 151 } 152 } 153 154 @Override 155 public void writeMetadata(DocumentModel doc, CoreSession session) { 156 // Check if rules applying for this document. 157 ActionContext actionContext = createActionContext(doc); 158 Set<MetadataRuleDescriptor> ruleDescriptors = checkFilter(actionContext); 159 List<String> mappingDescriptorIds = new ArrayList<>(); 160 for (MetadataRuleDescriptor ruleDescriptor : ruleDescriptors) { 161 mappingDescriptorIds.addAll(ruleDescriptor.getMetadataMappingIdDescriptors()); 162 } 163 if (mappingDescriptorIds.isEmpty()) { 164 return; 165 } 166 167 // For each mapping descriptors, overriding mapping document properties. 168 for (String mappingDescriptorId : mappingDescriptorIds) { 169 if (!BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().containsKey(mappingDescriptorId)) { 170 log.warn("Missing binary metadata descriptor with id '" + mappingDescriptorId 171 + "'. Or check your rule contribution with proper metadataMapping-id."); 172 continue; 173 } 174 writeMetadata(doc, session, mappingDescriptorId); 175 } 176 } 177 178 @Override 179 public void writeMetadata(DocumentModel doc, CoreSession session, String mappingDescriptorId) { 180 // Creating mapping properties Map. 181 Map<String, String> metadataMapping = new HashMap<>(); 182 List<String> blobMetadata = new ArrayList<>(); 183 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 184 mappingDescriptorId); 185 boolean ignorePrefix = mappingDescriptor.getIgnorePrefix(); 186 // Extract blob from the contributed xpath 187 Blob blob = doc.getProperty(mappingDescriptor.getBlobXPath()).getValue(Blob.class); 188 if (blob != null && mappingDescriptor.getMetadataDescriptors() != null 189 && !mappingDescriptor.getMetadataDescriptors().isEmpty()) { 190 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 191 metadataMapping.put(metadataDescriptor.getName(), metadataDescriptor.getXpath()); 192 blobMetadata.add(metadataDescriptor.getName()); 193 } 194 195 // Extract metadata from binary. 196 String processorId = mappingDescriptor.getProcessor(); 197 Map<String, Object> blobMetadataOutput; 198 if (processorId != null) { 199 blobMetadataOutput = readMetadata(processorId, blob, blobMetadata, ignorePrefix); 200 201 } else { 202 blobMetadataOutput = readMetadata(blob, blobMetadata, ignorePrefix); 203 } 204 205 // Write doc properties from outputs. 206 for (String metadata : blobMetadataOutput.keySet()) { 207 Object metadataValue = blobMetadataOutput.get(metadata); 208 if (!(metadataValue instanceof Date)) { 209 metadataValue = metadataValue.toString(); 210 } 211 doc.setPropertyValue(metadataMapping.get(metadata), (Serializable) metadataValue); 212 } 213 214 // document should exist if id != null 215 if (doc.getId() != null && session.exists(doc.getRef())) { 216 session.saveDocument(doc); 217 } 218 } 219 } 220 221 /*--------------------- Event Service --------------------------*/ 222 223 @Override 224 public void handleSyncUpdate(DocumentModel doc, DocumentEventContext docCtx) { 225 LinkedList<MetadataMappingDescriptor> syncMappingDescriptors = getSyncMapping(doc, docCtx); 226 if (syncMappingDescriptors != null) { 227 handleUpdate(syncMappingDescriptors, doc, docCtx); 228 } 229 } 230 231 @Override 232 public void handleUpdate(List<MetadataMappingDescriptor> mappingDescriptors, DocumentModel doc, 233 DocumentEventContext docCtx) { 234 for (MetadataMappingDescriptor mappingDescriptor : mappingDescriptors) { 235 Property fileProp = doc.getProperty(mappingDescriptor.getBlobXPath()); 236 boolean isDirtyMapping = isDirtyMapping(mappingDescriptor, doc); 237 Blob blob = fileProp.getValue(Blob.class); 238 if (blob != null) { 239 if (fileProp.isDirty()) { 240 if (isDirtyMapping) { 241 // if Blob dirty and document metadata dirty, write metadata from doc to Blob 242 Blob newBlob = writeMetadata(fileProp.getValue(Blob.class), mappingDescriptor.getId(), doc); 243 fileProp.setValue(newBlob); 244 } else { 245 // if Blob dirty and document metadata not dirty, write metadata from Blob to doc 246 writeMetadata(doc, docCtx.getCoreSession()); 247 } 248 } else { 249 if (isDirtyMapping) { 250 // if Blob not dirty and document metadata dirty, write metadata from doc to Blob 251 Blob newBlob = writeMetadata(fileProp.getValue(Blob.class), mappingDescriptor.getId(), doc); 252 fileProp.setValue(newBlob); 253 } 254 } 255 } 256 } 257 } 258 259 /*--------------------- Utils --------------------------*/ 260 261 /** 262 * Check for each Binary Rule if the document is accepted or not. 263 * 264 * @return the list of metadata which should be processed sorted by rules order. (high to low priority) 265 */ 266 protected Set<MetadataRuleDescriptor> checkFilter(final ActionContext actionContext) { 267 final ActionManager actionService = Framework.getLocalService(ActionManager.class); 268 Set<MetadataRuleDescriptor> filtered = Sets.filter(BinaryMetadataComponent.self.ruleRegistry.contribs, 269 new Predicate<MetadataRuleDescriptor>() { 270 271 @Override 272 public boolean apply(MetadataRuleDescriptor input) { 273 if (!input.getEnabled()) { 274 return false; 275 } 276 for (String filterId : input.getFilterIds()) { 277 if (!actionService.checkFilter(filterId, actionContext)) { 278 return false; 279 } 280 } 281 return true; 282 } 283 284 }); 285 return filtered; 286 } 287 288 protected ActionContext createActionContext(DocumentModel doc) { 289 ActionContext actionContext = new ELActionContext(new ExpressionContext(), new ExpressionFactoryImpl()); 290 actionContext.setCurrentDocument(doc); 291 return actionContext; 292 } 293 294 protected BinaryMetadataProcessor getProcessor(String processorId) throws NoSuchMethodException { 295 return BinaryMetadataComponent.self.processorRegistry.getProcessor(processorId); 296 } 297 298 /** 299 * @return Dirty metadata from metadata mapping contribution and handle async processes. 300 */ 301 public LinkedList<MetadataMappingDescriptor> getSyncMapping(DocumentModel doc, DocumentEventContext docCtx) { 302 // Check if rules applying for this document. 303 ActionContext actionContext = createActionContext(doc); 304 Set<MetadataRuleDescriptor> ruleDescriptors = checkFilter(actionContext); 305 Set<String> syncMappingDescriptorIds = new HashSet<>(); 306 HashSet<String> asyncMappingDescriptorIds = new HashSet<>(); 307 for (MetadataRuleDescriptor ruleDescriptor : ruleDescriptors) { 308 if (ruleDescriptor.getIsAsync()) { 309 asyncMappingDescriptorIds.addAll(ruleDescriptor.getMetadataMappingIdDescriptors()); 310 continue; 311 } 312 syncMappingDescriptorIds.addAll(ruleDescriptor.getMetadataMappingIdDescriptors()); 313 } 314 315 // Handle async rules which should be taken into account in async listener. 316 if (!asyncMappingDescriptorIds.isEmpty()) { 317 docCtx.setProperty(BinaryMetadataConstants.ASYNC_MAPPING_RESULT, getMapping(asyncMappingDescriptorIds)); 318 docCtx.setProperty(BinaryMetadataConstants.ASYNC_BINARY_METADATA_EXECUTE, Boolean.TRUE); 319 } 320 321 if (syncMappingDescriptorIds.isEmpty()) { 322 return null; 323 } 324 return getMapping(syncMappingDescriptorIds); 325 } 326 327 protected LinkedList<MetadataMappingDescriptor> getMapping(Set<String> mappingDescriptorIds) { 328 // For each mapping descriptors, store mapping. 329 LinkedList<MetadataMappingDescriptor> mappingResult = new LinkedList<>(); 330 for (String mappingDescriptorId : mappingDescriptorIds) { 331 if (!BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().containsKey(mappingDescriptorId)) { 332 log.warn("Missing binary metadata descriptor with id '" + mappingDescriptorId 333 + "'. Or check your rule contribution with proper metadataMapping-id."); 334 continue; 335 } 336 mappingResult.add(BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 337 mappingDescriptorId)); 338 } 339 return mappingResult; 340 } 341 342 /** 343 * Maps inspector only. 344 */ 345 protected boolean isDirtyMapping(MetadataMappingDescriptor mappingDescriptor, DocumentModel doc) { 346 Map<String, String> mappingResult = new HashMap<>(); 347 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 348 mappingResult.put(metadataDescriptor.getXpath(), metadataDescriptor.getName()); 349 } 350 // Returning only dirty properties 351 HashMap<String, Object> resultDirtyMapping = new HashMap<>(); 352 for (String metadata : mappingResult.keySet()) { 353 Property property = doc.getProperty(metadata); 354 if (property.isDirty()) { 355 resultDirtyMapping.put(mappingResult.get(metadata), doc.getPropertyValue(metadata)); 356 } 357 } 358 return !resultDirtyMapping.isEmpty(); 359 } 360}