package de.dpa.oss.metadata.mapper.imaging;
import com.google.common.base.Strings;
import com.google.common.collect.ListMultimap;
import de.dpa.oss.common.StringCharacterMapping;
import de.dpa.oss.metadata.mapper.common.DateTimeUtils;
import de.dpa.oss.metadata.mapper.common.YAXPathExpressionException;
import de.dpa.oss.metadata.mapper.imaging.common.ImageMetadata;
import de.dpa.oss.metadata.mapper.imaging.configuration.generated.*;
import de.dpa.oss.metadata.mapper.imaging.xmp.metadata.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
/**
* Note: instantiate this class only once in order to safe preparation time which consists of reading and
* interpreting the mapping configuration
*
* @author oliver langer
*/
public class G2ToMetadataMapper
{
private static Logger logger = LoggerFactory.getLogger(G2ToMetadataMapper.class);
private final List<MetadataProcessingInfo> metadataProcessingInfos;
private final String mappingName;
private StringCharacterMapping xmpStringCharacterMapping = null;
private EncodingCharset xmpCharacterCharset = EncodingCharset.UTF8;
private final IIMappingToImageMetadata iiMappingToImageMetadata;
private EncodingCharset iimCharacterCharset;
public G2ToMetadataMapper(final MappingType mapping)
{
mappingName = mapping.getName();
metadataProcessingInfos = new ArrayList<>();
for (MappingType.Metadata metadataMapping : mapping.getMetadata())
{
metadataProcessingInfos.add(new MetadataProcessingInfo(metadataMapping));
}
final Map<String, ConfigType.DateParser> idToDateParser;
if (mapping.getConfig() != null)
{
idToDateParser = configureDateParser(mapping.getConfig());
configureXMPMapping(mapping.getConfig());
}
else
{
idToDateParser = new HashMap<>();
}
iiMappingToImageMetadata = configureIIMMapping(mapping.getConfig(), idToDateParser);
}
private Map<String, ConfigType.DateParser> configureDateParser(final ConfigType config)
{
Map<String, ConfigType.DateParser> result = new HashMap<>();
if (config.getDateParser() == null)
{
return result;
}
for (ConfigType.DateParser dateParser : config.getDateParser())
{
result.put(dateParser.getId(), dateParser);
}
return result;
}
private void configureXMPMapping(final ConfigType config)
{
if (config.getXmp() != null)
{
final CharacterMappingType mappingConfig;
if (config.getXmp().getCharacterMappingRef() != null)
{
mappingConfig = (CharacterMappingType) config.getXmp().getCharacterMappingRef();
}
else
{
mappingConfig = null;
}
xmpStringCharacterMapping = ConfigStringCharacterMappingBuilder.stringCharacterMappingBuilder()
.withMappingConfigurartion(mappingConfig)
.withTargetCharsetAndFallbackReplacementChar(EncodingCharset.of(config.getXmp().getCharset()),
config.getXmp().getDefaultReplaceChar())
.build();
if (config.getXmp().getCharset() != null)
{
xmpCharacterCharset = EncodingCharset.of(config.getXmp().getCharset());
}
}
}
private IIMappingToImageMetadata configureIIMMapping(final ConfigType config, final Map<String, ConfigType.DateParser> idToDateParser)
{
StringCharacterMapping iimStringCharacterMapping = null;
iimCharacterCharset = EncodingCharset.ISO_8859_1;
if (config != null)
{
if (config.getIim() != null)
{
final CharacterMappingType mappingConfig;
if (config.getIim().getCharacterMappingRef() != null)
{
mappingConfig = (CharacterMappingType) config.getIim().getCharacterMappingRef();
}
else
{
mappingConfig = null;
}
iimStringCharacterMapping = ConfigStringCharacterMappingBuilder.stringCharacterMappingBuilder()
.withMappingConfigurartion(mappingConfig)
.withTargetCharsetAndFallbackReplacementChar(EncodingCharset.of(config.getIim().getCharset()),
config.getIim().getDefaultReplaceChar())
.build();
if (config.getIim().getCharset() != null)
{
iimCharacterCharset = EncodingCharset.of(config.getIim().getCharset());
}
}
}
if (iimStringCharacterMapping == null)
{
iimStringCharacterMapping = ConfigStringCharacterMappingBuilder.stringCharacterMappingBuilder().build();
}
return new IIMappingToImageMetadata(iimStringCharacterMapping, idToDateParser);
}
/**
* TODO
* experimental state: XMP mapping info is incomplete. Only root field is being shown.
*/
public void explainMapToImageMetadata(final Document document, final Writer writer)
throws YAXPathExpressionException, IOException
{
writer.write("Explain Metadata Mapping Config: " + mappingName);
logger.info("Explaining Metadata Mapping: " + mappingName);
for (MetadataProcessingInfo metadataProcessingInfo : metadataProcessingInfos)
{
writer.write("\nmetadata name:" + metadataProcessingInfo.getMappingName());
logger.debug("Processing mapping " + metadataProcessingInfo.getMappingName());
ListMultimap<String, String> partnameToSelectedValue = metadataProcessingInfo.selectXPathValues(document);
Iterator<String> partNames = metadataProcessingInfo.getPartNames();
ImageMetadata imageMetadata = new ImageMetadata();
while (partNames.hasNext())
{
final String partname = partNames.next();
writer.write("\n\tpart: " + partname);
List<String> values = partnameToSelectedValue.get(partname);
if (values.size() > 1)
{
writer.write("\n\t\tG2 Values:");
for (String value : values)
{
writer.write("\n\t\t\t\"" + value + "\"");
}
}
else if (values.size() == 1)
{
writer.write("\n\t\tG2 Value:\"" + values.get(0) + "\"");
}
mapToIIM(imageMetadata, partnameToSelectedValue, metadataProcessingInfo.getIIMapping());
mapToXMP(imageMetadata, partnameToSelectedValue, metadataProcessingInfo.getXMPMapping());
writer.write("\n\t\tXPath expressions (in order of execution):");
Iterator<QualifiedXPath> xPathsForPartName = metadataProcessingInfo.getXPathsForPartName(partname);
while (xPathsForPartName.hasNext())
{
QualifiedXPath qualifiedXPath = xPathsForPartName.next();
int rank = 1;
if (qualifiedXPath.getRank() != null)
{
rank = qualifiedXPath.getRank().intValue();
}
writer.write("\n\t\t\tRank=" + rank + " XPath=" + qualifiedXPath.getValue());
}
if (metadataProcessingInfo.getIIMapping() != null)
{
writer.write("\n\t\tIIM Mapping:");
List<IIMMapping.MapsTo> mapsTo = metadataProcessingInfo.getIIMapping().getMapsTo();
for (IIMMapping.MapsTo iimTarget : mapsTo)
{
if (Strings.isNullOrEmpty(iimTarget.getPartRef()))
{
writer.write("\n\t\t\tField=" + iimTarget.getField());
}
else if (iimTarget.getPartRef().equals(partname))
{
writer.write("\n\t\t\tField=" + iimTarget.getField());
}
if (imageMetadata.getIptcEntries().containsKey(iimTarget.getField()))
{
List<String> mappedValues = imageMetadata.getIptcEntries().get(iimTarget.getField());
if (mappedValues.size() > 1)
{
writer.write("\n\t\t\tValues:");
for (String value : mappedValues)
{
writer.write("\n\t\t\t\t\"" + value + "\"");
}
}
else if (mappedValues.size() == 1)
{
writer.write("\n\t\t\tValue:\"" + mappedValues.get(0) + "\"");
}
}
}
}
// TODO
if (metadataProcessingInfo.getXMPMapping() != null)
{
writer.write("\n\t\tXMP Mapping Target:");
List<XMPMapsTo> mapsTo = metadataProcessingInfo.getXMPMapping().getMapsTo();
for (XMPMapsTo xmpMapsTo : mapsTo)
{
if (Strings.isNullOrEmpty(xmpMapsTo.getPartRef()))
{
writer.write("\n\t\t\tField=" + xmpMapsTo.getField());
}
else if (xmpMapsTo.getPartRef().equals(partname))
{
writer.write("\n\t\t\tField=" + xmpMapsTo.getField());
}
}
}
}
}
}
public void mapToImageMetadata(final Document document, final ImageMetadata imageMetadata) throws YAXPathExpressionException
{
logger.debug("Mapping metadata to XMP and IIM structs");
for (MetadataProcessingInfo metadataProcessingInfo : metadataProcessingInfos)
{
logger.debug("Processing mapping " + metadataProcessingInfo.getMappingName());
ListMultimap<String, String> partnameToSelectedValue = metadataProcessingInfo.selectXPathValues(document);
imageMetadata.setIimCharset(iimCharacterCharset);
mapToIIM(imageMetadata, partnameToSelectedValue, metadataProcessingInfo.getIIMapping());
imageMetadata.setXmpCharset(xmpCharacterCharset);
mapToXMP(imageMetadata, partnameToSelectedValue, metadataProcessingInfo.getXMPMapping());
}
}
private void mapToXMP(final ImageMetadata imageMetadata,
final ListMultimap<String, String> partnameToSelectedValue,
final XMPMapping xmpMapping)
{
if (xmpMapping == null)
{
return;
}
for (XMPMapsTo xmpMapsTo : xmpMapping.getMapsTo())
{
XMPRootCollection rootMetadataCollection = new XMPRootCollection();
mapToXMP(xmpMapsTo, partnameToSelectedValue, rootMetadataCollection, false);
for (XMPMetadata xmpMetadata : rootMetadataCollection.getMetadata())
{
imageMetadata.addXMPMetadata(xmpMetadata);
}
}
}
private void mapToXMP(final XMPMapsTo xmpMapsTo,
final ListMultimap<String, String> partnameToSelectedValue, final XMPCollection collection,
final boolean multipleValuesValid)
{
XMPMappingTargetType targetType = xmpMapsTo.getTargetType();
if (targetType == null)
{
return;
}
String partReference = xmpMapsTo.getPartRef();
if (Strings.isNullOrEmpty(partReference))
{
partReference = MetadataProcessingInfo.DEFAULT_PARTNAME;
}
if (!partnameToSelectedValue.containsKey(partReference))
{
/* no data available for target field. If the target field is a primitive type (STRING, DATE,...) then mapping
ends here
*/
if (targetType != XMPMappingTargetType.BAG && targetType != XMPMappingTargetType.SEQUENCE
&& targetType != XMPMappingTargetType.STRUCT)
{
return;
}
}
switch (targetType)
{
case TEXT:
for (String value : partnameToSelectedValue.get(partReference))
{
collection.add(new XMPString(xmpMapsTo.getTargetNamespace(), xmpMapsTo.getField(), xmpStringCharacterMapping.map(value)));
if (!multipleValuesValid)
{
// only one element for the root collection
break;
}
}
break;
case INTEGER:
for (String value : partnameToSelectedValue.get(partReference))
{
try
{
collection.add(new XMPInteger(xmpMapsTo.getTargetNamespace(), xmpMapsTo.getField(),
Integer.parseInt(xmpStringCharacterMapping.map(value))));
}
catch (NumberFormatException ex)
{
logger.warn("Integer value expected for partReference=\"" + partReference + "\" but got this value:" + value);
}
if (!multipleValuesValid)
{
// only one element for the root collection
break;
}
}
break;
case LANG_ALT:
for (String value : partnameToSelectedValue.get(partReference))
{
collection.add(new XMPLocalizedText(xmpMapsTo.getTargetNamespace(), xmpMapsTo.getField(), "x-default",
xmpStringCharacterMapping.map(value)));
if (!multipleValuesValid)
{
// only one element for the root collection
break;
}
}
break;
case DATE:
try
{
for (String value : partnameToSelectedValue.get(partReference))
{
Date date = DateTimeUtils.parseDate(xmpStringCharacterMapping.map(value));
collection.add(new XMPDate(xmpMapsTo.getTargetNamespace(), xmpMapsTo.getField(), date));
if (!multipleValuesValid)
{
// only one element for the root collection
break;
}
}
}
catch (Throwable e)
{
logger.error("Unable to parse and map date field \"" + partReference + "\" with date string: "
+ partnameToSelectedValue.get(partReference).get(0));
}
break;
case BAG:
XMPBag xmpBagNamedMetadata = new XMPBag(xmpMapsTo.getTargetNamespace(), xmpMapsTo.getField()
);
mapToXMPComplexType(xmpBagNamedMetadata, xmpMapsTo, partnameToSelectedValue, true);
if (xmpBagNamedMetadata.hasItems())
{
collection.add(xmpBagNamedMetadata);
}
break;
case SEQUENCE:
XMPSequence xmpSequenceNamedMetadata = new XMPSequence(xmpMapsTo.getTargetNamespace(),
xmpMapsTo.getField());
mapToXMPComplexType(xmpSequenceNamedMetadata, xmpMapsTo, partnameToSelectedValue, true);
if (xmpSequenceNamedMetadata.hasItems())
{
collection.add(xmpSequenceNamedMetadata);
}
break;
case STRUCT:
XMPStruct xmpStructMetadata = new XMPStruct(xmpMapsTo.getTargetNamespace(), xmpMapsTo.getField());
mapToXMPComplexType(xmpStructMetadata, xmpMapsTo, partnameToSelectedValue, false);
if (xmpStructMetadata.hasItems())
{
collection.add(xmpStructMetadata);
}
break;
default:
logger.info("Mapping of XMP type \"" + targetType.name() + "\" not supported");
}
}
private void mapToXMPComplexType(final XMPCollection targetCollection, final XMPMapsTo rootMappingItem,
final ListMultimap<String, String> partnameToSelectedValue, final boolean multipleValuesValid)
{
for (XMPMapsTo innerElements : rootMappingItem.getMapsTo())
{
mapToXMP(innerElements, partnameToSelectedValue, targetCollection, multipleValuesValid);
}
}
private void mapToIIM(final ImageMetadata imageMetadata,
final ListMultimap<String, String> partnameToSelectedValue,
final IIMMapping iiMapping)
{
if (iiMapping == null)
{
return;
}
for (IIMMapping.MapsTo mapsTo : iiMapping.getMapsTo())
{
String partRef = mapsTo.getPartRef();
if (Strings.isNullOrEmpty(partRef))
{
partRef = MetadataProcessingInfo.DEFAULT_PARTNAME;
}
if (partnameToSelectedValue.containsKey(partRef))
{
iiMappingToImageMetadata.map(mapsTo, partnameToSelectedValue.get(partRef), imageMetadata);
}
}
}
}