Coverage Summary for Class: RulesetManagement (org.kitodo.dataeditor.ruleset)
Class |
Class, %
|
Method, %
|
Line, %
|
RulesetManagement |
100%
(1/1)
|
83,3%
(15/18)
|
70,1%
(68/97)
|
/*
* (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.dataeditor.ruleset;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale.LanguageRange;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalDivision;
import org.kitodo.api.dataeditor.rulesetmanagement.FunctionalMetadata;
import org.kitodo.api.dataeditor.rulesetmanagement.RulesetManagementInterface;
import org.kitodo.api.dataeditor.rulesetmanagement.StructuralElementViewInterface;
import org.kitodo.dataeditor.ruleset.xml.AcquisitionStage;
import org.kitodo.dataeditor.ruleset.xml.Division;
import org.kitodo.dataeditor.ruleset.xml.Key;
import org.kitodo.dataeditor.ruleset.xml.Namespace;
import org.kitodo.dataeditor.ruleset.xml.Ruleset;
import org.kitodo.dataeditor.ruleset.xml.Setting;
import org.kitodo.utils.JAXBContextCache;
/**
* This class provides the functionality of the rule set.
*/
public class RulesetManagement implements RulesetManagementInterface {
/**
* A logger can be used to keep a log.
*/
private static final Logger logger = LogManager.getLogger(RulesetManagement.class);
/**
* The ruleset.
*/
private Ruleset ruleset;
/**
* Returns the acquisition levels defined in this rule set. This function
* was not parallelized to repeatedly serve JSF in the same order when the
* function is called repeatedly.
*
* @return all acquisition levels showing up
*/
@Override
public Collection<String> getAcquisitionStages() {
List<AcquisitionStage> acquisitionStages = ruleset.getAcquisitionStages();
List<String> acquisitionStageNames = new ArrayList<>(acquisitionStages.size());
for (AcquisitionStage acquisitionStage : acquisitionStages) {
acquisitionStageNames.add(acquisitionStage.getName());
}
return acquisitionStageNames;
}
@Override
public List<String> getFunctionalKeys(FunctionalMetadata functionalMetadata) {
return getIdsOfKeysForSpecialField(ruleset.getKeys(), functionalMetadata);
}
@Override
public List<String> getFunctionalDivisions(FunctionalDivision functionalDivision) {
return getIdsOfDivisionsForSpecialField(ruleset.getDivisions(), functionalDivision);
}
@Override
public Collection<String> getDivisionsWithNoWorkflow() {
Collection<DivisionDeclaration> divisionDeclarations = ruleset.getDivisionDeclarations(true, true);
List<Division> divisions = divisionDeclarations.stream().map(DivisionDeclaration::getDivision)
.collect(Collectors.toList());
return getDivionsWithNoWorkflow(divisions);
}
private Collection<String> getDivionsWithNoWorkflow(List<Division> divisions) {
ArrayList<String> divionsWithNoWorkflow = new ArrayList<>();
for (Division division : divisions) {
if (!division.isWithWorkflow()) {
divionsWithNoWorkflow.add(division.getId());
}
}
return divionsWithNoWorkflow;
}
private List<String> getIdsOfDivisionsForSpecialField(List<Division> divisions,
FunctionalDivision functionalDivision) {
ArrayList<String> idsOfDivisionsForSpecialField = new ArrayList<>();
for (Division division : divisions) {
if (Objects.isNull(division.getUse())) {
continue;
}
Set<FunctionalDivision> uses = FunctionalDivision.valuesOf(division.getUse());
if (uses.contains(functionalDivision)) {
idsOfDivisionsForSpecialField.add(division.getId());
}
}
return idsOfDivisionsForSpecialField;
}
private List<String> getIdsOfKeysForSpecialField(List<Key> keys, FunctionalMetadata functionalMetadata) {
ArrayList<String> idsOfKeysForSpecialField = new ArrayList<>(1);
for (Key key : keys) {
if (key.getKeys().isEmpty()) {
if (Objects.isNull(key.getUse())) {
continue;
}
Set<FunctionalMetadata> uses = FunctionalMetadata.valuesOf(key.getUse());
if (uses.contains(functionalMetadata)) {
idsOfKeysForSpecialField.add(key.getId());
}
} else {
List<String> idsOfKeysOfKey = getIdsOfKeysForSpecialField(key.getKeys(), functionalMetadata);
for (String idOfKeyOfKey : idsOfKeysOfKey) {
idsOfKeysForSpecialField.add(key.getId() + '@' + idOfKeyOfKey);
}
}
}
return idsOfKeysForSpecialField;
}
/**
* Returns a translated list of divisions available in the ruleset. The map
* maps from ID to label.
*
* @return the list of divisions
*/
@Override
public Map<String, String> getStructuralElements(List<LanguageRange> priorityList) {
return ruleset.getDivisions(priorityList, false, true);
}
/**
* Opens a view on a division of the rule set.
*
* @param divisionId
* the division in view
* @param acquisitionStage
* the current acquisition level
* @param priorityList
* the wish list of the user regarding its preferred human
* languages
* @return a view on a division
*/
@Override
public StructuralElementViewInterface getStructuralElementView(String divisionId, String acquisitionStage,
List<LanguageRange> priorityList) {
Optional<Division> division = ruleset.getDivision(divisionId);
DivisionDeclaration divisionDeclaration = division.isPresent() ? new DivisionDeclaration(ruleset, division.get())
: new DivisionDeclaration(ruleset, divisionId);
return new DivisionView(ruleset, divisionDeclaration, acquisitionStage, priorityList);
}
/**
* Opens a views on a metadata of the ruleset.
*
* @param keyId
* the id of the metadata
* @param acquisitionStage
* the current acquisition level
* @param priorityList
* the list of display languages preferred by the user
* @return a view on a metadata
*/
@Override
public NestedKeyView<KeyDeclaration> getMetadataView(String keyId, String acquisitionStage, List<LanguageRange> priorityList) {
Optional<Key> key = ruleset.getKey(keyId);
KeyDeclaration keyDeclaration = key.isPresent() ? new KeyDeclaration(ruleset, key.get()) : new KeyDeclaration(ruleset, keyId);
Rule rule = ruleset.getRuleForKey(keyId);
return new NestedKeyView<>(ruleset, keyDeclaration, rule, ruleset.getSettings(acquisitionStage), priorityList);
}
/**
* Returns the most appropriate label for a key, if there is one.
*
* @param key
* key whose label should be returned
* @param priorityList
* weighted list of user-preferred display languages. Return
* value of the function {@link LanguageRange#parse(String)}.
* @return the best-matching label, if any
*/
@Override
public Optional<String> getTranslationForKey(String key, List<LanguageRange> priorityList) {
Optional<Key> optionalKey = ruleset.getKey(key);
if (optionalKey.isPresent()) {
KeyDeclaration keyDeclaration = new KeyDeclaration(ruleset, optionalKey.get());
String label = keyDeclaration.getLabel(priorityList);
return Optional.of(label);
} else {
return Optional.empty();
}
}
/**
* Loads a ruleset from a file.
*
* @param rulesetFile
* file to load
* @throws IOException
* if something goes wrong when reading
*/
@Override
public void load(File rulesetFile) throws IOException {
this.ruleset = read(rulesetFile);
initializeNamespaces(ruleset.getKeys(), rulesetFile.getParentFile());
}
/**
* Initializes the elements of namespaces if there is a corresponding file.
*
* @param keys
* the keys of the rule set (are processed recursively)
* @param home
* the ruleset directory
* @throws IOException
* if I/O fails
*/
private void initializeNamespaces(List<Key> keys, File home) throws IOException {
for (Key key : keys) {
Optional<String> optionalNamespace = key.getNamespace();
if (optionalNamespace.isPresent()) {
String namespaceURI = optionalNamespace.get();
File file = new File(home, namespaceURI.replaceFirst("^.*?/([^/]*?)[#/]?$", "$1").concat(".xml"));
if (file.isFile()) {
try {
Namespace namespace = read(Namespace.class, file);
if (namespace.isAbout(namespaceURI)) {
key.setOptions(namespace.getOptions());
} else {
logger.debug(
"The file {} for the namespace {} declares an inappropriate namespace. (Check about.)",
file, namespaceURI);
}
} catch (IOException e) {
logger.debug("The file {} for the namespace {} cannot be parsed: {}", file, namespaceURI,
e.getMessage());
}
} else {
logger.debug("The file {} for the namespace {} was not found or is unreadable.", file,
namespaceURI);
}
}
// is applied recursively to the sub-elements
initializeNamespaces(key.getKeys(), home);
}
}
private static Ruleset read(File rulesetFile) throws IOException {
Ruleset result = new Ruleset();
Ruleset base = read(Ruleset.class, rulesetFile);
for (String include : base.getIncludes()) {
File includedFile = new File(rulesetFile.getParentFile(), include);
Ruleset included = read(Ruleset.class, includedFile);
result.addAll(included);
}
result.addAll(base);
return result;
}
/**
* Reads an object from a file. For this purpose, a marshal eliminator of
* Java XML bindings is created, which eliminates the class of marshals and
* creates it as a Java object. As an error, an I/O exception is thrown out
* directly. Other marshal eliminator exceptions are packed in I/O
* exceptions, so the interface is independent of the parser used.
*
* @param objectClass
* class of object to read
* @param inputFile
* file to read from
* @return the read object
* @throws IOException
* if I/O fails
*/
@SuppressWarnings("unchecked")
private static <T> T read(Class<T> objectClass, File inputFile) throws IOException {
try {
return JAXBContextCache.getInstance().getUnmarshalled(objectClass, inputFile);
} catch (JAXBException e) {
/*
* If the parser ran on an IOException, we can throw it out
* directly, because that allows the method signature.
*/
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
/*
* Conversely, parser exceptions must be wrapped in an
* IOException because the method signature does not allow the
* parser exceptions.
*/
throw new IOException(e.getMessage(), e);
}
}
}
@Override
public boolean isAlwaysShowingForKey(String keyId) {
Optional<Setting> optionalSetting = ruleset.getSettings().parallelStream()
.filter(setting -> setting.getKey().equals(keyId)).findAny();
if (optionalSetting.isPresent()) {
return optionalSetting.get().isAlwaysShowing();
}
return false;
}
}