
com.day.cq.dam.commons.metadata.SimpleXmpToJcrMetadataBuilder Maven / Gradle / Ivy
/*
* Copyright 1997-2008 Day Management AG
* Barfuesserplatz 6, 4001 Basel, Switzerland
* All Rights Reserved.
*
* This software is the confidential and proprietary information of
* Day Management AG, ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Day.
*/
package com.day.cq.dam.commons.metadata;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.Binary;
import javax.jcr.Item;
import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import com.adobe.granite.asset.api.AssetMetadata;
import com.adobe.xmp.XMPConst;
import com.adobe.xmp.XMPDateTime;
import com.adobe.xmp.XMPDateTimeFactory;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPIterator;
import com.adobe.xmp.XMPMeta;
import com.adobe.xmp.XMPMetaFactory;
import com.adobe.xmp.XMPPathFactory;
import com.adobe.xmp.core.XMPArray;
import com.adobe.xmp.core.XMPMetadata;
import com.adobe.xmp.core.parser.RDFXMLParser;
import com.adobe.xmp.core.parser.RDFXMLParserContext;
import com.adobe.xmp.options.PropertyOptions;
import com.adobe.xmp.path.XMPPath;
import com.adobe.xmp.properties.XMPProperty;
import com.adobe.xmp.properties.XMPPropertyInfo;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.DamConstants;
import com.day.cq.dam.api.metadata.ExtractedMetadata;
import com.day.cq.dam.api.metadata.xmp.XmpMappings;
import com.day.cq.dam.commons.util.DateParser;
import com.day.cq.tagging.JcrTagManagerFactory;
import com.day.cq.tagging.Tag;
import com.day.cq.tagging.TagManager;
import org.apache.commons.imaging.common.RationalNumber;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.jcr.api.SlingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import static com.day.cq.dam.api.DamConstants.DC_DESCRIPTION;
import static com.day.cq.dam.api.DamConstants.DC_FORMAT;
/**
* The XmpToJcrMetadataBuilder
class ...
*/
@Component(metatype = false, label = "SimpleXmpToJcrMetadataBuilder Handler", description = "SimpleXmpToJcrMetadataBuilder Handler")
@Service(value = SimpleXmpToJcrMetadataBuilder.class)
public class SimpleXmpToJcrMetadataBuilder {
private static final String METADATA_PROPERTY_NAME_ADOBE_KEYWORDS = "lr:hierarchicalSubject";
private static final String XMP_ARRAY_TYPE = "xmpArrayType";
private static final String IS_XMP_ARRAY = "isXMPArray";
/**
* the default logger
*/
private static final Logger log = LoggerFactory.getLogger(SimpleXmpToJcrMetadataBuilder.class);
public static final String NT_RDF_BAG = "rdf:Bag";
public static final String NT_RDF_SEQ = "rdf:Seq";
public static final String NT_RDF_ALT = "rdf:Alt";
private static final String INGREDIENT_TAG = "xmpMM:Ingredients";
private static final String SYNC_FLAG = "newRendition";
private static final String DAM_NS_URI = "http://www.day.com/dam/1.0";
private static final String CQ_NS_URI = "http://www.day.com/jcr/cq/1.0";
// sub-service for adding new namespace mappings from XMP to JCR
private static final String NAMESPACE_UPDATE_HELPER = "namespaceupdatehelper";
private String defaultFormats[] = new String[] { "application/octet-stream" };
//initalize the list of properties that can have alternative texts
private static final List altArrayProps = new ArrayList(8);
//initialize the list
static {
altArrayProps.add(DamConstants.DC_DESCRIPTION);
altArrayProps.add(DamConstants.DC_TITLE);
altArrayProps.add(DamConstants.DC_RIGHTS);
altArrayProps.add("xmpRights:UsageTerms");
altArrayProps.add("exif:UserComment");
altArrayProps.add("tiff:Copyright");
altArrayProps.add("tiff:ImageDescription");
}
private static final List ignoreHierarchy = new ArrayList();
static {
ignoreHierarchy.add(DamConstants.DC_DESCRIPTION);
ignoreHierarchy.add(DamConstants.DC_TITLE);
ignoreHierarchy.add(DamConstants.DC_RIGHTS);
ignoreHierarchy.add("xmpRights:UsageTerms");
ignoreHierarchy.add("exif:UserComment");
ignoreHierarchy.add("tiff:Copyright");
ignoreHierarchy.add("tiff:ImageDescription");
ignoreHierarchy.add("cq:tags");
ignoreHierarchy.add("dc:creator");
ignoreHierarchy.add("creator");
ignoreHierarchy.add("dc:contributor");
ignoreHierarchy.add("dc:language");
ignoreHierarchy.add("dc:subject");
ignoreHierarchy.add("photoshop:SupplementalCategories");
}
public SimpleXmpToJcrMetadataBuilder() {
// priortise "Image Length" over "ImageLength".
// priortise "Image Width" over "ImageWidth".
conflictPropMap.put("ImageLength", "Image Length");
conflictPropMap.put("ImageWidth", "Image Width");
conflictPropMap.put(DamConstants.TIFF_IMAGEWIDTH, "Image Width");
conflictPropMap.put(DamConstants.TIFF_IMAGELENGTH, "Image Length");
conflictPropMap.put(DC_DESCRIPTION, "description");
conflictPropMap.put(DC_DESCRIPTION, "Caption/Abstract");
}
//prioritize properties
public final MapconflictPropMap = new HashMap(5);
@Reference
private XmpFilter xmpFilter;
@Reference
private JcrTagManagerFactory tagManagerFactory;
@Reference
private SlingRepository repository;
/*
* FIXME: which method should be used instead?
*/
@Deprecated
public void storeXmp(Node metadataRoot, XMPMeta meta, boolean doSave)
throws XMPException, RepositoryException {
XMPIterator itr = meta.iterator();
String rootPath = metadataRoot.getPath();
Map> arrayMap = new HashMap>();
while (itr.hasNext()) {
XMPPropertyInfo prop = (XMPPropertyInfo) itr.next();
if (!prop.getOptions().isSchemaNode()) {
if (prop.getOptions().isQualifier()
|| prop.getOptions().isSimple()) {
// check namespace TODO: correct here?
checkNamespace(prop, metadataRoot);
String path = (Text.getRelativeParent(prop.getPath(), 1).equals(""))
? rootPath
: rootPath + "/"
+ Text.getRelativeParent(prop.getPath(), 1);
if (isArrayMember(arrayMap, prop.getPath())) {
if (prop.getOptions().isQualifier()) {
log.debug("Qualifier detected (noop): "
+ prop.toString());
} else if (prop.getOriValue() != null) {
String p = prop.getPath();
p = p.substring(0, p.lastIndexOf("["));
arrayMap.get(p).add(prop);
}
} else {
if (prop.getOptions().isQualifier()) {
log.debug("Qualifier detected (noop): "
+ prop.toString());
} else if (prop.getOriValue() != null) {
Node node = getOrCreateNode(
metadataRoot.getSession(), path,
JcrConstants.NT_UNSTRUCTURED);
if (node != null) {
setProperty(node, prop);
}
log.debug("PATH: " + rootPath + "/" + prop.getPath()
+ ":" + prop.getValue());
} else {
log.debug(prop.getPath() + " is NULL");
}
}
} else if (prop.getOptions().isArray()) {
checkNamespace(prop, metadataRoot);
ArrayList members = new ArrayList();
members.add(prop); //add array node as first element
arrayMap.put(prop.getPath(),
members);
} else if (prop.getOptions().isStruct()) {
// struct can be member of array
if (isArrayMember(arrayMap, prop.getPath())) {
log.debug("Struct as member of array");
if (prop.getOptions().isQualifier()) {
log.debug("Qualifier detected (noop): "
+ prop.toString());
} else if (prop.getOriValue() != null) {
String p = prop.getPath();
int arrayIndexStrt = p.lastIndexOf("[");
if (arrayIndexStrt > -1) {
p = p.substring(0, arrayIndexStrt);
}
arrayMap.get(p).add(prop);
}
} else {
checkNamespace(prop, metadataRoot);
String path = rootPath + "/" + prop.getPath();
getOrCreateNode(metadataRoot.getSession(), path,
JcrConstants.NT_UNSTRUCTURED);
}
}
}
}
// add arrays
for (String path : arrayMap.keySet()) {
String parentPath = Text.getRelativeParent(path, 1);
List arrayMembers = arrayMap.get(path);
if (arrayMembers.size() > 1) { //first element is array itself
//normalize the parent path for array indexes in case of array of struct
//create the parent node
Node node = getOrCreateNode(metadataRoot.getSession(),
rootPath + "/" + normalizeArrayPath(parentPath), JcrConstants.NT_UNSTRUCTURED);
if (node != null) {
//array first member is the array prop itself, so remove it
// this was added to propagate the type of array
XMPPropertyInfo arrayProp = arrayMembers.remove(0);
//check the type
String name = NT_RDF_BAG;
if (arrayProp.getOptions().isArrayOrdered()) {
name = NT_RDF_SEQ;
} else if (arrayProp.getOptions().isArrayAlternate()) {
name = NT_RDF_ALT;
}
XMPPropertyInfo firstMember = arrayMembers.get(0);
//if first member is struct then it is an array of struct
if (firstMember.getOptions().isStruct() && !INGREDIENT_TAG.equalsIgnoreCase(Text.getName(path))) {
//create the array node
/*
* FIXME: the method getOrCreateNode returns NULL on errors in the underlying persistence
* and using the return value unchecked will generate NullPointerExceptions
*/
Node arrayNode = getOrCreateNode(metadataRoot.getSession(),
node.getPath() + "/" + Text.getName(path), JcrConstants.NT_UNSTRUCTURED);
arrayNode.setProperty(XMP_ARRAY_TYPE, name); //set the type of array
arrayNode.setProperty(IS_XMP_ARRAY, true);
//clean member nodes
NodeIterator nodes = arrayNode.getNodes();
while (nodes.hasNext()) {
Node childNode = (Node) nodes.next();
childNode.remove();
}
//handle simple properties/as well as struct (may be array)
for (XMPPropertyInfo prop:arrayMembers) {
if (prop.getOptions().isStruct()) {
//create the struct node with normalized path
getOrCreateNode(metadataRoot.getSession(),
rootPath + "/" + normalizeArrayPath(prop.getPath()), JcrConstants.NT_UNSTRUCTURED);
} else if (prop.getOptions().isSimple()) {
String parentStructPath = Text.getRelativeParent(prop.getPath(), 1);
Node parentStructNode = getOrCreateNode(metadataRoot.getSession(),
rootPath + "/" + normalizeArrayPath(parentStructPath), JcrConstants.NT_UNSTRUCTURED);
//set simple property
setProperty(parentStructNode, prop);
}
}
} else {
setMvProperty(node, arrayMembers, Text.getName(path));
}
}
}
}
if (doSave) {
metadataRoot.getSession().save();
}
}
/**
* Normalizes the XMP property path i.e. xmpTPg:SwatchGroups/xmpTPg:SwatchGroups[1]
* will be converted to xmpTPg:SwatchGroups/1
* xmpTPg:SwatchGroups/xmpTPg:SwatchGroups[1]/xmpG:Colorants/xmpG:Colorants[1]
* will be converted to
* xmpTPg:SwatchGroups/1/xmpG:Colorants/1
* @param xmpPath xpath to property
* @return normalized path
*/
private String normalizeArrayPath(String xmpPath) {
xmpPath = xmpPath.replace("[", "/");
return xmpPath.replace("]", "");
}
public void storeXmp(Node metadataRoot, XMPMeta meta) throws XMPException,
RepositoryException {
storeXmp(metadataRoot, meta, true);
}
private boolean isArrayMember(Map> arrayMap,
String path) {
if (path.lastIndexOf("[") > 0) {
String parentPath = path.substring(0, path.lastIndexOf("["));
return arrayMap.containsKey(parentPath);
}
return false;
}
@Deprecated
public XMPMeta getXmpFromJcr(Node metadataRoot) throws RepositoryException,
XMPException {
XMPMeta meta = XMPMetaFactory.create();
// simple props and arrays
PropertyIterator props = metadataRoot.getProperties();
while (props.hasNext()) {
try {
Property prop = props.nextProperty();
String name = prop.getName();
// try to get namespace...
if (name.indexOf(":") < 0) {
log.debug(
"property [{}] doesn't have namespace prefix, skipping. metadata node: [{}].",
name, metadataRoot.getPath());
continue;
}
String splits[] = name.split(":");
String nsPrefix = splits[0];
String namespace = metadataRoot.getSession().getWorkspace().getNamespaceRegistry().getURI(
nsPrefix);
String regPrefix = registerNs(nsPrefix, namespace);
// Bug 42205 : if namespace is registered with some other prefix
// then replace it.
if (!regPrefix.equals(nsPrefix) && splits.length > 1) {
nsPrefix = regPrefix;
name = nsPrefix + ":" + splits[1];
}
// simple props
if (name.indexOf("jcr:") < 0 && !prop.isMultiple()) {
Object val = getValue(prop);
try {
registerNs(nsPrefix, namespace);
if (altArrayProps.contains(name) && val instanceof String ) {
meta.setLocalizedText(namespace, name, XMPConst.X_DEFAULT, XMPConst.X_DEFAULT, (String) val);
} else {
meta.setProperty(namespace, name, val);
}
} catch (XMPException xmpe) {
if(log.isDebugEnabled())
log.debug("Cannot set xmp property: "
+ xmpe.getMessage(), xmpe);
else
log.warn("Cannot set xmp property: "
+ xmpe.getMessage());
}
} else if (name.indexOf("jcr:") < 0 && prop.isMultiple()) {
Object vals[] = getMultiValues(prop);
try {
registerNs(nsPrefix, namespace);
//if there is only single value, then use x-default to write back
//we can not handle lang alternatives yet but still trying to make simpler cases work
if (altArrayProps.contains(name) && vals.length == 1) {
if (vals[0] instanceof String) {
meta.setLocalizedText(namespace, name, XMPConst.X_DEFAULT, XMPConst.X_DEFAULT, (String) vals[0]);
}
} else {
for (Object v : vals) {
if (v instanceof String) {
meta.appendArrayItem(namespace, name,
new PropertyOptions().setArray(true),
(String) v, null);
}
}
}
} catch (XMPException xmpe) {
if(log.isDebugEnabled())
log.debug("Cannot set xmp property: "
+ xmpe.getMessage(), xmpe);
else
log.warn("Cannot set xmp property: "
+ xmpe.getMessage());
}
}
} catch (RepositoryException re) {
log.error("Cannot set xmp property: "
+ re.getMessage(), re);
}
}
try {
// Structs
checkForComplexMetadata(metadataRoot, meta, null, null, new HashMap());
} catch (PathNotFoundException e) {
log.info("Complex Metadata extraction is not applicable for the binary");
} catch (Exception e) { // error in complex metadata extraction should
// not block normal metadata processing
log.error("Unable to extract the complex metadata "
+ e.getMessage() , e);
}
return meta;
}
/**
* To process the complex metadata. It parses properties & structure of
* child nodes of 'jcr:content/metadata' as well as their subsequent
* hierarchy. Name of all the direct child as well as subsequent ones should
* be namespace qualified.
* @param metadataRoot root node (always points to jcr:content/metadata in current impl)
* @param xmpMeta XMPMeta Object
* @param nodeName
* @param nsRootURI
* @throws XMPException
* @throws RepositoryException
*/
private void checkForComplexMetadata(Node metadataRoot, XMPMeta xmpMeta,
String nodeName, String nsRootURI, Map arrOfStructMap)
throws XMPException, RepositoryException {
String pNodeName = nodeName;
// child nodes iteration
for (NodeIterator iter = metadataRoot.getNodes(); iter.hasNext();) {
String childRootURI = nsRootURI;
final Node childMetadataNode = iter.nextNode();
final PropertyIterator dpi = childMetadataNode.getProperties();
final PropertyOptions dpropertyOption = new PropertyOptions();
dpropertyOption.setArray(true);
// For subsequent child nodes
nodeName = (pNodeName == null)
? childMetadataNode.getName(): pNodeName + "/" + childMetadataNode.getName();
// nsSchema remain same for all the subsequent child nodes
if (childRootURI == null ) {
// try to get namespace...
if (childMetadataNode.getName().indexOf(":") < 0) {
log.warn(
"property [{}] doesn't have namespace prefix, skipping. metadata node: [{}].",
childMetadataNode.getName(),
childMetadataNode.getPath());
return;
}
childRootURI = registerPrefix(childMetadataNode);
}
//check the if node represents an array of struct
if (childMetadataNode.hasProperty(IS_XMP_ARRAY) || (childMetadataNode.hasProperty("xmpNodeType") && "xmpArray".equals(childMetadataNode.getProperty("xmpNodeType").getString()) )) {
//it is an array of struct
//create array item
PropertyOptions arrayOptions = new PropertyOptions();
if (childMetadataNode.hasProperty(XMP_ARRAY_TYPE)) {
String arrayType = childMetadataNode.getProperty(XMP_ARRAY_TYPE).getString();
if (NT_RDF_BAG.equals(arrayType)) {
arrayOptions.setArray(true);
} else if (NT_RDF_SEQ.equals(arrayType)) {
arrayOptions.setArrayOrdered(true);
} else if (NT_RDF_ALT.equals(arrayType)) {
arrayOptions.setArrayAlternate(true);
}
} else {
arrayOptions.setArray(true);
}
arrOfStructMap.put(childMetadataNode.getPath(), arrayOptions);
} else {
//if current node's parent (metadataRoot) is part of array of struct
if (arrOfStructMap.containsKey(metadataRoot.getPath())) {
//add array of struct
xmpMeta.appendArrayItem(childRootURI, pNodeName,
arrOfStructMap.get(metadataRoot.getPath()), null,
new PropertyOptions().setStruct(true));
int index = xmpMeta.countArrayItems(childRootURI, pNodeName);
//call handle complex metadata
nodeName = pNodeName + "[" + index + "]";
}
// ToDo: Add empty node structure to support nodes which doesn't
// have properties.
// Iterating & writing back all the properties
while (dpi.hasNext()) {
Property p = dpi.nextProperty();
// try to get namespace...
if (p.getName().indexOf(":") < 0) {
log.debug(
"property [{}] doesn't have namespace prefix, skipping. metadata node: [{}].",
p.getName(), childMetadataNode.getPath());
continue;
}
//register property prefix
registerPrefix(p);
if (childRootURI != null && p.getName() != null) {
if ((p.getName().indexOf("jcr:") < 0 && p.isMultiple())) {
log.debug(
"Multiple value metadata property {}, creating String[] in XMP",
p.getName());
try {
Value values[] = p.getValues();
for (Value value : values) {
xmpMeta.appendArrayItem(childRootURI, nodeName
+ "/" + p.getName(), dpropertyOption,
value.getString(), null);
}
} catch (XMPException xmpe) {
if(log.isDebugEnabled())
log.debug("Cannot set xmp property: "
+ xmpe.getMessage(), xmpe);
else
log.warn("Cannot set xmp property: "
+ xmpe.getMessage());
}
} else if ((p.getName().indexOf("jcr:") < 0 && !p.isMultiple())) {
log.debug("Writing {} with value: {}", p.getName(),
p.getString());
try {
// ToDO: Used setProperty in place of setStructField
// due to bug in parsing xpath expression
xmpMeta.setProperty(childRootURI,
nodeName + "/" + p.getName(), p.getString());
// xmpMeta.setStructField(nsUriChild,derivedMetadataNode.getName(),nsUri,p.getName(),p.getString());
} catch (XMPException xmpe) {
if(log.isDebugEnabled())
log.debug("Cannot set xmp property: "
+ xmpe.getMessage(), xmpe);
else
log.warn("Cannot set xmp property: "
+ xmpe.getMessage());
}
}
}
}
}
// Recursively child nodes extraction
if (childMetadataNode.getNodes().getSize() > 0) {
checkForComplexMetadata(childMetadataNode, xmpMeta, nodeName,
childRootURI, arrOfStructMap);
}
}
}
private String registerPrefix(final Item jcrItem)
throws RepositoryException {
final String childPrefix = jcrItem.getName().substring(
0, jcrItem.getName().indexOf(":"));
final String nsUriChild = jcrItem.getSession().getWorkspace().getNamespaceRegistry().getURI(
childPrefix);
try {
registerNs(childPrefix, nsUriChild);
} catch (XMPException xmpe) {
log.warn("Cannot process the xmp structure: "
+ xmpe.getMessage());
}
return nsUriChild;
}
/**
* @param metadata - map of properties extracted by asset handler
* @param metadataRoot - metadata node of asset
* @param doSave - whether the repository changes are saved or not
* @throws XMPException
* @throws RepositoryException
* @deprecated use {@link SimpleXmpToJcrMetadataBuilder#storeAsXmp(ExtractedMetadata, Asset, boolean)} instead
*/
@Deprecated
public void storeAsXmp(ExtractedMetadata metadata, Node metadataRoot, boolean doSave) throws XMPException, RepositoryException {
XMPMeta meta = XMPMetaFactory.create();
convertToXmp( metadata, metadataRoot, meta, doSave);
storeXmp(metadataRoot, meta, doSave);
}
/**
* @param metadata - map of properties extracted by asset handler
* @param asset - asset to which metadata is to be save
* @param doSave - whether the repository changes are saved or not
* @throws XMPException
* @throws RepositoryException
*/
/* FIXME: this seems never be used outside this class, should be private */
public void storeAsXmp(ExtractedMetadata metadata, Asset asset,
boolean doSave) throws XMPException, RepositoryException {
Node assetNode = asset.adaptTo(Node.class);
Node metadataRoot = assetNode.getNode(JcrConstants.JCR_CONTENT+'/'+DamConstants.METADATA_FOLDER);
InputStream is = metadata.getXmp();
XMPMeta xmpMeta = null;
try {
if (is != null) {
if (xmpFilter != null) {
is = xmpFilter.filter(is);
}
xmpMeta = XMPMetaFactory.parse(is);
resolvePropConflict(metadata, conflictPropMap);
resolvePropConflict(metadata, xmpMeta, conflictPropMap);
convertToXmp(metadata, metadataRoot, xmpMeta, doSave);
if (metadataRoot.hasProperty(DC_FORMAT)
&& !isDefaultFormat(metadataRoot.getProperty(DC_FORMAT).getValue().getString())
&& xmpMeta.getPropertyString(XMPConst.NS_DC, DC_FORMAT) != null) {
xmpMeta.deleteProperty(XMPConst.NS_DC, DC_FORMAT);
}
}
else {
xmpMeta = XMPMetaFactory.create();
convertToXmp( metadata, metadataRoot, xmpMeta, doSave);
}
XMPMetadata xmpMetadata=null;
final byte[] xmpBytes = XMPMetaFactory.serializeToBuffer(xmpMeta, null);
RDFXMLParserContext parserContext = new RDFXMLParserContext();
xmpMetadata = (new RDFXMLParser()).parse(xmpBytes,parserContext);
Set nss = parserContext.getPrefixDefinitions().keySet();
for (String ns : nss){
checkNamespace(parserContext.getPrefixDefinitions().get(ns), metadataRoot);
}
validateTags(xmpMetadata,asset);
AssetMetadata assetMetadata = ((asset.adaptTo(Resource.class)).adaptTo((com.adobe.granite.asset.api.Asset.class))).getAssetMetadata();
assetMetadata.setXMP(xmpMetadata, null, ignoreHierarchy);
extractHierarchicalSubjects(asset,metadataRoot.getSession());
} catch (com.adobe.xmp.core.XMPException e) {
log.info("cannot convert extractedmetadata to XMPMetadata",e);
} catch (IOException e) {
log.info("cannot convert extractedmetadata to XMPMetadata", e);
}
}
/**
* ignore the cq:tags which are not available in the system - Refer:- CQ-106710
* @param xmpMetadata
* @param asset
*/
private void validateTags(XMPMetadata xmpMetadata, Asset asset){
XMPArray tagsArray = xmpMetadata.getArray( CQ_NS_URI,"tags");
if(tagManagerFactory == null){
return;
}
final ResourceResolver resolver = asset.adaptTo(Resource.class).getResourceResolver();
final TagManager tagManager = tagManagerFactory
.getTagManager(resolver);
ArrayList listIndexRemoval = new ArrayList();
if(tagsArray != null ){
for ( int i = 0; i< tagsArray.size(); i++)
{
String tagValue = tagsArray.getSimple(i).getValue();
if(tagValue!= null){
final Tag tag = tagManager.resolve(tagValue);
if (null == tag) {
listIndexRemoval.add(tagsArray.get(i).getXMPPath());
}
}
}
for (XMPPath path : listIndexRemoval) {
try {
xmpMetadata.remove(path);
} catch (com.adobe.xmp.core.XMPException e) {
log.debug("unable to delete the tag from path "+ xmpMetadata.get(path).getName() );
}
}
}
}
private boolean isDefaultFormat(String format) {
for (String defFormat : defaultFormats) {
if (defFormat.equals(format)) {
return true;
}
}
return false;
}
private void convertToXmp(ExtractedMetadata metadata, Node metadataRoot, XMPMeta meta,
boolean doSave) throws XMPException, RepositoryException {
/*
* FIXME: this assigns a java.util.Set to a Set. Either cast explicitly or convert otherwise.
*/
Set keys = metadata.getMetaDataProperties().keySet();
for (String mkey : keys) {
// remove the "(" and ")" char
String key = mkey.replaceAll("\\(", "").replaceAll("\\)", "");
if (XmpMappings.defaultSimpleXmpMappings.containsKey(key)) {
String xmpKeys[] = getXmpKeys(XmpMappings.defaultSimpleXmpMappings.get(key));
for (String xmpKey : xmpKeys) {
try {
setXmpProperty(meta, xmpKey,
metadata.getMetaDataProperties().get(key),metadataRoot.getSession());
} catch (XMPException e) {
log.debug("Cannot create xmp property: "
+ e.getMessage(), e);
}
}
} else if (XmpMappings.defaultBagXmpMappings.containsKey(key)
|| XmpMappings.defaultSeqXmpMappings.containsKey(key)
|| XmpMappings.defaultAltXmpMappings.containsKey(key)) {
String xmpKeys[] = new String[0];
if (XmpMappings.defaultBagXmpMappings.containsKey(key)) {
// CQ-4215741 : in case of pdf asset, dc:subject must store only the pdf keywords metadata and not any other subject key
if(!metadata.getMetaDataProperties().get(DC_FORMAT).equals("application/pdf") || !key.equalsIgnoreCase("subject")) {
xmpKeys = getXmpKeys(XmpMappings.defaultBagXmpMappings.get(key));
}
} else if (XmpMappings.defaultSeqXmpMappings.containsKey(key)) {
xmpKeys = getXmpKeys(XmpMappings.defaultSeqXmpMappings.get(key));
} else if (XmpMappings.defaultAltXmpMappings.containsKey(key)) {
xmpKeys = getXmpKeys(XmpMappings.defaultAltXmpMappings.get(key));
}
for (String xmpKey : xmpKeys) {
try {
String namespace = getNamespace(xmpKey);
Object val = metadata.getMetaDataProperties().get(key);
if (val instanceof List>) {
List> valList = (List>) val;
for (Object value : valList) {
boolean exists = doesArrayItemExistInXMPMeta(meta, namespace, xmpKey, (String) value);
if (!exists) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
(String) value, null);
}
}
}
else if (val instanceof Object[]) {
Object[] valArray = (Object[]) val;
for (Object value : valArray) {
boolean exists = doesArrayItemExistInXMPMeta(meta, namespace, xmpKey, value.toString());
if (!exists) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
value.toString(), null);
}
}
}
else if (val.getClass().isArray() && val.getClass().getComponentType().isPrimitive()){
Class> componentType;
componentType = val.getClass().getComponentType();
if (boolean.class.isAssignableFrom(componentType)) {
for (boolean value : (boolean[]) val) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
value+"", null);
}
}
else if (float.class.isAssignableFrom(componentType)) {
for (float value : (float[]) val) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
value+"", null);
}
}
else if (double.class.isAssignableFrom(componentType)) {
for (double value : (double[]) val) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
value+"", null);
}
}
else if (int.class.isAssignableFrom(componentType)) {
for (int value : (int[]) val) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
value+"", null);
}
}
else if (long.class.isAssignableFrom(componentType)) {
for (long value : (long[]) val) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
value+"", null);
}
}
else if (short.class.isAssignableFrom(componentType)) {
for (short value : (short[]) val) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true),
value+"", null);
}
}
}
else {
String strVal = (val instanceof String)
? (String) val
: String.valueOf(val);
boolean exists = doesArrayItemExistInXMPMeta(meta, namespace, xmpKey, strVal);
if (!exists) {
meta.appendArrayItem(namespace, xmpKey,
new PropertyOptions().setArray(true), strVal,
null);
}
}
} catch (XMPException e) {
log.debug("Cannot create xmp property: "
+ e.getMessage());
}
}
} else {
// fallback
// use dam namespace
if (key.indexOf(":") < 0) {
// no namespace at all
String nsPrefix = registerNs("dam", DAM_NS_URI);
try {
Object value = metadata.getMetaDataProperties().get(key);
String xmpKey = nsPrefix + ":" + key.replace(" ", "");
if (value instanceof List>) {
List> valList = (List> )value;
for (Object val:valList) {
boolean exists = doesArrayItemExistInXMPMeta(meta, DAM_NS_URI, xmpKey, (String) val);
if (!exists) {
meta.appendArrayItem(DAM_NS_URI, xmpKey, new PropertyOptions().setArray(true),
(String)val, null);
}
}
}
else if (value instanceof Object[]) {
Object[] valArray = (Object[]) value;
for (Object val : valArray) {
boolean exists = doesArrayItemExistInXMPMeta(meta, DAM_NS_URI, xmpKey, val.toString());
if (!exists) {
meta.appendArrayItem(DAM_NS_URI, xmpKey, new PropertyOptions().setArray(true),
val.toString(), null);
}
}
}
else if (value!=null && (!(value instanceof String) || !StringUtils.isEmpty((String)value))){
meta.setProperty(DAM_NS_URI, xmpKey.trim(),value);
}
} catch (XMPException e) {
if(log.isDebugEnabled()) log.debug("Cannot set xmp property:" + e.getMessage(), e);
else log.warn("Cannot set xmp property:" + e.getMessage());
}
} else {
try {
setXmpProperty(meta, key,
metadata.getMetaDataProperties().get(key), metadataRoot.getSession());
} catch (XMPException e) {
log.debug("Cannot create xmp property: "
+ e.getMessage());
}
}
}
}
}
public void storeAsXmp(ExtractedMetadata metadata, Node metadataRoot)
throws XMPException, RepositoryException {
storeAsXmp(metadata, metadataRoot, true);
}
private void setXmpProperty(XMPMeta meta, String xmpKey, Object value, Session session)
throws XMPException, RepositoryException {
if (value != null) {
// already prefixed key
// check if namespace is already registered in xmp schema registry
try {
String nsPrefix = xmpKey.substring(0, xmpKey.indexOf(":"));
String nsUri = session.getNamespaceURI(nsPrefix); // TODO:
// better
// taken
// from ws
nsPrefix = registerNs(nsPrefix, nsUri);
if (value instanceof Boolean) {
meta.setPropertyBoolean(nsUri, xmpKey, (Boolean) value);
} else if (value instanceof Calendar) {
meta.setPropertyCalendar(nsUri, xmpKey, (Calendar) value);
} else if (value instanceof Date) {
Calendar cal = Calendar.getInstance();
cal.setTime((Date) value);
meta.setPropertyDate(nsUri, xmpKey,
XMPDateTimeFactory.createFromCalendar(cal));
} else if (value instanceof Double) {
meta.setPropertyDouble(nsUri, xmpKey, (Double) value);
} else if (value instanceof Integer) {
meta.setPropertyInteger(nsUri, xmpKey, (Integer) value);
} else if (value instanceof Long) {
meta.setPropertyLong(nsUri, xmpKey, (Long) value);
} else if (value instanceof String) {
if (!StringUtils.isEmpty((String) value)) {
meta.setProperty(nsUri, xmpKey, value);
}
} else if (value instanceof List>) {
List> valList = (List>) value;
for (Object val : valList) {
boolean exists = doesArrayItemExistInXMPMeta(meta, nsUri, xmpKey, (String) val);
if (!exists) {
meta.appendArrayItem(nsUri, xmpKey,
new PropertyOptions().setArray(true),
(String) val, null);
}
}
}
else if (value instanceof Object[]) {
Object[] valArray = (Object[]) value;
if (altArrayProps.contains(xmpKey) && valArray[0] != null && valArray[0] instanceof String) {
// special case handling for dc:title and dc:description fields
meta.setProperty(nsUri, xmpKey, valArray[0]);
} else {
for (Object val : valArray) {
boolean exists = doesArrayItemExistInXMPMeta(meta, nsUri, xmpKey, (String) val);
if (!exists) {
meta.appendArrayItem(nsUri, xmpKey,
new PropertyOptions().setArray(true),
(String) val, null);
}
}
}
}
else {
meta.setProperty(nsUri, xmpKey, value);
}
} catch (NamespaceException nsEx) {
if(log.isDebugEnabled()) log.debug("namespace exception in setting xmp property", nsEx);
else log.warn("namespace exception in setting xmp property", nsEx.getMessage());
}
catch (ClassCastException ccEx) {
if(log.isDebugEnabled()) log.debug("class cast exception in setting xmp property", ccEx);
else log.warn("class cast exception in setting xmp property", ccEx.getMessage());
}
}
}
private String[] getXmpKeys(String keyString) {
if (keyString.indexOf(",") > 0) {
return keyString.split(",");
} else {
return new String[] { keyString };
}
}
private String getNamespace(String xmpKey) {
if (xmpKey.indexOf(":") > 0) {
String nsPrefix = xmpKey.substring(0, xmpKey.indexOf(":"));
return XMPMetaFactory.getSchemaRegistry().getNamespaceURI(nsPrefix);
}
return null;
}
private Node getOrCreateNode(Session session, String path, String nodetype) {
try {
if (session.itemExists(path)) {
return (Node) session.getItem(path);
} else {
Node childMetaNode = session.getRootNode().addNode(path.substring(1),
nodetype);
childMetaNode.setProperty(SYNC_FLAG, true);
return childMetaNode;
}
} catch (RepositoryException e) {
// We can not throw this exception unless we change the way storeXmp
// is implemented
log.warn("Failed to get or create node {}", path, e.getMessage());
if (log.isDebugEnabled()) {
log.debug("Failed to get or create node", e);
}
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Failed to get or create node", e);
}
}
return null;
}
private String getPropertyName(Node node, XMPPropertyInfo prop) {
String path = prop.getPath();
String name = (path.lastIndexOf("/") > 0)
? path.substring(path.lastIndexOf("/") + 1)
: path;
if (name.indexOf(":") > 0) {
String splits[] = name.split(":");
String prefix = splits[0];
String namespace = XMPMetaFactory.getSchemaRegistry().getNamespaceURI(
prefix);
if (namespace != null) {
try {
String regPrefix = checkNamespace(namespace, node);
// Bug 42205 : In case of getting prefix different than
// registered one
if (!regPrefix.equals(prefix)) {
prefix = regPrefix;
}
} catch (RepositoryException e) {
log.warn("Failed to check the namespace {}", namespace);
}
if (splits.length > 1) {
name = prefix + ":" + Text.escapeIllegalJcrChars(splits[1]);
}
} else {
name = Text.escapeIllegalJcrChars(name);
}
} else {
name = Text.escapeIllegalJcrChars(name);
}
return name;
}
/**
* Tells if the array item exists in the XMP metadata
*
* @param meta XMPMeta Object
* @param schemaNS The namespace URI for the array. Has the same usage as in
* getProperty()
.
* @param arrayName The name of the array. May be a general path expression, must not be
* null
or the empty string. Has the same namespace prefix usage as
* propName in getProperty()
.
* @param itemValue The value of the item to compare.
* @return Returns true
if the array exists, false
otherwise.
*/
private static boolean doesArrayItemExistInXMPMeta(XMPMeta meta, String schemaNS, String arrayName, String itemValue)
{
try {
int numOfItems = meta.countArrayItems(schemaNS, arrayName);
for (int index = 1; index <= numOfItems; ++index) {
String propPath = XMPPathFactory.composeArrayItemPath(arrayName, index);
String property = meta.getPropertyString(schemaNS, propPath);
if (property != null) {
if (property.equals(itemValue)) {
return true;
}
}
}
} catch (XMPException e) {
return false;
}
return false;
}
/**
* Resolve conflicting properties. if both low priority and high priority property exists
* in metadata, low priority property is removed from it.
* @param metadata
* @param conflictPropMap mapping of low priority vs high priority property names.
*/
private void resolvePropConflict(ExtractedMetadata metadata, MapconflictPropMap) {
for(Map.Entry entry: conflictPropMap.entrySet()){
if (metadata.getMetaDataProperties().containsKey(entry.getKey())
&& metadata.getMetaDataProperties().containsKey(entry.getValue())) {
metadata.getMetaDataProperties().remove(entry.getKey());
}
}
}
/**
* Resolve conflicting properties. if both low priority exists in XMP metadata and high priority
* exists in in metadata, low priority property is removed from it.
* @param metadata
* @param conflictPropMap mapping of low priority vs high priority property names.
*/
private void resolvePropConflict(ExtractedMetadata metadata, XMPMeta meta, MapconflictPropMap) {
for(Map.Entry entry: conflictPropMap.entrySet()){
try {
String namespace = getNamespace(entry.getKey());
//if namespace is null don't worry
if (namespace != null) {
XMPProperty property = meta.getProperty(namespace, entry.getKey());
if (property != null && metadata.getMetaDataProperties().containsKey(entry.getValue())) {
meta.deleteProperty(namespace, entry.getKey());
}
} else {
log.debug("namespace is null {} ", entry.getKey());
}
} catch (XMPException e) {
log.debug("exception while getting the property", e);
}
}
}
private Property setProperty(Node node, XMPPropertyInfo prop) {
Property p = null;
try {
Object val = prop.getOriValue();
String name = getPropertyName(node, prop);
val = checkForDate(val);
val = checkExif(name, val);
if (val instanceof Boolean) {
p = node.setProperty(name, (Boolean) val);
} else if (val instanceof Long || val instanceof Integer) {
p = node.setProperty(name, val instanceof Long
? (Long) val
: (Integer) val);
} else if (val instanceof Short) {
p = node.setProperty(name, (Short) val);
} else if (val instanceof Double) {
Double value = (Double) val;
// set 0 for infinite value
p = node.setProperty(name, value.isInfinite() || value.isNaN()
? 0
: value);
} else if (val instanceof XMPDateTime) {
p = node.setProperty(name, ((XMPDateTime) val).getCalendar());
} else if (val instanceof byte[]) {
p = node.setProperty(
name,
node.getSession().getValueFactory().createBinary(
new ByteArrayInputStream((byte[]) val)));
} else if (val instanceof Byte) {
p = node.setProperty(name, ((Byte) val).intValue());
} else if (val instanceof Date) {
Calendar cal = Calendar.getInstance();
cal.setTime((Date) val);
p = node.setProperty(name, cal);
} else if (val instanceof Calendar) {
p = node.setProperty(name, (Calendar) val);
} else if (val instanceof RationalNumber) {
Double value = ((RationalNumber) val).doubleValue();
p = node.setProperty(name, value.isInfinite() || value.isNaN()
? 0
: value);
} else if (val instanceof RationalNumber[]) {
List vals = new ArrayList();
for (RationalNumber rv : ((RationalNumber[]) val)) {
Double value = rv.doubleValue();
vals.add(node.getSession().getValueFactory().createValue(
value.isInfinite() || value.isNaN() ? 0 : value));
}
p = node.setProperty(name, vals.toArray(new Value[vals.size()]));
} else if (val instanceof int[]) {
// convert into a mv property in this case
List vals = new ArrayList();
for (Integer i : ((int[]) val)) {
vals.add(node.getSession().getValueFactory().createValue(i));
}
p = node.setProperty(name, vals.toArray(new Value[vals.size()]));
} else if (val instanceof short[]) {
// convert into a mv property in this case
List vals = new ArrayList();
for (Short i : ((short[]) val)) {
vals.add(node.getSession().getValueFactory().createValue(i));
}
p = node.setProperty(name, vals.toArray(new Value[vals.size()]));
} else if (val instanceof String) {
p = node.setProperty(name, (String) val);// prop.getValue());
} else {
log.warn("Cannot handle as the type is not supported for the xmp property("
+ prop.getPath() + ")");
}
} catch (Throwable re) {
if(log.isDebugEnabled())
log.debug("Cannot set xmp property (" + prop.getPath() + "): "
+ re.getMessage(), re);
else
log.warn("Cannot set xmp property (" + prop.getPath() + "): "
+ re.getMessage());
}
return p;
}
private Object checkExif(String name, Object val) {
// this "hack" is needed because of the exif spec that specifies such
// "strange" things
if (name.startsWith("exif:") && val instanceof byte[]) {
return new String((byte[]) val);
} else {
return val;
}
}
/**
* Sets multivalue props. One has to make sure that existing vals are not
* overwritten
*/
private Property setMvProperty(Node node, List props,
String name) {
Property p = null;
List vals = new ArrayList();
try {
// only check first entry
Object val = checkForDate(props.get(0).getOriValue());
// get existing props
if (node.hasProperty(name) && !INGREDIENT_TAG.equalsIgnoreCase(name)) {
Property existingProp = node.getProperty(name);
if (existingProp.isMultiple()) {
vals.addAll(Arrays.asList(existingProp.getValues()));
} else {
vals.add(existingProp.getValue());
existingProp.remove();
}
}
// update
if (val instanceof Boolean) {
for (XMPPropertyInfo prop : props) {
if (!hasDuplicate(vals, prop.getOriValue())) {
vals.add(node.getSession().getValueFactory().createValue(
(Boolean) prop.getOriValue()));
}
}
p = node.setProperty(name,
vals.toArray(new Value[props.size()]));
} else if (val instanceof Long) {
for (XMPPropertyInfo prop : props) {
if (!hasDuplicate(vals, prop.getOriValue())) {
vals.add(node.getSession().getValueFactory().createValue(
(Long) prop.getOriValue()));
}
}
p = node.setProperty(name,
vals.toArray(new Value[props.size()]));
} else if (val instanceof Double) {
for (XMPPropertyInfo prop : props) {
Double value = (Double) prop.getOriValue();
// set 0 for infinite value
value = value.isInfinite() || value.isNaN() ? 0 : value;
if (!hasDuplicate(vals, value)) {
vals.add(node.getSession().getValueFactory().createValue(
value));
}
}
p = node.setProperty(name,
vals.toArray(new Value[props.size()]));
} else if (val instanceof XMPDateTime) {
for (XMPPropertyInfo prop : props) {
Calendar cal = ((XMPDateTime) prop.getOriValue()).getCalendar();
if (!hasDuplicate(vals, cal)) {
vals.add(node.getSession().getValueFactory().createValue(
cal));
}
}
p = node.setProperty(name,
vals.toArray(new Value[props.size()]));
} else if (val instanceof byte[]) {
for (XMPPropertyInfo prop : props) {
final Binary binary = node.getSession().getValueFactory().createBinary(
new ByteArrayInputStream((byte[]) prop.getOriValue()));
vals.add(node.getSession().getValueFactory().createValue(
binary));
}
p = node.setProperty(name,
vals.toArray(new Value[props.size()]));
} else if (val instanceof Date) {
for (XMPPropertyInfo prop : props) {
Calendar cal = Calendar.getInstance();
cal.setTime((Date) checkForDate(prop.getOriValue()));
if (!hasDuplicate(vals, cal)) {
vals.add(node.getSession().getValueFactory().createValue(
cal));
}
}
p = node.setProperty(name,
vals.toArray(new Value[props.size()]));
} else {
for (XMPPropertyInfo prop : props) {
if (!hasDuplicate(vals, prop.getOriValue())) {
vals.add(node.getSession().getValueFactory().createValue(
(String) prop.getOriValue()));
}
}
p = node.setProperty(name,
vals.toArray(new Value[props.size()]));
}
} catch (Throwable re) {
log.warn("Cannot set xmp mv property (" + name + "): "
+ re.getMessage());
}
return p;
}
private boolean hasDuplicate(List values, Object value) {
/*
* FIXME: this gives warnings because of empty catch blocks. Failures of the underlying repository
* are ignored and lead to spurious, silent false returns as if no duplicate is present.
*/
if (value instanceof Boolean) {
for (Value val : values) {
try {
if (val.getBoolean() == ((Boolean) value)) {
return true;
}
} catch (RepositoryException re) {
}
}
} else if (value instanceof Long) {
for (Value val : values) {
try {
if (val.getLong() == ((Long) value)) {
return true;
}
} catch (RepositoryException re) {
}
}
} else if (value instanceof Calendar) {
for (Value val : values) {
try {
if (val.getDate().equals(value)) {
return true;
}
} catch (RepositoryException re) {
}
}
} else if (value instanceof Double) {
for (Value val : values) {
try {
if (val.getDouble() == ((Double) value)) {
return true;
}
} catch (RepositoryException re) {
}
}
} else {
// String here
for (Value val : values) {
try {
if (val.getString().equals((String) value)) {
return true;
}
} catch (RepositoryException re) {
}
}
}
return false;
}
private Object checkForDate(Object val) {
if (val instanceof String) {
Date date = DateParser.parseDate((String) val);
return (date == null) ? val : date;
} else {
return val;
}
}
private Object getValue(Property prop) {
Object val = null;
try {
switch (prop.getType()) {
case PropertyType.BINARY:
val = IOUtils.toByteArray(prop.getBinary().getStream());
break;
case PropertyType.DATE:
val = XMPDateTimeFactory.createFromCalendar(prop.getDate());
break;
case PropertyType.BOOLEAN:
val = prop.getBoolean();
break;
case PropertyType.DOUBLE:
val = prop.getDouble();
break;
case PropertyType.LONG:
val = prop.getLong();
break;
default:
val = prop.getString();
}
} catch (RepositoryException re) {
log.warn("Problem while getting xmp value from jcr property: "
+ re.getMessage());
} catch (IOException ioe) {
log.warn("Problem while getting binary xmp value from jcr property: "
+ ioe.getMessage());
}
return val;
}
private Object[] getMultiValues(Property prop) {
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy