001/* 002 * (C) Copyright 2014-2016 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 * vpasquier <vpasquier@nuxeo.com> 018 * ajusto <ajusto@nuxeo.com> 019 */ 020package org.nuxeo.binary.metadata.internals; 021 022import java.io.Serializable; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Date; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.binary.metadata.api.BinaryMetadataConstants; 036import org.nuxeo.binary.metadata.api.BinaryMetadataException; 037import org.nuxeo.binary.metadata.api.BinaryMetadataProcessor; 038import org.nuxeo.binary.metadata.api.BinaryMetadataService; 039import org.nuxeo.ecm.core.api.Blob; 040import org.nuxeo.ecm.core.api.CoreSession; 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.PropertyException; 043import org.nuxeo.ecm.core.api.model.Property; 044import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 045import org.nuxeo.ecm.platform.actions.ActionContext; 046import org.nuxeo.ecm.platform.actions.ELActionContext; 047import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 048import org.nuxeo.runtime.api.Framework; 049 050import com.google.common.base.Predicate; 051import com.google.common.collect.Sets; 052 053/** 054 * @since 7.1 055 */ 056public class BinaryMetadataServiceImpl implements BinaryMetadataService { 057 058 private static final Log log = LogFactory.getLog(BinaryMetadataServiceImpl.class); 059 060 @Override 061 public Map<String, Object> readMetadata(String processorName, Blob blob, List<String> metadataNames, 062 boolean ignorePrefix) { 063 try { 064 BinaryMetadataProcessor processor = getProcessor(processorName); 065 return processor.readMetadata(blob, metadataNames, ignorePrefix); 066 } catch (NoSuchMethodException e) { 067 throw new BinaryMetadataException(e); 068 } 069 } 070 071 @Override 072 public Map<String, Object> readMetadata(Blob blob, List<String> 073 metadataNames, boolean ignorePrefix) { 074 try { 075 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 076 return processor.readMetadata(blob, metadataNames, ignorePrefix); 077 } catch (NoSuchMethodException e) { 078 throw new BinaryMetadataException(e); 079 } 080 } 081 082 @Override 083 public Map<String, Object> readMetadata(Blob blob, boolean ignorePrefix) { 084 try { 085 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 086 return processor.readMetadata(blob, ignorePrefix); 087 } catch (NoSuchMethodException e) { 088 throw new BinaryMetadataException(e); 089 } 090 } 091 092 @Override 093 public Map<String, Object> readMetadata(String processorName, Blob blob, boolean ignorePrefix) { 094 try { 095 BinaryMetadataProcessor processor = getProcessor(processorName); 096 return processor.readMetadata(blob, ignorePrefix); 097 } catch (NoSuchMethodException e) { 098 throw new BinaryMetadataException(e); 099 } 100 } 101 102 @Override 103 public Blob writeMetadata(String processorName, Blob blob, Map<String, Object> metadata, boolean ignorePrefix) { 104 try { 105 BinaryMetadataProcessor processor = getProcessor(processorName); 106 return processor.writeMetadata(blob, metadata, ignorePrefix); 107 } catch (NoSuchMethodException e) { 108 throw new BinaryMetadataException(e); 109 } 110 } 111 112 @Override 113 public Blob writeMetadata(Blob blob, Map<String, Object> metadata, boolean ignorePrefix) { 114 try { 115 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 116 return processor.writeMetadata(blob, metadata, ignorePrefix); 117 } catch (NoSuchMethodException e) { 118 throw new BinaryMetadataException(e); 119 } 120 } 121 122 @Override 123 public Blob writeMetadata(String processorName, Blob blob, String mappingDescriptorId, DocumentModel doc) { 124 try { 125 // Creating mapping properties Map. 126 Map<String, Object> metadataMapping = new HashMap<>(); 127 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 128 mappingDescriptorId); 129 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 130 metadataMapping.put(metadataDescriptor.getName(), doc.getPropertyValue(metadataDescriptor.getXpath())); 131 } 132 BinaryMetadataProcessor processor = getProcessor(processorName); 133 return processor.writeMetadata(blob, metadataMapping, mappingDescriptor.getIgnorePrefix()); 134 } catch (NoSuchMethodException e) { 135 throw new BinaryMetadataException(e); 136 } 137 138 } 139 140 @Override 141 public Blob writeMetadata(Blob blob, String mappingDescriptorId, DocumentModel doc) { 142 return writeMetadata(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID, blob, mappingDescriptorId, doc); 143 } 144 145 @Override 146 public void writeMetadata(DocumentModel doc, CoreSession session) { 147 // Check if rules applying for this document. 148 ActionContext actionContext = createActionContext(doc); 149 Set<MetadataRuleDescriptor> ruleDescriptors = checkFilter(actionContext); 150 List<String> mappingDescriptorIds = new ArrayList<>(); 151 for (MetadataRuleDescriptor ruleDescriptor : ruleDescriptors) { 152 mappingDescriptorIds.addAll(ruleDescriptor.getMetadataMappingIdDescriptors()); 153 } 154 if (mappingDescriptorIds.isEmpty()) { 155 return; 156 } 157 158 // For each mapping descriptors, overriding mapping document properties. 159 for (String mappingDescriptorId : mappingDescriptorIds) { 160 if (!BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().containsKey(mappingDescriptorId)) { 161 log.warn("Missing binary metadata descriptor with id '" + mappingDescriptorId 162 + "'. Or check your rule contribution with proper metadataMapping-id."); 163 continue; 164 } 165 writeMetadata(doc, session, mappingDescriptorId); 166 } 167 } 168 169 @Override 170 public void writeMetadata(DocumentModel doc, CoreSession session, String mappingDescriptorId) { 171 // Creating mapping properties Map. 172 Map<String, String> metadataMapping = new HashMap<>(); 173 List<String> blobMetadata = new ArrayList<>(); 174 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 175 mappingDescriptorId); 176 boolean ignorePrefix = mappingDescriptor.getIgnorePrefix(); 177 // Extract blob from the contributed xpath 178 Blob blob = doc.getProperty(mappingDescriptor.getBlobXPath()).getValue(Blob.class); 179 if (blob != null && mappingDescriptor.getMetadataDescriptors() != null 180 && !mappingDescriptor.getMetadataDescriptors().isEmpty()) { 181 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 182 metadataMapping.put(metadataDescriptor.getName(), metadataDescriptor.getXpath()); 183 blobMetadata.add(metadataDescriptor.getName()); 184 } 185 186 // Extract metadata from binary. 187 String processorId = mappingDescriptor.getProcessor(); 188 Map<String, Object> blobMetadataOutput; 189 if (processorId != null) { 190 blobMetadataOutput = readMetadata(processorId, blob, blobMetadata, ignorePrefix); 191 192 } else { 193 blobMetadataOutput = readMetadata(blob, blobMetadata, ignorePrefix); 194 } 195 196 // Write doc properties from outputs. 197 for (String metadata : blobMetadataOutput.keySet()) { 198 Object metadataValue = blobMetadataOutput.get(metadata); 199 String property = metadataMapping.get(metadata); 200 if (!(metadataValue instanceof Date) && !(metadataValue instanceof Collection) 201 && !(metadataValue.getClass().isArray())) { 202 metadataValue = metadataValue.toString(); 203 } 204 try { 205 doc.setPropertyValue(property, (Serializable) metadataValue); 206 } catch (PropertyException e) { 207 log.warn(String.format( 208 "Failed to set property '%s' to value %s from metadata '%s' in '%s' in document '%s' ('%s')", 209 property, metadataValue, metadata, mappingDescriptor.getBlobXPath(), doc.getId(), 210 doc.getPath())); 211 } 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(mappingDescriptor.getProcessor(), 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(mappingDescriptor.getProcessor(), 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(); 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}