All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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.LinkedHashSet;
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);

    private static final int MAX_ITEM_SIZE = 1000;

    //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;
                            insertIntoXMPMeta(meta, xmpKey, valList, namespace);
                        }
                        else if (val instanceof Object[]) {
                            List valList = Arrays.asList((Object[]) val);
                            insertIntoXMPMeta(meta, xmpKey, valList, namespace);
                        }
                        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);
                            insertIntoXMPMeta(meta, xmpKey, Arrays.asList(new String[]{strVal}), namespace);
                        }
                    } 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;
                            insertIntoXMPMeta(meta, xmpKey, valList, DAM_NS_URI);
                        }
                        else if (value instanceof Object[]) {
                            List valList = Arrays.asList((Object[]) value);
                            insertIntoXMPMeta(meta, xmpKey, valList, DAM_NS_URI);
                        }
                        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;
					insertIntoXMPMeta(meta, xmpKey, valList, nsUri);
				}
                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 {
                        List valList = Arrays.asList(valArray);
                        insertIntoXMPMeta(meta, xmpKey, valList, nsUri);
                    }
                }
                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;
    }

    /**
     * Inserts the given list into XMPMeta making sure there are no duplicates in it.
     * Limits the length of the array in XMPMeta to MAX_ITEM_SIZE
     * @param 
     * @param meta XMPMeta Object
     * @param xmpKey The name of the array in XMPMeta
     * @param valList The list to be inserted in XMPMeta
     * @param schemaNameSpace he namespace URI for the array. Has the same usage as in getProperty().
     * @throws XMPException
     */
    private void insertIntoXMPMeta(XMPMeta meta, String xmpKey, List valList, String schemaNameSpace)
            throws XMPException {

        Set valArrayWithoutDuplicates = new LinkedHashSet();
        if(meta.doesPropertyExist(schemaNameSpace, xmpKey)){
            int propertySize = meta.countArrayItems(schemaNameSpace, xmpKey);
            for(int i=1; i <= propertySize && valArrayWithoutDuplicates.size() < MAX_ITEM_SIZE; i++) {
                valArrayWithoutDuplicates.add((T)meta.getArrayItem(schemaNameSpace, xmpKey, i));
            }
        }
        for (Object val : valList) {
            if(valArrayWithoutDuplicates.size() >= MAX_ITEM_SIZE) {
                break;
            }
            String trimmedVal = val.toString().trim();
            if(!"".equals(trimmedVal)) {
                valArrayWithoutDuplicates.add((T)trimmedVal);
            }
        }
        for (Object val : valArrayWithoutDuplicates) {
            meta.appendArrayItem(schemaNameSpace, xmpKey, new PropertyOptions().setArray(true),
                    val.toString(), null);
        }
    }

    /**
     * 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 valueList = new ArrayList();
        try {
            Value values[] = prop.getValues();
            for (Value v : values) {
                //XMP array item can be string only so convert all other objects to string
                valueList.add(v.getString());
            }
        } catch (RepositoryException re) {
            log.warn("Problem while getting xmp value from jcr property: "
                    + re.getMessage());
        }
        return valueList.toArray(new Object[valueList.size()]);
    }

    private void checkNamespace(XMPPropertyInfo prop, Node metadataRoot)
            throws RepositoryException {
        checkNamespace(prop.getNamespace(), metadataRoot);
    }

    private String checkNamespace(String namespace, Node metadataRoot)
            throws RepositoryException {
        try {
            return metadataRoot.getSession().getWorkspace().getNamespaceRegistry().getPrefix(
                namespace);
        } catch (NamespaceException e) {
            String prefix = XMPMetaFactory.getSchemaRegistry().getNamespacePrefix(
                namespace);
            prefix = prefix.indexOf(":") > 0 ? prefix.substring(0,
                prefix.indexOf(":")) : prefix;

            Session serviceSession = null;
            try {
                Session session = metadataRoot.getSession();
                /* default to user session when we have no repository, e.g. are inside
                 * a test case. */
                if (repository != null) {
                    session = serviceSession = repository.loginService(NAMESPACE_UPDATE_HELPER,
                            session.getWorkspace().getName());
                }
                session.getWorkspace().getNamespaceRegistry().registerNamespace(prefix, namespace);
                session = metadataRoot.getSession();
                session.refresh(true);

                return session.getWorkspace().getNamespaceRegistry().getPrefix(namespace);
            } catch (RepositoryException re) {
                log.warn("Registration of {} failed, due to {}, caused by {}", namespace,
                        re.toString(), re.getCause().toString());
            } finally {
                if (null != serviceSession) {
                    serviceSession.logout();
                }
            }
            return prefix;
        }
    }

    private String registerNs(String nsPrefix, String namespace)
            throws XMPException {
        String regPrefix;
        if (XMPMetaFactory.getSchemaRegistry().getNamespacePrefix(namespace) == null) {
            // register ns
            regPrefix = XMPMetaFactory.getSchemaRegistry().registerNamespace(
                namespace, nsPrefix);
        } else {
            regPrefix = XMPMetaFactory.getSchemaRegistry().getNamespacePrefix(
                namespace);
        }

        return (regPrefix.indexOf(":") > 0 ? regPrefix.substring(0,
            regPrefix.indexOf(":")) : regPrefix);
    }

    /**
     * This method extracts hierarchical subjects from the respective XMP
     * property ({@link #METADATA_PROPERTY_NAME_ADOBE_KEYWORDS} and attempts to
     * map them to CQ tags. If tags are found, the asset's metadata node is
     * tagged as such.
     *
     * @param asset
     *            The {@link Asset}
     * @param session
     *            The session.
     */
    private void extractHierarchicalSubjects(final Asset asset, Session session) {

        final Resource assetResource = asset.adaptTo(Resource.class);
        final ResourceResolver resolver = assetResource.getResourceResolver();
        final Resource metadataResource = resolver.getResource(assetResource,JcrConstants.JCR_CONTENT +"/"+ DamConstants.METADATA_FOLDER);
        if (null != metadataResource) {

            final ValueMap props = metadataResource.adaptTo(ValueMap.class);
            final String[] subjects = props.get(
                    METADATA_PROPERTY_NAME_ADOBE_KEYWORDS, new String[0]);

            log.debug("got hierarchical subjects [{}] with content [{}].",
                    METADATA_PROPERTY_NAME_ADOBE_KEYWORDS,
                    StringUtils.join(subjects, ", "));

            if (subjects.length > 0) {
                if(tagManagerFactory == null){
                    log.warn("cannot save hierarchical subjects for asset [{}]", asset.getPath());
                    return;
                }
                final TagManager tagManager = tagManagerFactory
                        .getTagManager(session);
                final ArrayList tags = new ArrayList();

                for (final String subject : subjects) {

                    // treat first tag as namespace and "merge" with second tag
                    String titlePath = StringUtils.replaceOnce(subject, ":|",
                            ":");

                    // convert separators to path
                    titlePath = StringUtils.replace(titlePath, "|", "/");

                    final Tag tag = tagManager.resolveByTitle(titlePath);
                    if (null != tag) {
                        log.debug("got tag [{}] from title path [{}].",
                                tag.getTagID(), titlePath);
                        tags.add(tag);
                    } else {
                        log.warn("could not find tag from title path [{}].",
                                titlePath);
                    }
                }

                if (!tags.isEmpty()) {
                    try {
                        log.debug("tagging [{}] with [{}] tags.",
                                asset.getPath(), tags.size());
                        tagManager.setTags(metadataResource,
                                tags.toArray(new Tag[tags.size()]),
                                !asset.isBatchMode());

                    } catch (Exception e) {
                        log.error(
                                "cannot save hierarchical subjects for asset [{}]: ",
                                asset.getPath(), e);
                    }
                }
            }
        } else {
            log.error(
                    "cannot save hierarchical subjects for asset [{}], doesn't have metdata node.",
                    asset.getPath());
        }
    }
    private class DBParserErrorHandler implements ErrorHandler{
        @Override
        public void error(SAXParseException exception) throws SAXException {
            log.debug("DBParserErrorHandler error: " + exception);
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
            log.debug("DBParserErrorHandler fatalError: " + exception);
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
            log.debug("DBParserErrorHandler warning: " + exception);
        }
    }
}