001/* 002 * (C) Copyright 2014 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 */ 019package org.nuxeo.binary.metadata.internals; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Date; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.commons.lang.ObjectUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.jboss.el.ExpressionFactoryImpl; 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.model.Property; 043import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 044import org.nuxeo.ecm.platform.actions.ActionContext; 045import org.nuxeo.ecm.platform.actions.ELActionContext; 046import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 047import org.nuxeo.ecm.platform.el.ExpressionContext; 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, String> 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, String> 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, String> metadataMapping = new HashMap<>(); 127 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 128 mappingDescriptorId); 129 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 130 Serializable value = doc.getPropertyValue(metadataDescriptor.getXpath()); 131 metadataMapping.put(metadataDescriptor.getName(), ObjectUtils.toString(value)); 132 } 133 BinaryMetadataProcessor processor = getProcessor(processorName); 134 return processor.writeMetadata(blob, metadataMapping, mappingDescriptor.getIgnorePrefix()); 135 } catch (NoSuchMethodException e) { 136 throw new BinaryMetadataException(e); 137 } 138 139 } 140 141 @Override 142 public Blob writeMetadata(Blob blob, String mappingDescriptorId, DocumentModel doc) { 143 try { 144 // Creating mapping properties Map. 145 Map<String, String> metadataMapping = new HashMap<>(); 146 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 147 mappingDescriptorId); 148 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 149 Serializable value = doc.getPropertyValue(metadataDescriptor.getXpath()); 150 metadataMapping.put(metadataDescriptor.getName(), ObjectUtils.toString(value)); 151 } 152 BinaryMetadataProcessor processor = getProcessor(BinaryMetadataConstants.EXIF_TOOL_CONTRIBUTION_ID); 153 return processor.writeMetadata(blob, metadataMapping, mappingDescriptor.getIgnorePrefix()); 154 } catch (NoSuchMethodException e) { 155 throw new BinaryMetadataException(e); 156 } 157 } 158 159 @Override 160 public void writeMetadata(DocumentModel doc, CoreSession session) { 161 // Check if rules applying for this document. 162 ActionContext actionContext = createActionContext(doc); 163 Set<MetadataRuleDescriptor> ruleDescriptors = checkFilter(actionContext); 164 List<String> mappingDescriptorIds = new ArrayList<>(); 165 for (MetadataRuleDescriptor ruleDescriptor : ruleDescriptors) { 166 mappingDescriptorIds.addAll(ruleDescriptor.getMetadataMappingIdDescriptors()); 167 } 168 if (mappingDescriptorIds.isEmpty()) { 169 return; 170 } 171 172 // For each mapping descriptors, overriding mapping document properties. 173 for (String mappingDescriptorId : mappingDescriptorIds) { 174 if (!BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().containsKey(mappingDescriptorId)) { 175 log.warn("Missing binary metadata descriptor with id '" + mappingDescriptorId 176 + "'. Or check your rule contribution with proper metadataMapping-id."); 177 continue; 178 } 179 writeMetadata(doc, session, mappingDescriptorId); 180 } 181 } 182 183 @Override 184 public void writeMetadata(DocumentModel doc, CoreSession session, String mappingDescriptorId) { 185 // Creating mapping properties Map. 186 Map<String, String> metadataMapping = new HashMap<>(); 187 List<String> blobMetadata = new ArrayList<>(); 188 MetadataMappingDescriptor mappingDescriptor = BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 189 mappingDescriptorId); 190 boolean ignorePrefix = mappingDescriptor.getIgnorePrefix(); 191 // Extract blob from the contributed xpath 192 Blob blob = doc.getProperty(mappingDescriptor.getBlobXPath()).getValue(Blob.class); 193 if (blob != null && mappingDescriptor.getMetadataDescriptors() != null 194 && !mappingDescriptor.getMetadataDescriptors().isEmpty()) { 195 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 196 metadataMapping.put(metadataDescriptor.getName(), metadataDescriptor.getXpath()); 197 blobMetadata.add(metadataDescriptor.getName()); 198 } 199 200 // Extract metadata from binary. 201 String processorId = mappingDescriptor.getProcessor(); 202 Map<String, Object> blobMetadataOutput; 203 if (processorId != null) { 204 blobMetadataOutput = readMetadata(processorId, blob, blobMetadata, ignorePrefix); 205 206 } else { 207 blobMetadataOutput = readMetadata(blob, blobMetadata, ignorePrefix); 208 } 209 210 // Write doc properties from outputs. 211 for (String metadata : blobMetadataOutput.keySet()) { 212 Object metadataValue = blobMetadataOutput.get(metadata); 213 if (!(metadataValue instanceof Date)) { 214 metadataValue = metadataValue.toString(); 215 } 216 doc.setPropertyValue(metadataMapping.get(metadata), (Serializable) metadataValue); 217 } 218 219 // document should exist if id != null 220 if (doc.getId() != null && session.exists(doc.getRef())) { 221 session.saveDocument(doc); 222 } 223 } 224 } 225 226 /*--------------------- Event Service --------------------------*/ 227 228 @Override 229 public void handleSyncUpdate(DocumentModel doc, DocumentEventContext docCtx) { 230 LinkedList<MetadataMappingDescriptor> syncMappingDescriptors = getSyncMapping(doc, docCtx); 231 if (syncMappingDescriptors != null) { 232 handleUpdate(syncMappingDescriptors, doc, docCtx); 233 } 234 } 235 236 @Override 237 public void handleUpdate(List<MetadataMappingDescriptor> mappingDescriptors, DocumentModel doc, 238 DocumentEventContext docCtx) { 239 for (MetadataMappingDescriptor mappingDescriptor : mappingDescriptors) { 240 Property fileProp = doc.getProperty(mappingDescriptor.getBlobXPath()); 241 boolean isDirtyMapping = isDirtyMapping(mappingDescriptor, doc); 242 Blob blob = fileProp.getValue(Blob.class); 243 if (blob != null) { 244 if (fileProp.isDirty()) { 245 if (isDirtyMapping) { 246 // if Blob dirty and document metadata dirty, write metadata from doc to Blob 247 Blob newBlob = writeMetadata(fileProp.getValue(Blob.class), mappingDescriptor.getId(), doc); 248 fileProp.setValue(newBlob); 249 } else { 250 // if Blob dirty and document metadata not dirty, write metadata from Blob to doc 251 writeMetadata(doc, docCtx.getCoreSession()); 252 } 253 } else { 254 if (isDirtyMapping) { 255 // if Blob not dirty and document metadata dirty, write metadata from doc to Blob 256 Blob newBlob = writeMetadata(fileProp.getValue(Blob.class), mappingDescriptor.getId(), doc); 257 fileProp.setValue(newBlob); 258 } 259 } 260 } 261 } 262 } 263 264 /*--------------------- Utils --------------------------*/ 265 266 /** 267 * Check for each Binary Rule if the document is accepted or not. 268 * 269 * @return the list of metadata which should be processed sorted by rules order. (high to low priority) 270 */ 271 protected Set<MetadataRuleDescriptor> checkFilter(final ActionContext actionContext) { 272 final ActionManager actionService = Framework.getLocalService(ActionManager.class); 273 Set<MetadataRuleDescriptor> filtered = Sets.filter(BinaryMetadataComponent.self.ruleRegistry.contribs, 274 new Predicate<MetadataRuleDescriptor>() { 275 276 @Override 277 public boolean apply(MetadataRuleDescriptor input) { 278 if (!input.getEnabled()) { 279 return false; 280 } 281 for (String filterId : input.getFilterIds()) { 282 if (!actionService.checkFilter(filterId, actionContext)) { 283 return false; 284 } 285 } 286 return true; 287 } 288 289 }); 290 return filtered; 291 } 292 293 protected ActionContext createActionContext(DocumentModel doc) { 294 ActionContext actionContext = new ELActionContext(new ExpressionContext(), new ExpressionFactoryImpl()); 295 actionContext.setCurrentDocument(doc); 296 return actionContext; 297 } 298 299 protected BinaryMetadataProcessor getProcessor(String processorId) throws NoSuchMethodException { 300 return BinaryMetadataComponent.self.processorRegistry.getProcessor(processorId); 301 } 302 303 /** 304 * @return Dirty metadata from metadata mapping contribution and handle async processes. 305 */ 306 public LinkedList<MetadataMappingDescriptor> getSyncMapping(DocumentModel doc, DocumentEventContext docCtx) { 307 // Check if rules applying for this document. 308 ActionContext actionContext = createActionContext(doc); 309 Set<MetadataRuleDescriptor> ruleDescriptors = checkFilter(actionContext); 310 Set<String> syncMappingDescriptorIds = new HashSet<>(); 311 HashSet<String> asyncMappingDescriptorIds = new HashSet<>(); 312 for (MetadataRuleDescriptor ruleDescriptor : ruleDescriptors) { 313 if (ruleDescriptor.getIsAsync()) { 314 asyncMappingDescriptorIds.addAll(ruleDescriptor.getMetadataMappingIdDescriptors()); 315 continue; 316 } 317 syncMappingDescriptorIds.addAll(ruleDescriptor.getMetadataMappingIdDescriptors()); 318 } 319 320 // Handle async rules which should be taken into account in async listener. 321 if (!asyncMappingDescriptorIds.isEmpty()) { 322 docCtx.setProperty(BinaryMetadataConstants.ASYNC_MAPPING_RESULT, getMapping(asyncMappingDescriptorIds)); 323 docCtx.setProperty(BinaryMetadataConstants.ASYNC_BINARY_METADATA_EXECUTE, Boolean.TRUE); 324 } 325 326 if (syncMappingDescriptorIds.isEmpty()) { 327 return null; 328 } 329 return getMapping(syncMappingDescriptorIds); 330 } 331 332 protected LinkedList<MetadataMappingDescriptor> getMapping(Set<String> mappingDescriptorIds) { 333 // For each mapping descriptors, store mapping. 334 LinkedList<MetadataMappingDescriptor> mappingResult = new LinkedList<>(); 335 for (String mappingDescriptorId : mappingDescriptorIds) { 336 if (!BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().containsKey(mappingDescriptorId)) { 337 log.warn("Missing binary metadata descriptor with id '" + mappingDescriptorId 338 + "'. Or check your rule contribution with proper metadataMapping-id."); 339 continue; 340 } 341 mappingResult.add(BinaryMetadataComponent.self.mappingRegistry.getMappingDescriptorMap().get( 342 mappingDescriptorId)); 343 } 344 return mappingResult; 345 } 346 347 /** 348 * Maps inspector only. 349 */ 350 protected boolean isDirtyMapping(MetadataMappingDescriptor mappingDescriptor, DocumentModel doc) { 351 Map<String, String> mappingResult = new HashMap<>(); 352 for (MetadataMappingDescriptor.MetadataDescriptor metadataDescriptor : mappingDescriptor.getMetadataDescriptors()) { 353 mappingResult.put(metadataDescriptor.getXpath(), metadataDescriptor.getName()); 354 } 355 // Returning only dirty properties 356 HashMap<String, Object> resultDirtyMapping = new HashMap<>(); 357 for (String metadata : mappingResult.keySet()) { 358 Property property = doc.getProperty(metadata); 359 if (property.isDirty()) { 360 resultDirtyMapping.put(mappingResult.get(metadata), doc.getPropertyValue(metadata)); 361 } 362 } 363 return !resultDirtyMapping.isEmpty(); 364 } 365}