package de.dpa.oss.metadata.mapper.imaging;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import de.dpa.oss.metadata.mapper.common.XmlUtils;
import de.dpa.oss.metadata.mapper.common.YAXPathExpressionException;
import de.dpa.oss.metadata.mapper.imaging.configuration.generated.*;
import de.dpa.oss.metadata.mapper.processor.Processor;
import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* @author oliver langer
*/
public class MetadataProcessingInfo
{
private static Logger logger = LoggerFactory.getLogger(MetadataProcessingInfo.class);
public static final String DEFAULT_PARTNAME = "@__default";
/**
* each part may have multiple xpath expressions assigned to it. These expressions are orderd by a rank
*/
final Map<String, TreeMap<Integer, QualifiedXPath>> partNameToOrderedXPaths;
final Map<String, String> partnameToDefaultValues;
final MappingType.Metadata metadataMapping;
final Map<String, Processor> partnameToProcessor;
public MetadataProcessingInfo(final MappingType.Metadata metadataMapping)
{
this.metadataMapping = metadataMapping;
partNameToOrderedXPaths = createMapOfXPathsOrderedByRank(metadataMapping);
partnameToDefaultValues = createMapOfDefaultValues(metadataMapping);
partnameToProcessor = createMapOfProcessors(metadataMapping);
}
private Map<String, Processor> createMapOfProcessors(final MappingType.Metadata metadataMapping)
{
Map<String, Processor> toReturn = new HashMap<>();
if (metadataMapping.getProcessors() == null)
{
return toReturn;
}
for (ProcessorType processorConfig : metadataMapping.getProcessors().getProcessor())
{
logger.debug("Loading processorConfig: " + processorConfig.getClazz());
Processor processor;
try
{
processor = (Processor) this.getClass().getClassLoader().loadClass(processorConfig.getClazz()).newInstance();
for (ProcessorType.Parameter parameter : processorConfig.getParameter())
{
PropertyUtils.setSimpleProperty(processor, parameter.getName(), parameter.getValue());
}
}
catch (ClassNotFoundException e)
{
logger.error("Unable to load processorConfig class: " + processorConfig.getClazz(), e);
throw new IllegalArgumentException("Unable to load processorConfig class: " + processorConfig.getClazz(), e);
}
catch (InstantiationException e)
{
logger.error("Unable to instantiate processorConfig class: " + processorConfig.getClazz(), e);
throw new IllegalArgumentException("Unable to instantiate processorConfig class: " + processorConfig.getClazz(), e);
}
catch (IllegalAccessException e)
{
logger.error("Unable to access processorConfig class: " + processorConfig.getClazz(), e);
throw new IllegalArgumentException("Unable to access processorConfig class: " + processorConfig.getClazz(), e);
}
catch (NoSuchMethodException e)
{
logger.error("Unable to hand-over parameter to processor: " + processorConfig.getClazz(), e);
throw new IllegalArgumentException("Unable to hand-over parameter to processor: " + processorConfig.getClazz(), e);
}
catch (InvocationTargetException e)
{
logger.error("Handing-over parameters to processor failed. Processor: " + processorConfig.getClazz(), e);
throw new IllegalArgumentException("Handing-over parameters to processor failed. Processor: " + processorConfig.getClazz(),
e);
}
if (processor != null)
{
toReturn.put(processorConfig.getPartRef(), processor);
}
}
return toReturn;
}
private Map<String, String> createMapOfDefaultValues(final MappingType.Metadata metadataMapping)
{
Map<String, String> toReturn = new HashMap<>();
for (MappingType.Metadata.Default aDefault : metadataMapping.getDefault())
{
if (Strings.isNullOrEmpty(aDefault.getPart()))
{
toReturn.put(DEFAULT_PARTNAME, aDefault.getValue());
}
else
{
toReturn.put(aDefault.getPart(), aDefault.getValue());
}
}
return toReturn;
}
/**
* @return a map where the part maps to the selected values. If a processor is assigned to this part then
* it will be evaluated too
*/
public ListMultimap<String, String> selectXPathValues(final Document document) throws YAXPathExpressionException
{
ListMultimap<String, String> partnameToValue = ArrayListMultimap.create();
for (String partname : partNameToOrderedXPaths.keySet())
{
List<String> selectedValues = selectFirstMatchingXPathValues(document, partNameToOrderedXPaths.get(partname));
if (selectedValues != null && selectedValues.size() > 0)
{
if (partnameToProcessor.containsKey(partname))
{
logger.debug("Post-process selected values for part name: " + partname);
List<String> processedData = partnameToProcessor.get(partname).process(selectedValues);
partnameToValue.putAll(partname, processedData);
}
else
{
partnameToValue.putAll(partname, selectedValues);
}
}
else
{
if (partnameToDefaultValues.containsKey(partname))
{
partnameToValue.put(partname, partnameToDefaultValues.get(partname));
}
}
}
/*
* now use default in cases where no value has been found
*/
for (String partname : partnameToDefaultValues.keySet())
{
if (!partnameToValue.containsKey(partname) || partnameToValue.get(partname).isEmpty())
{
partnameToValue.put(partname, partnameToDefaultValues.get(partname));
}
}
return partnameToValue;
}
public Iterator<String> getPartNames()
{
return partNameToOrderedXPaths.keySet().iterator();
}
/**
* @return list of xpaths belonging to the given partname. The xpaths are given in the order defined by the rank
*/
public Iterator<QualifiedXPath> getXPathsForPartName(final String partname)
{
if (!partNameToOrderedXPaths.containsKey(partname))
{
throw new IllegalArgumentException("Unable to find part with name: " + partname);
}
return partNameToOrderedXPaths.get(partname).values().iterator();
}
private List<String> selectFirstMatchingXPathValues(final Document document,
final TreeMap<Integer, QualifiedXPath> integerQualifiedXPathTreeMap) throws YAXPathExpressionException
{
List<String> toReturn = new ArrayList<>();
for (QualifiedXPath qualifiedXPath : integerQualifiedXPathTreeMap.values())
{
logger.debug("Evaluating xpath \"" + qualifiedXPath.getValue() + "\"");
try
{
if (qualifiedXPath.getReturnType() == XPathReturnType.STRING)
{
String selectedValue = XmlUtils.selectValue(qualifiedXPath.getValue(), document.getDocumentElement());
if (!Strings.isNullOrEmpty(selectedValue))
{
toReturn.add(selectedValue);
break;
}
}
else
{
List<String> selectedNodeValues = XmlUtils.selectValues(qualifiedXPath.getValue(), document.getDocumentElement());
if (selectedNodeValues != null && selectedNodeValues.size() > 0)
{
toReturn.addAll(selectedNodeValues);
break;
}
}
}
catch (YAXPathExpressionException e)
{
logger.error("Error evaluating XPath expression \"" + qualifiedXPath.getValue() + "\", reason: " + e.getMessage());
throw e;
}
}
return toReturn;
}
/**
* Add xpaths element to a hash map. Order entries by rank
*/
private Map<String, TreeMap<Integer, QualifiedXPath>> createMapOfXPathsOrderedByRank(final MappingType.Metadata metadata)
{
Map<String, TreeMap<Integer, QualifiedXPath>> partnameToOrderedXPath = new HashMap<>();
for (QualifiedXPath qualifiedXPath : metadata.getXpath())
{
final String partName;
if (Strings.isNullOrEmpty(qualifiedXPath.getPart()))
{
partName = DEFAULT_PARTNAME;
}
else
{
partName = qualifiedXPath.getPart();
}
final TreeMap<Integer, QualifiedXPath> rankToQualifiedPath;
if (partnameToOrderedXPath.containsKey(partName))
{
rankToQualifiedPath = partnameToOrderedXPath.get(partName);
}
else
{
rankToQualifiedPath = new TreeMap<>();
partnameToOrderedXPath.put(partName, rankToQualifiedPath);
}
Integer rank;
if (qualifiedXPath.getRank() == null)
{
rank = 0;
}
else
{
rank = qualifiedXPath.getRank().intValue();
}
rankToQualifiedPath.put(rank, qualifiedXPath);
}
return partnameToOrderedXPath;
}
public String getMappingName()
{
return metadataMapping.getName();
}
public IIMMapping getIIMapping()
{
return metadataMapping.getIim();
}
public XMPMapping getXMPMapping()
{
return metadataMapping.getXmp();
}
}