Coverage Summary for Class: ExportXmlLog (org.kitodo.docket)
Class |
Class, %
|
Method, %
|
Line, %
|
ExportXmlLog |
100%
(1/1)
|
100%
(21/21)
|
91%
(191/210)
|
/*
* (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.docket;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.kitodo.api.docket.DocketData;
import org.kitodo.api.docket.Property;
import org.kitodo.config.KitodoConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides xml logfile generation. After the generation the file
* will be written to user home directory
*
* @author Robert Sehr
* @author Steffen Hankiewicz
*
*/
public class ExportXmlLog implements Consumer<OutputStream> {
private static final Logger logger = LoggerFactory.getLogger(ExportXmlLog.class);
private static final String LABEL = "label";
private static final String NAMESPACE = "http://www.kitodo.org/logfile";
private static final String PROPERTIES = "properties";
private static final String PROPERTY = "property";
private static final String PROPERTY_IDENTIFIER = "propertyIdentifier";
private static final String VALUE = "value";
List<DocketData> docketData;
/**
* Makes the class polymorphic.
*
* @param docketData
* docket data
*/
ExportXmlLog(Iterable<DocketData> docketData) {
this.docketData = docketData instanceof List ? (List<DocketData>) docketData
: StreamSupport.stream(docketData.spliterator(), false).collect(Collectors.toList());
}
/**
* Makes the class polymorphic.
*
* @param docketData
* docket data
*/
ExportXmlLog(DocketData docketData) {
this.docketData = Arrays.asList(docketData);
}
@Override
public void accept(OutputStream outputStream) {
try {
if (docketData.size() == 1) {
startExport(outputStream);
} else {
startMultipleExport(outputStream);
}
} catch (IOException ioFailed) {
throw new UncheckedIOException(ioFailed);
}
}
/**
* This method exports the production metadata as XML to a given stream.
*
* @param os
* the OutputStream to write the contents to
* @throws IOException
* Throws IOException, when document creation fails.
*/
void startExport(OutputStream os) throws IOException {
try {
Document doc = createDocument(docketData.get(0), true);
XMLOutputter outp = new XMLOutputter();
outp.setFormat(Format.getPrettyFormat());
outp.output(doc, os);
os.close();
} catch (RuntimeException e) {
logger.error("Document creation failed.");
throw new IOException(e);
}
}
/**
* This method exports the production metadata for al list of processes as a
* single file to a given stream.
*
* @param outputStream
* The output stream, to write the docket to.
*/
void startMultipleExport(OutputStream outputStream) {
Document answer = new Document();
Element root = new Element("processes");
answer.setRootElement(root);
Namespace xmlns = Namespace.getNamespace(NAMESPACE);
Namespace xsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
root.addNamespaceDeclaration(xsi);
root.setNamespace(xmlns);
Attribute attSchema = new Attribute("schemaLocation", NAMESPACE + " XML-logfile.xsd",
xsi);
root.setAttribute(attSchema);
for (DocketData docketData : this.docketData) {
Document doc = createDocument(docketData, false);
Element processRoot = doc.getRootElement();
processRoot.detach();
root.addContent(processRoot);
}
XMLOutputter outp = new XMLOutputter(Format.getPrettyFormat());
try {
outp.output(answer, outputStream);
} catch (IOException e) {
logger.error("Generating XML Output failed.", e);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
logger.error("Closing the output stream failed.", e);
}
}
}
}
/**
* This method creates a new xml document with process metadata.
*
* @param docketData
* the docketData to export
* @return a new xml document
*/
private Document createDocument(DocketData docketData, boolean addNamespace) {
Element processElm = new Element("process");
final Document doc = new Document(processElm);
processElm.setAttribute("processID", String.valueOf(docketData.getProcessId()));
Namespace xmlns = Namespace.getNamespace(NAMESPACE);
processElm.setNamespace(xmlns);
// namespace declaration
processNamespaceDeclaration(addNamespace, processElm);
// process information
ArrayList<Element> processElements = processProcessInformation(docketData, xmlns);
// template information
processTemplateInformation(docketData, xmlns, processElements);
// digital document information
processDigitalDocumentInformation(docketData, xmlns, processElements);
// METS information
Element metsElement = new Element("metsInformation", xmlns);
List<Element> metadataElements = createMetadataElements(xmlns, docketData);
metsElement.addContent(metadataElements);
processElements.add(metsElement);
processElm.setContent(processElements);
return doc;
}
private void processDigitalDocumentInformation(DocketData docketData, Namespace xmlns,
ArrayList<Element> processElements) {
ArrayList<Element> docElements = new ArrayList<>();
Element dd = new Element("digitalDocument", xmlns);
List<Element> docProperties = prepareProperties(docketData.getWorkpieceProperties(), xmlns);
if (!docProperties.isEmpty()) {
Element properties = new Element(PROPERTIES, xmlns);
properties.addContent(docProperties);
dd.addContent(properties);
}
docElements.add(dd);
Element digdoc = new Element("digitalDocuments", xmlns);
digdoc.addContent(docElements);
processElements.add(digdoc);
}
private void processTemplateInformation(DocketData docketData, Namespace xmlns,
ArrayList<Element> processElements) {
ArrayList<Element> templateElements = new ArrayList<>();
Element template = new Element("original", xmlns);
ArrayList<Element> templateProperties = new ArrayList<>();
if (docketData.getTemplateProperties() != null) {
processTemplateProperties(docketData, xmlns, templateProperties);
}
if (!templateProperties.isEmpty()) {
Element properties = new Element(PROPERTIES, xmlns);
properties.addContent(templateProperties);
template.addContent(properties);
}
templateElements.add(template);
Element templates = new Element("originals", xmlns);
templates.addContent(templateElements);
processElements.add(templates);
}
private void processTemplateProperties(DocketData docketData, Namespace xmlns,
ArrayList<Element> templateProperties) {
for (Property prop : docketData.getTemplateProperties()) {
Element property = new Element(PROPERTY, xmlns);
property.setAttribute(PROPERTY_IDENTIFIER, prop.getTitle());
if (prop.getValue() != null) {
property.setAttribute(VALUE, replacer(prop.getValue()));
} else {
property.setAttribute(VALUE, "");
}
Element label = new Element(LABEL, xmlns);
label.setText(prop.getTitle());
property.addContent(label);
templateProperties.add(property);
if (prop.getTitle().equals("Signatur")) {
Element secondProperty = new Element(PROPERTY, xmlns);
secondProperty.setAttribute(PROPERTY_IDENTIFIER, prop.getTitle() + "Encoded");
if (prop.getValue() != null) {
secondProperty.setAttribute(VALUE, "vorl:" + replacer(prop.getValue()));
Element secondLabel = new Element(LABEL, xmlns);
secondLabel.setText(prop.getTitle());
secondProperty.addContent(secondLabel);
templateProperties.add(secondProperty);
}
}
}
}
private ArrayList<Element> processProcessInformation(DocketData docketData, Namespace xmlns) {
ArrayList<Element> processElements = new ArrayList<>();
Element processTitle = new Element("title", xmlns);
processTitle.setText(docketData.getProcessName());
processElements.add(processTitle);
Element project = new Element("project", xmlns);
project.setText(docketData.getProjectName());
processElements.add(project);
Element date = new Element("time", xmlns);
date.setAttribute("type", "creation date");
date.setText(String.valueOf(docketData.getCreationDate()));
processElements.add(date);
Element ruleset = new Element("ruleset", xmlns);
ruleset.setText(docketData.getRulesetName());
processElements.add(ruleset);
Element comment = new Element("comment", xmlns);
comment.setText(docketData.getComment());
processElements.add(comment);
List<Element> processProperties = prepareProperties(docketData.getProcessProperties(), xmlns);
if (!processProperties.isEmpty()) {
Element properties = new Element(PROPERTIES, xmlns);
properties.addContent(processProperties);
processElements.add(properties);
}
return processElements;
}
private void processNamespaceDeclaration(boolean addNamespace, Element processElm) {
if (addNamespace) {
Namespace xsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
processElm.addNamespaceDeclaration(xsi);
Attribute attSchema = new Attribute("schemaLocation", NAMESPACE + " XML-logfile.xsd",
xsi);
processElm.setAttribute(attSchema);
}
}
private List<Element> prepareProperties(List<Property> properties, Namespace xmlns) {
ArrayList<Element> preparedProperties = new ArrayList<>();
for (Property property : properties) {
Element propertyElement = new Element(PROPERTY, xmlns);
propertyElement.setAttribute(PROPERTY_IDENTIFIER, property.getTitle());
if (property.getValue() != null) {
propertyElement.setAttribute(VALUE, replacer(property.getValue()));
} else {
propertyElement.setAttribute(VALUE, "");
}
Element label = new Element(LABEL, xmlns);
label.setText(property.getTitle());
propertyElement.addContent(label);
preparedProperties.add(propertyElement);
}
return preparedProperties;
}
private List<Element> createMetadataElements(Namespace xmlns, DocketData docketData) {
List<Element> metadataElements = new ArrayList<>();
try {
HashMap<String, String> names = getNamespacesFromConfig();
Namespace[] namespaces = new Namespace[names.size()];
int index = 0;
for (var entries = names.entrySet().iterator(); entries.hasNext(); index++) {
Entry<String, String> entry = entries.next();
namespaces[index] = Namespace.getNamespace(entry.getKey(), entry.getValue());
}
prepareMetadataElements(metadataElements, false, docketData, namespaces, xmlns);
if (Objects.nonNull(docketData.getParent())) {
prepareMetadataElements(metadataElements, true, docketData.getParent(), namespaces, xmlns);
}
} catch (IOException | JDOMException | IllegalArgumentException e) {
logger.error(e.getMessage(), e);
}
return metadataElements;
}
private HashMap<String, String> getNamespacesFromConfig() {
return getXmlPathFromConfig("namespace");
}
private HashMap<String, String> getXmlPathFromConfig(String xmlPath) {
HashMap<String, String> fields = new HashMap<>();
try {
File file = new File(KitodoConfig.getKitodoConfigDirectory() + "kitodo_exportXml.xml");
if (file.exists() && file.canRead()) {
XMLConfiguration config = new XMLConfiguration(file);
config.setListDelimiter('&');
config.setReloadingStrategy(new FileChangedReloadingStrategy());
int count = config.getMaxIndex(xmlPath);
for (int i = 0; i <= count; i++) {
String name = config.getString(xmlPath + "(" + i + ")[@name]");
String value = config.getString(xmlPath + "(" + i + ")[@value]");
fields.put(name, value);
}
}
} catch (ConfigurationException | RuntimeException e) {
logger.debug(e.getMessage(), e);
fields = new HashMap<>();
}
return fields;
}
private void prepareMetadataElements(List<Element> metadataElements, boolean useAnchor, DocketData docketData,
Namespace[] namespaces, Namespace xmlns)
throws IOException, JDOMException {
HashMap<String, String> fields = getMetsFieldsFromConfig(useAnchor);
try (InputStream in = docketData.metadataFile().toURL().openStream()) {
Document metsDoc = new SAXBuilder().build(in);
prepareMetadataElements(metadataElements, fields, metsDoc, namespaces, xmlns);
}
}
private void prepareMetadataElements(List<Element> metadataElements, Map<String, String> fields, Document document,
Namespace[] namespaces, Namespace xmlns) {
for (Map.Entry<String, String> entry : fields.entrySet()) {
String key = entry.getKey();
List<Object> metsValues = getMetsValues(entry.getValue(), document, namespaces);
for (Object object : metsValues) {
boolean isElement = Filters.element().matches(object);
if (isElement || Filters.attribute().matches(object)) {
Element ele = new Element(PROPERTY, xmlns);
ele.setAttribute("name", key);
ele.addContent(isElement
? ((Element) object).getTextTrim()
: ((Attribute) object).getValue());
metadataElements.add(ele);
}
}
}
}
private HashMap<String, String> getMetsFieldsFromConfig(boolean useAnchor) {
String xmlpath = "mets." + PROPERTY;
if (useAnchor) {
xmlpath = "anchor." + PROPERTY;
}
HashMap<String, String> fields = getXmlPathFromConfig(xmlpath);
return fields;
}
/**
* Get METS values.
*
* @param expr
* String
* @param element
* Object
* @param namespaces
* HashMap
* @return list of elements
*/
private List<Object> getMetsValues(String expr, Document document, Namespace[] namespaces) {
XPathExpression<Object> xpath = XPathFactory.instance().compile(expr.trim().replace("\n", ""),
Filters.fpassthrough(), Collections.emptyMap(), namespaces);
return xpath.evaluate(document);
}
private String replacer(String in) {
in = in.replace("°", "?");
in = in.replace("^", "?");
in = in.replace("|", "?");
in = in.replace(">", "?");
in = in.replace("<", "?");
return in;
}
}