Coverage Summary for Class: DivXmlElementAccess (org.kitodo.dataformat.access)
Class |
Class, %
|
Method, %
|
Line, %
|
DivXmlElementAccess |
100%
(1/1)
|
90,9%
(10/11)
|
96,4%
(133/138)
|
/*
* (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org>
*
* This file is part of the Kitodo project.
*
* It is licensed under GNU General Public License version 3 or later.
*
* For the full copyright and license information, please read the
* GPL3-License.txt file that was distributed with this source code.
*/
package org.kitodo.dataformat.access;
import java.math.BigInteger;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.apache.commons.lang3.tuple.Pair;
import org.kitodo.api.MdSec;
import org.kitodo.api.Metadata;
import org.kitodo.api.MetadataEntry;
import org.kitodo.api.MetadataGroup;
import org.kitodo.api.dataformat.LogicalDivision;
import org.kitodo.api.dataformat.PhysicalDivision;
import org.kitodo.api.dataformat.View;
import org.kitodo.api.dataformat.mets.KitodoUUID;
import org.kitodo.dataformat.metskitodo.AmdSecType;
import org.kitodo.dataformat.metskitodo.DivType;
import org.kitodo.dataformat.metskitodo.KitodoType;
import org.kitodo.dataformat.metskitodo.MdSecType;
import org.kitodo.dataformat.metskitodo.MdSecType.MdWrap;
import org.kitodo.dataformat.metskitodo.MdSecType.MdWrap.XmlData;
import org.kitodo.dataformat.metskitodo.MetadataGroupType;
import org.kitodo.dataformat.metskitodo.MetadataType;
import org.kitodo.dataformat.metskitodo.Mets;
/**
* The tree-like outline structure for digital representation. This structuring
* structure can be subdivided into arbitrary finely granular.
*/
public class DivXmlElementAccess extends LogicalDivision {
/**
* The qualified name of the Kitodo metadata format, needed to assemble the
* metadata entries in METS using JAXB.
*/
private static final QName KITODO_QNAME = new QName("http://meta.kitodo.org/v1/", "kitodo");
/**
* Some magic numbers that are used in the METS XML file representation of
* this structure to describe relations between XML elements. They need to
* be stored because some scatty third-party scripts rely on them not being
* changed anymore once assigned.
*/
private final String metsReferrerId;
/**
* Creates a new DivXmlElementAccess.
*/
public DivXmlElementAccess() {
super();
metsReferrerId = KitodoUUID.randomUUID();
}
/**
* Creates a new DivXmlElementAccess for an existing structure.
*/
DivXmlElementAccess(LogicalDivision logicalDivision) {
super(logicalDivision);
metsReferrerId = logicalDivision instanceof DivXmlElementAccess
? ((DivXmlElementAccess) logicalDivision).metsReferrerId
: KitodoUUID.randomUUID();
}
/**
* Constructor to read a structure from METS.
*
* @param div
* METS {@code <div>} element from which the structure is to be
* built
* @param mets
* METS data structure from which it is possible to determine
* what kind of metadata section is linked
* @param physicalDivisionsMap
* From this map, the physical divisions are read, which must be
* referenced here by their ID.
* @param parentOrder
* This represents the value of the parent's {@code ORDER} attribute. It is not required for logical elements by the
* mets standard but is used in Kitodo internal data format. It helps to display logical and physical elements in an
* advanced combined tree.
*/
DivXmlElementAccess(DivType div, Mets mets, Map<String, List<FileXmlElementAccess>> physicalDivisionsMap, int parentOrder) {
super();
div.getCONTENTIDS().parallelStream().map(URI::create).forEachOrdered(super.getContentIds()::add);
super.setLabel(div.getLABEL());
for (Object mdSecType : div.getDMDID()) {
super.getMetadata().addAll(readMetadata((MdSecType) mdSecType, MdSec.DMD_SEC));
}
for (Object mdSecType : div.getADMID()) {
super.getMetadata().addAll(readMetadata((MdSecType) mdSecType, amdSecTypeOf(mets, (MdSecType) mdSecType)));
}
metsReferrerId = div.getID();
BigInteger order = div.getORDER();
if (Objects.nonNull(order) && order.intValue() > 0) {
setOrder(order.intValue());
} else if (parentOrder > 0) {
setOrder(parentOrder);
} else {
setOrder(1);
}
super.setOrderlabel(div.getORDERLABEL());
for (DivType child : div.getDiv()) {
getChildren().add(new DivXmlElementAccess(child, mets, physicalDivisionsMap, getOrder()));
}
super.setType(div.getTYPE());
List<FileXmlElementAccess> fileXmlElementAccesses = physicalDivisionsMap.get(div.getID());
if (Objects.nonNull(fileXmlElementAccesses)) {
for (FileXmlElementAccess fileXmlElementAccess : fileXmlElementAccesses) {
if (Objects.nonNull(fileXmlElementAccess)
&& !fileXmlElementAccessIsLinkedToChildren(fileXmlElementAccess, div.getDiv(), physicalDivisionsMap)) {
super.getViews().add(new AreaXmlElementAccess(fileXmlElementAccess).getView());
fileXmlElementAccess.getPhysicalDivision().getLogicalDivisions().add(this);
}
}
}
super.setLink(MptrXmlElementAccess.getLinkFromDiv(div));
}
private boolean fileXmlElementAccessIsLinkedToChildren(FileXmlElementAccess fileXmlElementAccess,
List<DivType> divs,
Map<String, List<FileXmlElementAccess>> physicalDivisionsMap) {
if (divs.size() == 0) {
return false;
}
boolean test = false;
for (DivType div : divs) {
List<FileXmlElementAccess> fileXmlElementAccesses = physicalDivisionsMap.get(div.getID());
if (Objects.nonNull(fileXmlElementAccesses) && fileXmlElementAccesses.contains(fileXmlElementAccess)) {
return true;
}
if (div.getDiv().size() > 0
&& fileXmlElementAccessIsLinkedToChildren(fileXmlElementAccess, div.getDiv(), physicalDivisionsMap)) {
test = true;
}
}
return test;
}
/**
* Determines from a METS data structure of which type is a metadata
* section.
*
* <p>
* Implementation note: This method would be a good candidate for
* parallelization.
*
* @param mets
* METS data structure that determines what type of metadata
* section is
* @param mdSec
* administrative metadata section whose type is to be
* determined
* @return the type of administrative metadata section
*/
static final MdSec amdSecTypeOf(Mets mets, MdSecType mdSec) {
for (AmdSecType amdSec : mets.getAmdSec()) {
if (amdSec.getSourceMD().contains(mdSec)) {
return MdSec.SOURCE_MD;
} else if (amdSec.getDigiprovMD().contains(mdSec)) {
return MdSec.DIGIPROV_MD;
} else if (amdSec.getRightsMD().contains(mdSec)) {
return MdSec.RIGHTS_MD;
} else if (amdSec.getTechMD().contains(mdSec)) {
return MdSec.TECH_MD;
}
}
throw new NoSuchElementException();
}
/**
* Reads a metadata section and adds the metadata to the structure.
*
* @param mdSecType
* type of metadata section
* @param mdSec
* metadata section to be read
*
* @return a collection of type Metadata
*/
static final Collection<Metadata> readMetadata(MdSecType mdSecType, MdSec mdSec) {
Collection<Metadata> metadata = new HashSet<>();
if (Objects.nonNull(mdSecType) && Objects.nonNull(mdSecType.getMdWrap())) {
for (Object object : mdSecType.getMdWrap().getXmlData().getAny()) {
if (object instanceof JAXBElement) {
JAXBElement<?> jaxbElement = (JAXBElement<?>) object;
Object value = jaxbElement.getValue();
if (value instanceof KitodoType) {
KitodoType kitodoType = (KitodoType) value;
for (MetadataType metadataEntry : kitodoType.getMetadata()) {
if (!metadataEntry.getValue().isEmpty()) {
metadata.add(new MetadataXmlElementAccess(mdSec, metadataEntry).getMetadataEntry());
}
}
for (MetadataGroupType metadataGroup : kitodoType.getMetadataGroup()) {
metadata.add(new MetadataGroupXmlElementAccess(mdSec, metadataGroup).getMetadataGroup());
}
}
}
}
}
return metadata;
}
/**
* Creates a METS {@code <div>} element from this structure.
*
* @param physicalDivisionIDs
* the assigned identifier for each physical division so that the link
* pairs of the struct link section can be formed later
* @param smLinkData
* the link pairs of the struct link section are added to this
* list
* @param mets
* the METS structure in which the metadata is added
* @return a METS {@code <div>} element
*/
DivType toDiv(Map<PhysicalDivision, String> physicalDivisionIDs, LinkedList<Pair<String, String>> smLinkData, Mets mets) {
DivType div = new DivType();
div.setID(metsReferrerId);
if (!super.getContentIds().isEmpty()) {
super.getContentIds().parallelStream().map(URI::toString).forEachOrdered(div.getCONTENTIDS()::add);
}
div.setLABEL(super.getLabel());
if (getOrder() > 0) {
div.setORDER(BigInteger.valueOf(getOrder()));
}
div.setORDERLABEL(super.getOrderlabel());
div.setTYPE(super.getType());
smLinkData.addAll(super.getViews().stream().map(View::getPhysicalDivision).map(physicalDivisionIDs::get)
.map(physicalDivisionId -> Pair.of(metsReferrerId, physicalDivisionId)).collect(Collectors.toList()));
Optional<MdSecType> optionalDmdSec = createMdSec(super.getMetadata(), MdSec.DMD_SEC);
if (optionalDmdSec.isPresent()) {
MdSecType dmdSec = optionalDmdSec.get();
String name = metsReferrerId + ':' + MdSec.DMD_SEC.toString();
dmdSec.setID(KitodoUUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)));
mets.getDmdSec().add(dmdSec);
div.getDMDID().add(dmdSec);
}
Optional<AmdSecType> optionalAmdSec = createAmdSec(super.getMetadata(), metsReferrerId, div);
if (optionalAmdSec.isPresent()) {
AmdSecType admSec = optionalAmdSec.get();
mets.getAmdSec().add(admSec);
}
if (Objects.nonNull(super.getLink())) {
MptrXmlElementAccess.addMptrToDiv(super.getLink(), div);
}
for (LogicalDivision subLogicalDivision : super.getChildren()) {
div.getDiv().add(new DivXmlElementAccess(subLogicalDivision).toDiv(physicalDivisionIDs, smLinkData, mets));
}
return div;
}
/**
* Creates a metadata section of the specified domain of the Kitodo type
* and returns it with its connection to the METS if there is data for it.
*
* @param domain
* Domain for which a metadata section is to be generated
* @return a metadata section, if there is data for it
*/
static Optional<MdSecType> createMdSec(Iterable<Metadata> metadata, MdSec domain) {
if (StreamSupport.stream(metadata.spliterator(), false)
.noneMatch(piece -> Objects.equals(piece.getDomain(), domain))) {
return Optional.empty();
}
KitodoType kitodoType = new KitodoType();
for (Metadata entry : metadata) {
if (domain.equals(entry.getDomain())) {
if (entry instanceof MetadataEntry) {
kitodoType.getMetadata().add(new MetadataXmlElementAccess((MetadataEntry) entry).toMetadata());
} else if (entry instanceof MetadataGroup) {
kitodoType.getMetadataGroup()
.add(new MetadataGroupXmlElementAccess((MetadataGroup) entry).toXMLMetadataGroup());
}
}
}
XmlData xmlData = new XmlData();
xmlData.getAny().add(new JAXBElement<>(KITODO_QNAME, KitodoType.class, kitodoType));
MdWrap mdWrap = new MdWrap();
mdWrap.setXmlData(xmlData);
MdSecType dmdSec = new MdSecType();
dmdSec.setMdWrap(mdWrap);
return Optional.of(dmdSec);
}
/**
* Generates an {@code <amdSec>} if administrative metadata exists on this
* structure.
*
* @param div
* div where ADMID references must be added to the generated
* metadata sections
* @return an {@code <amdSec>}, if necessary
*/
static Optional<AmdSecType> createAmdSec(Iterable<Metadata> metadata, String metsReferrerId, DivType div) {
AmdSecType amdSec = new AmdSecType();
boolean source = addMdSec(createMdSec(metadata, MdSec.SOURCE_MD), metsReferrerId, MdSec.SOURCE_MD,
AmdSecType::getSourceMD, amdSec, div);
boolean digiprov = addMdSec(createMdSec(metadata, MdSec.DIGIPROV_MD), metsReferrerId, MdSec.DIGIPROV_MD,
AmdSecType::getDigiprovMD, amdSec, div);
boolean rights = addMdSec(createMdSec(metadata, MdSec.RIGHTS_MD), metsReferrerId, MdSec.RIGHTS_MD,
AmdSecType::getRightsMD, amdSec, div);
boolean tech = addMdSec(createMdSec(metadata, MdSec.TECH_MD), metsReferrerId, MdSec.TECH_MD,
AmdSecType::getTechMD, amdSec, div);
return source || digiprov || rights || tech ? Optional.of(amdSec) : Optional.empty();
}
/**
* Adds a metadata section to an administrative metadata section, if there
* is one. This function deduplicates fourfold existing function for four
* different metadata sections.
*
* @param optionalMdSec
* perhaps existing metadata section to be added if it exists
* @param mdSecType
* the type of the mdSec, used in ID generation
* @param mdSecTypeGetter
* the getter via which the metadata section can be added to the
* administrative metadata section
* @param amdSec
* administrative metadata section to which the metadata
* section should be added, if any
* @param div
* div where ADMID references must be added to the generated
* metadata sections
* @return whether something has been added to the administrative metadata
* section
*/
private static boolean addMdSec(Optional<MdSecType> optionalMdSec, String metsReferrerId, MdSec mdSecType,
Function<AmdSecType, List<MdSecType>> mdSecTypeGetter, AmdSecType amdSec, DivType div) {
if (!optionalMdSec.isPresent()) {
return false;
} else {
MdSecType mdSec = optionalMdSec.get();
String name = metsReferrerId + ':' + mdSecType.toString();
mdSec.setID(KitodoUUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)));
mdSecTypeGetter.apply(amdSec).add(mdSec);
div.getADMID().add(mdSec);
return true;
}
}
}