validator/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java
2025-04-24 12:54:14 +02:00

230 lines
9.9 KiB
Java

/*
* Copyright 2017-2022 Koordinierungsstelle für IT-Standards (KoSIT)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.kosit.validationtool.config;
import de.kosit.validationtool.api.Check;
import de.kosit.validationtool.api.Configuration;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.ConversionService;
import de.kosit.validationtool.impl.ResolvingMode;
import de.kosit.validationtool.impl.Scenario;
import de.kosit.validationtool.impl.SchemaProvider;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.DocumentParseAction;
import de.kosit.validationtool.impl.xml.RelativeUriResolver;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ResourceType;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import de.kosit.validationtool.model.scenarios.Scenarios;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmNodeKind;
import javax.xml.validation.Schema;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.startsWith;
/**
* Configuration class that loads necessary {@link Check} configuration from an existing scenario.xml specification.
* This is the recommended option when an official configuration exists as is the case with 'xrechnung'.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
@Slf4j
public class ConfigurationLoader {
private static final String SUPPORTED_MAJOR_VERSION = "1";
private static final String SUPPORTED_MAJOR_VERSION_SCHEMA = "http://www.xoev.de/de/validator/framework/1/scenarios";
protected final Map<String, Object> parameters = new HashMap<>();
/**
* URL, die auf die scenario.xml Datei zeigt.
*/
@Getter(AccessLevel.PACKAGE)
private final URI scenarioDefinition;
/**
* Root-Ordner mit den von den einzelnen Szenarien benötigten Dateien
*/
private final URI scenarioRepository;
protected ResolvingMode resolvingMode = ResolvingMode.STRICT_RELATIVE;
protected ResolvingConfigurationStrategy resolvingConfigurationStrategy;
private static void checkVersion(final URI scenarioDefinition, final Processor processor) {
try {
final Result<XdmNode, XMLSyntaxError> result = new DocumentParseAction(processor)
.parseDocument(InputFactory.read(scenarioDefinition.toURL()));
if (result.isValid() && !isSupportedDocument(result.getObject())) {
throw new IllegalStateException(String.format(
"Specified scenario configuration %s is not supported.%nThis version only supports definitions of '%s'",
scenarioDefinition, SUPPORTED_MAJOR_VERSION_SCHEMA));
}
} catch (final MalformedURLException e) {
throw new IllegalStateException("Error reading definition file");
}
}
private static XdmNode findRoot(final XdmNode doc) {
for (final XdmNode node : doc.children()) {
if (node.getNodeKind() == XdmNodeKind.ELEMENT) {
return node;
}
}
throw new IllegalArgumentException("Kein root element gefunden");
}
private static boolean isSupportedDocument(final XdmNode doc) {
final XdmNode root = findRoot(doc);
final String frameworkVersion = root.getAttributeValue(new QName("frameworkVersion"));
return startsWith(frameworkVersion, SUPPORTED_MAJOR_VERSION)
&& root.getNodeName().getNamespace().equals(SUPPORTED_MAJOR_VERSION_SCHEMA);
}
private static Scenario createFallback(final Scenarios scenarios, final ContentRepository repository) {
final ResourceType noScenarioResource = scenarios.getNoScenarioReport().getResource();
return new FallbackBuilder().source(noScenarioResource.getLocation()).name(noScenarioResource.getName()).build(repository)
.getObject();
}
private static List<Scenario> initializeScenarios(final Scenarios def, final ContentRepository contentRepository) {
return def.getScenario().stream().map(s -> initialize(s, contentRepository)).collect(Collectors.toList());
}
private static Scenario initialize(final ScenarioType def, final ContentRepository repository) {
final Scenario s = new Scenario(def);
s.setMatchExecutable(repository.createMatchExecutable(def));
s.setSchema(repository.createSchema(def));
s.setSchematronValidations(repository.createSchematronTransformations(def));
if (def.getCreateReport() != null) {
s.setReportTransformation(repository.createReportTransformation(def));
} else {
log.warn("No report configured. Will provide an internal format as report!");
s.setReportTransformation(repository.createIdentityTransformation());
}
s.setFactory(repository.getResolvingConfigurationStrategy());
s.setUriResolver(repository.getResolver());
s.setUnparsedTextURIResolver(repository.getUnparsedTextURIResolver());
if (def.getAcceptMatch() != null) {
s.setAcceptExecutable(repository.createAcceptExecutable(def));
}
return s;
}
URI getScenarioRepository() {
if (this.scenarioRepository == null) {
log.info("Creating default scenario repository (alongside scenario definition)");
return RelativeUriResolver.resolve(URI.create("."), this.scenarioDefinition);
}
return this.scenarioRepository;
}
public Configuration build(final Processor processor) {
final ResolvingConfigurationStrategy resolving = getResolvingConfigurationStrategy();
final ContentRepository contentRepository = new ContentRepository(processor, resolving, getScenarioRepository());
final Scenarios def = loadScenarios(SchemaProvider.getScenarioSchema(), processor);
final List<Scenario> scenarios = initializeScenarios(def, contentRepository);
final Scenario fallbackScenario = createFallback(def, contentRepository);
final DefaultConfiguration configuration = new DefaultConfiguration(scenarios, fallbackScenario);
configuration.setAdditionalParameters(this.parameters);
configuration.setAuthor(def.getAuthor());
configuration.setDate(def.getDate().toString());
configuration.setName(def.getName());
configuration.setContentRepository(contentRepository);
configuration.getAdditionalParameters().put(Keys.SCENARIOS_FILE, this.scenarioDefinition);
configuration.getAdditionalParameters().put(Keys.SCENARIO_DEFINITION, def);
return configuration;
}
private ResolvingConfigurationStrategy getResolvingConfigurationStrategy() {
if (this.resolvingConfigurationStrategy != null) {
log.info("Custom resolving strategy supplied. Please take care of xml security!");
return this.resolvingConfigurationStrategy;
}
log.info("Using resolving strategy {}", this.resolvingMode);
return this.resolvingMode.getStrategy();
}
private Scenarios loadScenarios(final Schema scenarioSchema, final Processor processor) {
checkVersion(this.scenarioDefinition, processor);
log.info("Loading scenarios from {}", this.scenarioDefinition);
final CollectingErrorEventHandler handler = new CollectingErrorEventHandler();
final ConversionService conversionService = new ConversionService();
final Scenarios scenarios = conversionService.readXml(this.scenarioDefinition, Scenarios.class, scenarioSchema, handler);
if (!handler.hasErrors()) {
log.info("Loading scenario content from {}", this.getScenarioRepository());
} else {
throw new IllegalStateException(
String.format("Can not load scenarios from %s due to %s", getScenarioDefinition(), handler.getErrorDescription()));
}
return scenarios;
}
/**
* Sets actual {@link ResolvingMode}, when the validator needs to resolve stuff on startup.
*
* @param mode the resolving mode
* @return this
*/
public ConfigurationLoader setResolvingMode(final ResolvingMode mode) {
if (this.resolvingConfigurationStrategy != null) {
log.warn("Ignoring resolving mode configuration since a custom strategy is already defined");
}
this.resolvingMode = mode;
return this;
}
public ConfigurationLoader setResolvingStrategy(final ResolvingConfigurationStrategy strategy) {
this.resolvingConfigurationStrategy = strategy;
return this;
}
/**
* Add a parameter to the configuration.
*
* @param name the name of the parameter
* @param value the parameter value object
* @return this
*/
@SuppressWarnings("unused")
public ConfigurationLoader addParameter(final String name, final Object value) {
this.parameters.put(name, value);
return this;
}
}