Initial Implementation

This commit is contained in:
apenski 2017-10-26 09:50:58 +02:00
parent 2950785e25
commit beeb104007
98 changed files with 29308 additions and 0 deletions

View file

@ -0,0 +1,37 @@
<!--
~ Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
~ one or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. KoSIT licenses this file
~ to you 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.
-->
<assembly>
<id>standalone</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
</fileSet>
</fileSets>
</assembly>

View file

@ -0,0 +1,53 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.api;
import java.util.List;
import java.util.stream.Collectors;
import org.w3c.dom.Document;
/**
* Zentrale Schnittstellendefinition für das Prüf-Tool.
*
* @author Andreas Penski
*/
public interface Check {
/**
* Führt die konfigurierte Prüfung für die übergebene Resource aus.
*
* @param input die Resource / XML-Datei, die geprüft werden soll.
* @return ein Ergebnis-{@link Document}
*/
Document check(Input input);
/**
* Führt eine Prüfung im Batch-Mode durch. Die Default-Implementierung führt die Prüfung sequentiell aus.
*
* @param input die Eingabe
* @return Liste mit Ergebnis-Dokumenten
*/
default List<Document> check(List<Input> input) {
return input.stream().map(i -> check(i)).collect(Collectors.toList());
}
}

View file

@ -0,0 +1,68 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.api;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.net.URI;
/**
* Zentrale Konfigration einer Prüf-Instanz.
*
* @author Andreas Penski
*/
@Getter
@Setter
@Slf4j
@RequiredArgsConstructor
public class CheckConfiguration {
/**
* URL, die auf die scenerio.xml Datei zeigt.
*/
private final URI scenarioDefinition;
/**
* Root-Ordner mit den von den einzelnen Szenarien benötigten Dateien
*/
private URI scenarioRepository;
/**
* Liefert das Repository mit den Artefakten der einzelnen Szenarien.
*
* @return uri die durch entsprechende resolver aufgelöst werden kann
*/
public URI getScenarioRepository() {
if (scenarioRepository == null) {
scenarioRepository = createDefaultRepository();
}
return scenarioRepository;
}
private URI createDefaultRepository() {
log.info("Creating default scenario repository (alongside scenario definition)");
return scenarioDefinition.resolve(".");
}
}

View file

@ -0,0 +1,45 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.api;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Eine Datei in eingelesener Form.
*
* @author apenski
*/
@Getter
@RequiredArgsConstructor (access = AccessLevel.PACKAGE)
@AllArgsConstructor (access = AccessLevel.PACKAGE)
public class Input {
private final byte[] content;
private final String name;
private byte[] hashCode;
private String digestAlgorithm;
}

View file

@ -0,0 +1,244 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.api;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/**
* Service zum Einlesen des Test-Objekts in den Speicher. Beim Einlesen wird gleichzeitig eine Prüfsumme ermittelt und
* mit dem Ergebnis mitgeführt.
*
* @author Andreas Penski
*/
@Slf4j
public class InputFactory {
static final String DEFAULT_ALGORITH = "SHA-256";
private static final int EOF = -1;
private static final int DEFAULT_BUFFER_SIZE = 4096;
public static final String MESSAGE_OPEN_STREAM_ERROR = "Can not open stream from";
@Getter
private final String algorithm;
InputFactory() {
this(null);
}
InputFactory(String specifiedAlgorithm) {
this.algorithm = isNotEmpty(specifiedAlgorithm) ? specifiedAlgorithm : DEFAULT_ALGORITH;
createDigest();
}
/**
* Liest einen Prüfling von dem übergebenen Pfad. Es wird der Default-Prüfsummenalgorithmus zur Ermittlung der Prüfsumme
* genutzt.
*
* @param path der Prüflings
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(Path path) {
return read(path, DEFAULT_ALGORITH);
}
/**
* Liest einen Prüfling von der übergebenen URL. Es wird ein definierter Algorithmis zur Ermittlung der Prüfsumme
* genutzt.
*
* @param path der Prüflings
* @param digestAlgorithm der Prüfsummenalgorithmus
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(Path path, String digestAlgorithm) {
checkNull(path);
try ( InputStream stream = Files.newInputStream(path) ) {
return read(stream, path.toString(), digestAlgorithm);
} catch (IOException e) {
throw new IllegalArgumentException(MESSAGE_OPEN_STREAM_ERROR + path, e);
}
}
/**
* Liest einen Prüfling von der übergebenen Datei. Es wird der Default-Prüfsummenalgorithmus zur Ermittlung der
* Prüfsumme genutzt.
*
* @param file der Prüflings
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(File file) {
return read(file, DEFAULT_ALGORITH);
}
/**
* Liest einen Prüfling von der übergebenen URL. Es wird der Default-Prüfsummenalgorithmus zur Ermittlung der Prüfsumme
* genutzt.
*
* @param url URL des Prüflings
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(URL url) {
return read(url, DEFAULT_ALGORITH);
}
/**
* Liest einen Prüfling von der übergebenen URL. Es wird ein definierter Algorithmis zur Ermittlung der Prüfsumme
* genutzt.
*
* @param url URL des Prüflings
* @param digestAlgorithm der Prüfsummenalgorithmus
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(URL url, String digestAlgorithm) {
checkNull(url);
try {
return read(url.openStream(), url.getFile(), digestAlgorithm);
} catch (IOException e) {
throw new IllegalArgumentException(MESSAGE_OPEN_STREAM_ERROR + url, e);
}
}
/**
* Liest einen Prüfling von der übergebenen URL. Es wird ein definierter Algorithmis zur Ermittlung der Prüfsumme
* genutzt.
*
* @param file der Prüflings
* @param digestAlgorithm der Prüfsummenalgorithmus
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(File file, String digestAlgorithm) {
checkNull(file);
try {
return read(file.toURI().toURL(), digestAlgorithm);
} catch (IOException e) {
throw new IllegalArgumentException(MESSAGE_OPEN_STREAM_ERROR + file, e);
}
}
/**
* Liest einen Prüfling von der übergebenen byte-Sequenz. Es wird ein definierter Algorithmis zur Ermittlung der
* Prüfsumme genutzt.
*
* @param input URL des Prüflings
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(byte[] input, String name) {
checkNull(input);
return read(input, name, DEFAULT_ALGORITH);
}
/**
* Liest einen Prüfling von der übergebenen byte-Sequenz. Es wird ein definierter Algorithmis zur Ermittlung der
* Prüfsumme genutzt.
*
* @param input URL des Prüflings
* @param digestAlgorithm der Prüfsummenalgorithmus
* @return ein Prüf-Eingabe-Objekt
*/
public static Input read(byte[] input, String name, String digestAlgorithm) {
checkNull(input);
return read(new ByteArrayInputStream(input), name, digestAlgorithm);
}
private static void checkNull(Object input) {
if (input == null) {
throw new IllegalArgumentException("Input can not be null");
}
}
/**
* Liest einen Prüfling vom übergebenen {@link InputStream}.
*
* @param inputStream der {@link InputStream}
* @param name der Name/Bezeichner des Prüflings
* @return einen Prüfling in eingelesener Form
*/
public static Input read(InputStream inputStream, String name) {
return read(inputStream, name, DEFAULT_ALGORITH);
}
/**
* Liest einen Prüfling vom übergebenen {@link InputStream}.
*
* @param inputStream der {@link InputStream}
* @param name der Name/Bezeichner des Prüflings
* @param digestAlgorithm der Prüfsummenalgorithmus
* @return einen Prüfling in eingelesener Form
*/
public static Input read(InputStream inputStream, String name, String digestAlgorithm) {
return new InputFactory(digestAlgorithm).readStream(inputStream, name);
}
private Input readStream(InputStream inputStream, String name) {
if (StringUtils.isNotBlank(name)) {
log.debug("Generating hashcode for {} using {} algorithm", name, getAlgorithm());
MessageDigest digest = createDigest();
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
try ( BufferedInputStream bis = new BufferedInputStream(inputStream);
DigestInputStream dis = new DigestInputStream(bis, digest);
ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
// read the file and update the hash calculation
int n;
while (EOF != (n = dis.read(buffer))) {
out.write(buffer, 0, n);
}
// get the hash value as byte array
byte[] hash = digest.digest();
log.debug("Generated hashcode for {} is {}", name, DatatypeConverter.printHexBinary(hash));
out.flush();
return new Input(out.toByteArray(), name, hash, digest.getAlgorithm());
} catch (IOException e) {
throw new IllegalArgumentException(MESSAGE_OPEN_STREAM_ERROR + name, e);
}
} else {
throw new IllegalArgumentException("Must supply a valid name/identifier for the input");
}
}
private MessageDigest createDigest() {
try {
MessageDigest digest;
digest = MessageDigest.getInstance(getAlgorithm());
return digest;
} catch (NoSuchAlgorithmException e) {
// should not happen
throw new IllegalStateException(String.format("Specified method %s is not available", getAlgorithm()), e);
}
}
}

View file

@ -0,0 +1,132 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.cmd;
import de.kosit.validationtool.cmd.assertions.AssertionType;
import de.kosit.validationtool.cmd.assertions.Assertions;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.CheckAction;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.saxon.s9api.*;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import javax.xml.transform.dom.DOMSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Überprüft den Report mittels bereitgestellter Assertions. Diese {@link CheckAction} dient der Überprüfung der von der
* KoSIT bereitgestellten Prüfszenarien und den darin enthaltenen Artefakten.
*
* @author Andreas Penski
*/
@Slf4j
@RequiredArgsConstructor
public class CheckAssertionAction implements CheckAction {
private final Assertions assertions;
@Getter(AccessLevel.PRIVATE)
private final Processor processor;
private Map<String, List<AssertionType>> mappedAssertions;
private static boolean matches(String key, String name) {
return key.startsWith(name) || (name + ".xml").endsWith(key);
}
@Override
public void check(Bag results) {
log.info("Checking assertions for {}", results.getInput().getName());
final List<AssertionType> toCheck = findAssertions(results.getName());
final List<String> errors = new ArrayList<>();
if (toCheck != null && !toCheck.isEmpty()) {
final XdmNode node = loadDocument(results.getReport());
toCheck.forEach(a -> {
if (!check(node, a)) {
log.error("Assertion mismatch: {}", a.getValue());
errors.add(a.getValue());
}
});
if (errors.isEmpty()) {
log.info("{} assertions successfully verified for {}", toCheck.size(), results.getName());
} else {
log.warn("{} assertion of {} failed while checking {}", errors.size(), toCheck.size(), results.getName());
}
results.setAssertionResult(new Result<>(toCheck.size(), errors));
} else {
log.warn("Can not find assertions for {}", results.getName());
}
}
private List<AssertionType> findAssertions(String name) {
return getMapped().entrySet().stream().filter(e -> matches(e.getKey(), name)).map(Map.Entry::getValue).findFirst().orElse(null);
}
private XdmNode loadDocument(Document d) {
DocumentBuilder documentBuilder = getProcessor().newDocumentBuilder();
try {
return documentBuilder.build(new DOMSource(d));
} catch (SaxonApiException e) {
log.error("Can not load result document. Therefore can not run defined assertions", e);
}
return null;
}
private boolean check(XdmNode document, AssertionType assertion) {
try {
final XPathSelector selector = createSelector(assertion);
selector.setContextItem(document);
return selector.effectiveBooleanValue();
} catch (SaxonApiException e) {
log.error("Error evaluating assertion {} for {}", assertion.getTest(), assertion.getReportDoc(), e);
}
return false;
}
private XPathSelector createSelector(AssertionType assertion) throws SaxonApiException {
try {
final XPathCompiler compiler = getProcessor().newXPathCompiler();
assertions.getNamespace().forEach(ns -> compiler.declareNamespace(ns.getPrefix(), ns.getValue()));
return compiler.compile(assertion.getTest()).load();
} catch (SaxonApiException e) {
throw new IllegalStateException(String.format("Can not compile xpath match expression '%s'",
StringUtils.isNotBlank(assertion.getTest()) ? assertion.getTest() : "EMPTY EXPRESSION"), e);
}
}
private Map<String, List<AssertionType>> getMapped() {
if (mappedAssertions == null) {
mappedAssertions = new HashMap<>();
for (AssertionType assertionType : assertions.getAssertion()) {
List<AssertionType> list = mappedAssertions.computeIfAbsent(assertionType.getReportDoc(), k -> new ArrayList<>());
list.add(assertionType);
}
}
return mappedAssertions;
}
}

View file

@ -0,0 +1,290 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.cmd;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.cli.*;
import org.apache.commons.lang3.StringUtils;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.cmd.assertions.Assertions;
import de.kosit.validationtool.impl.ConversionService;
import de.kosit.validationtool.impl.ObjectFactory;
/**
* Commandline Version des Prüftools. Parsed die Kommandozeile und führt die konfigurierten Aktionen aus.
*
* @author Andreas Penski
*/
@Slf4j
public class CommandLineApplication {
private static final Option HELP = Option.builder("?").longOpt("help").argName("Help").desc("Displays this help").build();
private static final Option SCENARIOS = Option.builder("s").required().longOpt("scenarios").hasArg()
.desc("Location of scenarios.xml e.g.").build();
private static final Option REPOSITORY = Option.builder("r").longOpt("repository").hasArg()
.desc("Directory containing scenario content").build();
private static final Option PRINT = Option.builder("p").longOpt("print").desc("Prints the check result to stdout").build();
private static final Option OUTPUT = Option.builder("o").longOpt("output-directory")
.desc("Defines the out directory for results. Defaults to cwd").hasArg().build();
private static final Option EXTRACT_HTML = Option.builder("h").longOpt("html")
.desc("Extract and save any html content within result as a separate file ").build();
private static final Option DEBUG = Option.builder("d").longOpt("debug").desc("Prints some more debug information").build();
private static final Option CHECK_ASSERTIONS = Option.builder("c").longOpt("check-assertions").hasArg()
.desc("Check the result using defined assertions").argName("assertions-file").build();
private CommandLineApplication() {
// main class -> hide constructor
}
/**
* Main-Funktion für die Kommandozeilen-Applikation.
*
* @param args die Eingabe-Argumente
*/
public static void main(String[] args) {
final int resultStatus = mainProgram(args);
System.exit(resultStatus);
}
/**
* Hauptprogramm für die Kommandozeilen-Applikation.
*
* @param args die Eingabe-Argumente
*/
static int mainProgram(String[] args) {
Options options = createOptions();
if (isHelpRequested(args)) {
printHelp(options);
} else {
try {
CommandLineParser parser = new DefaultParser();
final CommandLine cmd = parser.parse(options, args);
if (cmd.getArgList().isEmpty()) {
printHelp(createOptions());
} else {
return processActions(cmd);
}
} catch (ParseException e) {
log.error("Error processing command line arguments: " + e.getMessage());
printHelp(options);
}
}
return 0;
}
private static boolean isHelpRequested(String[] args) {
Options helpOptions = createHelpOptions();
try {
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(helpOptions, args, true);
if (cmd.hasOption(HELP.getOpt()) || args.length == 0) {
return true;
}
} catch (ParseException e) {
// we can ignore that, we just look for the help parameters
}
return false;
}
private static int processActions(CommandLine cmd) {
try {
long start = System.currentTimeMillis();
CheckConfiguration d = new CheckConfiguration(determineDefinition(cmd));
d.setScenarioRepository(determineRepository(cmd));
InternalCheck check = new InternalCheck(d);
Path outputDirectory = determineOutputDirectory(cmd);
if (cmd.hasOption(EXTRACT_HTML.getOpt())) {
check.getCheckSteps().add(new ExtractHtmlContentAction(check.getContentRepository(), outputDirectory));
}
check.getCheckSteps().add(new SerializeReportAction(outputDirectory));
if (cmd.hasOption(PRINT.getOpt())) {
check.getCheckSteps().add(new PrintReportAction());
}
if (cmd.hasOption(CHECK_ASSERTIONS.getOpt())) {
Assertions assertions = loadAssertions(cmd.getOptionValue(CHECK_ASSERTIONS.getOpt()));
check.getCheckSteps().add(new CheckAssertionAction(assertions, ObjectFactory.createProcessor()));
}
log.info("Setup completed in {}ms\n", System.currentTimeMillis() - start);
final Collection<Path> targets = determineTestTargets(cmd);
start = System.currentTimeMillis();
final List<Input> input = targets.stream().map(InputFactory::read).collect(Collectors.toList());
boolean result = check.checkInput(input);
log.info("Processing {} object(s) completed in {}ms", input.size(), System.currentTimeMillis() - start);
return result ? 0 : 1;
} catch (Exception e) {
if (cmd.hasOption(DEBUG.getOpt())) {
log.error(e.getMessage(), e);
} else {
log.error(e.getMessage());
}
return -1;
}
}
private static Assertions loadAssertions(String optionValue) {
Path p = Paths.get(optionValue);
Assertions a = null;
if (Files.exists(p)) {
ConversionService c = new ConversionService();
c.initialize(de.kosit.validationtool.cmd.assertions.ObjectFactory.class.getPackage());
a = c.readXml(p.toUri(), Assertions.class);
}
return a;
}
private static Path determineOutputDirectory(CommandLine cmd) {
final String value = cmd.getOptionValue(OUTPUT.getOpt());
Path fir;
if (StringUtils.isNotBlank(value)) {
fir = Paths.get(value);
if ((!Files.exists(fir) && !fir.toFile().mkdirs()) || !Files.isDirectory(fir)) {
throw new IllegalStateException(String.format("Invalid target directory %s specified", value));
}
} else {
fir = Paths.get(""/* cwd */);
}
return fir;
}
private static Collection<Path> determineTestTargets(CommandLine cmd) {
Collection<Path> targets = new ArrayList<>();
if (!cmd.getArgList().isEmpty()) {
cmd.getArgList().forEach(e -> targets.addAll(determineTestTarget(e)));
}
if (targets.isEmpty()) {
throw new IllegalStateException("No test targets found. Nothing to check. Will quit now!");
}
return targets;
}
private static Collection<Path> determineTestTarget(String s) {
Path d = Paths.get(s);
if (Files.isDirectory(d)) {
return listDirectoryTargets(d);
} else if (Files.exists(d)) {
return Collections.singleton(d);
}
log.warn("The specified test target {} does not exist. Will be ignored", s);
return Collections.emptyList();
}
private static Collection<Path> listDirectoryTargets(Path d) {
try {
return Files.list(d).filter(path -> path.toString().endsWith(".xml")).collect(Collectors.toList());
} catch (IOException e) {
throw new IllegalStateException("IOException while liste directory content. Can not determine test targets.", e);
}
}
private static URI determineRepository(CommandLine cmd) throws MalformedURLException {
if (checkOptionWithValue(REPOSITORY, cmd)) {
Path d = Paths.get(cmd.getOptionValue(REPOSITORY.getOpt()));
if (Files.isDirectory(d)) {
return d.toUri();
} else {
throw new IllegalArgumentException(
String.format("Not a valid path for scenario definition specified: '%s'", d.toAbsolutePath()));
}
}
return null;
}
private static URI determineDefinition(CommandLine cmd) throws MalformedURLException {
checkOptionWithValue(SCENARIOS, cmd);
Path f = Paths.get(cmd.getOptionValue(SCENARIOS.getOpt()));
if (Files.isRegularFile(f)) {
return f.toAbsolutePath().toUri();
} else {
throw new IllegalArgumentException(
String.format("Not a valid path for scenario definition specified: '%s'", f.toAbsolutePath()));
}
}
private static boolean checkOptionWithValue(Option option, CommandLine cmd) {
String opt = option.getOpt();
if (cmd.hasOption(opt)) {
String value = cmd.getOptionValue(opt);
if (StringUtils.isNoneBlank(value)) {
return true;
} else {
throw new IllegalArgumentException(String.format("Option value required for Option '%s'", option.getLongOpt()));
}
} else if (option.isRequired()) {
throw new IllegalArgumentException(String.format("Option '%s' required ", option.getLongOpt()));
}
return false;
}
private static void printHelp(Options options) {
// automatically generate the help statement
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("check-tool -s <scenario-config-file> [OPTIONS] [FILE]... ", options, false);
}
private static Options createHelpOptions() {
Options options = new Options();
options.addOption(HELP);
return options;
}
private static Options createOptions() {
Options options = new Options();
options.addOption(HELP);
options.addOption(SCENARIOS);
options.addOption(REPOSITORY);
options.addOption(PRINT);
options.addOption(OUTPUT);
options.addOption(EXTRACT_HTML);
options.addOption(DEBUG);
options.addOption(CHECK_ASSERTIONS);
return options;
}
}

View file

@ -0,0 +1,98 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.cmd;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.dom.DOMSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.tasks.CheckAction;
import net.sf.saxon.s9api.*;
/**
* Extrahiert HTML-Dokumente aus dem Report und persistiert diese im konfigurierten Ausgabe-Verzeichnis.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
@Slf4j
public class ExtractHtmlContentAction implements CheckAction {
private static final QName NAME_ATTRIBUTE = new QName("data-report-type");
private final ContentRepository repository;
private final Path outputDirectory;
private XPathExecutable executable;
@Override
public void check(Bag results) {
try {
final XPathSelector selector = getSelector();
DocumentBuilder documentBuilder = repository.getProcessor().newDocumentBuilder();
final XdmNode xdmSource = documentBuilder.build(new DOMSource(results.getReport()));
selector.setContextItem(xdmSource);
selector.forEach(m -> print(results.getName(), m));
} catch (SaxonApiException e) {
throw new IllegalStateException("Can not extract html content", e);
}
}
private void print(String origName, XdmItem xdmItem) {
XdmNode node = (XdmNode) xdmItem;
final String name = origName + "-" + node.getAttributeValue(NAME_ATTRIBUTE);
final Path file = outputDirectory.resolve(name + ".html");
final Serializer serializer = repository.getProcessor().newSerializer(file.toFile());
try {
log.info("Writing report html '{}' to {}", name, file.toAbsolutePath());
serializer.serializeNode(node);
} catch (SaxonApiException e) {
log.info("Error extracting html content to {}", file.toAbsolutePath(), e);
}
}
private XPathSelector getSelector() {
if (executable == null) {
Map<String, String> ns = new HashMap<>();
ns.put("html", "http://www.w3.org/1999/xhtml");
executable = repository.createXPath("//html:html", ns);
}
return executable.load();
}
@Override
public boolean isSkipped(Bag results) {
if (results.getReport() == null) {
log.warn("Can not extract html content. No report document found");
return true;
}
return false;
}
}

View file

@ -0,0 +1,83 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.cmd;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.impl.DefaultCheck;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.CheckAction;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Simple Erweiterung der Klasse {@link DefaultCheck} um das Ergebnis der Assertion-Prüfung auszwerten und auszugeben.
* Diese Klasse stellt keine fachlicher Erweiterung des eigentlichen Prüfvorganges dar!
*
* @author Andreas Penski
*/
@Slf4j
class InternalCheck extends DefaultCheck {
/**
* Erzeugt eine neue Instanz mit der angegebenen Konfiguration.
*
* @param configuration die Konfiguration
*/
public InternalCheck(CheckConfiguration configuration) {
super(configuration);
}
/**
* Prüft die Prüflinge und gibt Informationen über etwaige Assertions aus.
*
* @param input die Prüflinge
* @return false wenn es Assertion-Fehler gibt, sonst true
*/
public boolean checkInput(List<Input> input) {
List<CheckAction.Bag> results = new ArrayList<>();
input.forEach(i -> {
CheckAction.Bag bag = new CheckAction.Bag(i, createReport());
runCheckInternal(bag);
results.add(bag);
});
return printAndEvaluate(results);
}
private boolean printAndEvaluate(List<CheckAction.Bag> results) {
final List<Result<Integer, String>> asserts = results.stream().filter(r -> r.getAssertionResult() != null)
.map(CheckAction.Bag::getAssertionResult).collect(Collectors.toList());
int checkAssertions = asserts.stream().mapToInt(e -> e.getObject()).sum();
int failedAssertions = asserts.stream().mapToInt(e -> e.getErrors().size()).sum();
if (failedAssertions > 0) {
log.error("Assertion check failed.\n\nAssertions run: {}, Assertions failed: {}\n", checkAssertions, failedAssertions);
} else if (checkAssertions > 0) {
log.info("Assertion check successful.\n\nAssertions run: {}, Assertions failed: {}\n", checkAssertions, failedAssertions);
}
return failedAssertions == 0;
}
}

View file

@ -0,0 +1,57 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.cmd;
import java.io.StringWriter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
/**
* Gibt das Ergebnis-Document auf std-out aus.
*
* @author Andreas Penski
*/
@Slf4j
public class PrintReportAction implements CheckAction {
@Override
public void check(Bag results) {
try {
Transformer transformer = ObjectFactory.createTransformer(true);
final StringWriter writer = new StringWriter();
Result output = new StreamResult(writer);
Source input = new DOMSource(results.getReport());
transformer.transform(input, output);
System.out.print(writer.toString());
} catch (TransformerException e) {
log.error("Error while printing result to stdout", e);
}
}
}

View file

@ -0,0 +1,68 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.cmd;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.nio.file.Path;
/**
* Schreibt das Prüfergebnis als XML-Dokument an eine definierte Stelle.
*
* @author Andreas Penski
*/
@Slf4j
@RequiredArgsConstructor
public class SerializeReportAction implements CheckAction {
private final Path outputDirectory;
@Override
public void check(Bag results) {
final Path file = outputDirectory.resolve(results.getName() + "-report.xml");
try {
log.info("Serializing result to {}", file.toAbsolutePath());
Transformer transformer = ObjectFactory.createTransformer(true);
Result output = new StreamResult(file.toFile());
Source input = new DOMSource(results.getReport());
transformer.transform(input, output);
} catch (TransformerException e) {
log.error("Can not serialize result report to {}", file.toAbsolutePath(), e);
}
}
@Override
public boolean isSkipped(Bag results) {
if (results.getReport() == null) {
log.warn("Can not serialize result report. No document found");
return true;
}
return false;
}
}

View file

@ -0,0 +1,118 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
* {@link LSResourceResolver} der objekte relativ zu einem Basis-Pfad aus dem Classpath der Anwendung laden kann.
*
* @author Andreas Penski
*/
@Slf4j
class ClassPathResourceResolver implements LSResourceResolver {
private final URI base;
/**
* Instantiiert einen neuen resolver mit angegebenen Basispfad
*
* @param basePath der Basispfad
*/
public ClassPathResourceResolver(String basePath) {
if (!StringUtils.startsWith(basePath, "/")) {
throw new IllegalArgumentException("Base path must start with a slash");
}
base = URI.create(basePath + (basePath.endsWith("/") == basePath.length() > 1 ? "" : "/"));
}
@Override
public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
final URL resource = ClassPathResourceResolver.class.getResource(base.resolve(systemId).toASCIIString());
if (resource != null) {
try {
InputStream in = resource.openStream();
final LSInputImpl input = new LSInputImpl(publicId, systemId, baseURI);
input.setByteStream(in);
return input;
} catch (IOException e) {
log.error("Error loading schema resource from {}", resource, e);
}
}
// not found
return null;
}
/**
* Simple {@link LSInput}-Implementierung, die einen Stream liefern kann
*/
@Getter
@Setter
@RequiredArgsConstructor
private static class LSInputImpl implements LSInput {
private Reader characterStream;
private InputStream byteStream;
private String systemId;
private String publicId;
private String baseURI;
private String encoding;
private boolean certifiedText;
private String stringData;
/**
* Instantiierung einer neue Instanz.
* @param publicId die publicId
* @param systemId die systemId
* @param baseURI die baseURI
*/
public LSInputImpl(String publicId, String systemId, String baseURI) {
this.publicId = publicId;
this.systemId = systemId;
this.baseURI = baseURI;
}
@Override
public boolean getCertifiedText() {
return certifiedText;
}
}
}

View file

@ -0,0 +1,168 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.StringJoiner;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import lombok.Getter;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.reportInput.XMLSyntaxErrorSeverity;
import net.sf.saxon.s9api.MessageListener;
import net.sf.saxon.s9api.XdmNode;
/**
* Sammelt Fehler-Ereignisinformation beim Schema-Validieren und weiteren XML-basierten Aktionen
*
* @author Andreas Penski
*/
@Getter
public class CollectingErrorEventHandler implements ValidationEventHandler, ErrorHandler, MessageListener, ErrorListener {
private static final int DEFAULT_ABORT_COUNT = 50;
private Collection<XMLSyntaxError> errors = new ArrayList<>();
private int stopProcessCount = DEFAULT_ABORT_COUNT;
private static XMLSyntaxError createError(XMLSyntaxErrorSeverity severity, String message) {
XMLSyntaxError e = new XMLSyntaxError();
e.setSeverity(severity);
e.setMessage(message);
return e;
}
private static XMLSyntaxError createError(XMLSyntaxErrorSeverity severity, SAXParseException exception) {
XMLSyntaxError e = createError(severity, exception.getMessage());
e.setRowNumber(exception.getLineNumber());
e.setColumnNumber(exception.getColumnNumber());
return e;
}
private static XMLSyntaxError createError(XMLSyntaxErrorSeverity severity, TransformerException exception) {
XMLSyntaxError e = createError(severity, exception.getMessage());
if (exception.getLocator() != null) {
e.setRowNumber(exception.getLocator().getLineNumber());
e.setColumnNumber(exception.getLocator().getColumnNumber());
}
return e;
}
private static XMLSyntaxErrorSeverity translateSeverity(int severity) {
switch (severity) {
case ValidationEvent.WARNING:
return XMLSyntaxErrorSeverity.SEVERITY_WARNING;
case ValidationEvent.ERROR:
return XMLSyntaxErrorSeverity.SEVERITY_ERROR;
case ValidationEvent.FATAL_ERROR:
return XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR;
default:
throw new IllegalArgumentException("Unknown severity level " + severity);
}
}
@Override
public boolean handleEvent(ValidationEvent event) {
XMLSyntaxError e = createError(translateSeverity(event.getSeverity()), event.getMessage());
e.setColumnNumber(event.getLocator().getColumnNumber());
e.setRowNumber(event.getLocator().getLineNumber());
errors.add(e);
return stopProcessCount != errors.size();
}
/**
* Zeigt an, ob Validierungsfehler vorhanden sind.
*
* @return true wenn mindestens ein Fehler vorhanden ist.
*/
public boolean hasErrors() {
return hasEvents() && errors.stream().anyMatch(e -> e.getSeverity() != XMLSyntaxErrorSeverity.SEVERITY_WARNING);
}
/**
* Zeigt an, ob es Validierungs-Ereignisse gab.
*
* @return true wenn mindestens ein Validierungsereignis aufgetreten ist
*/
public boolean hasEvents() {
return !errors.isEmpty();
}
@Override
public void warning(SAXParseException exception) throws SAXException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_WARNING, exception));
}
@Override
public void error(SAXParseException exception) throws SAXException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_ERROR, exception));
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR, exception));
}
@Override
public void message(XdmNode content, boolean terminate, SourceLocator locator) {
XMLSyntaxError e = new XMLSyntaxError();
if (locator != null) {
e.setColumnNumber(locator.getColumnNumber());
e.setRowNumber(locator.getLineNumber());
}
e.setMessage("Error procesing" + content.getStringValue());
e.setSeverity(terminate ? XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR : XMLSyntaxErrorSeverity.SEVERITY_WARNING);
}
@Override
public void warning(TransformerException exception) throws TransformerException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_WARNING, exception));
}
@Override
public void error(TransformerException exception) throws TransformerException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_ERROR, exception));
}
@Override
public void fatalError(TransformerException exception) throws TransformerException {
errors.add(createError(XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR, exception));
}
public String getErrorDescription() {
final StringJoiner joiner = new StringJoiner("\n");
errors.forEach(e -> joiner
.add(e.getSeverity().value() + " " + e.getMessage() + " At row " + e.getRowNumber() + " at pos " + e.getColumnNumber()));
return joiner.toString();
}
}

View file

@ -0,0 +1,175 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.SAXException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.saxon.s9api.*;
/**
* Repository für verschiedene XML Artefakte zur Vearbeitung der Prüfszenarien.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
@Slf4j
public class ContentRepository {
@Getter
private final Processor processor;
private final URI repository;
private Schema reportInputSchema;
private static Source resolve(URL resource) {
try {
return new StreamSource(resource.openStream(), resource.toURI().getRawPath());
} catch (IOException | URISyntaxException e) {
throw new IllegalStateException("Can not load schema for resource " + resource.getPath(), e);
}
}
private static Schema createSchema(Source[] schemaSources, LSResourceResolver resourceResolver) {
try {
SchemaFactory sf = ObjectFactory.createSchemaFactory();
sf.setResourceResolver(resourceResolver);
return sf.newSchema(schemaSources);
} catch (SAXException e) {
throw new IllegalArgumentException("Can not load schema from sources " + schemaSources[0].getSystemId(), e);
}
}
private static Schema createSchema(Source[] schemaSources) {
return createSchema(schemaSources, null);
}
/**
* Lädt ein XSL von der angegebenen URI
*
* @param uri die URI der XSL Definition
* @return ein XSLT Executable
*/
public XsltExecutable loadXsltScript(URI uri) {
log.info("Loading XSLT script from {}", uri);
final XsltCompiler xsltCompiler = getProcessor().newXsltCompiler();
final CollectingErrorEventHandler listener = new CollectingErrorEventHandler();
try {
xsltCompiler.setErrorListener(listener);
xsltCompiler.setURIResolver(new RelativeUriResolver(repository));
return xsltCompiler.compile(resolve(uri));
} catch (SaxonApiException e) {
listener.getErrors().forEach(event -> event.log(log));
throw new IllegalStateException("Can not compile xslt executable for uri " + uri, e);
} finally {
if (!listener.hasErrors() && listener.hasEvents()) {
log.warn("Received warnings while loading a xslt script {}", uri);
listener.getErrors().forEach(e -> e.log(log));
}
}
}
/**
* Erzeugt ein Schema-Objekt auf Basis der übergebenen URL.
*
* @param url die url
* @return das erzeugte Schema
*/
public Schema createSchema(URL url) {
log.info("Load schema from source {}", url.getPath());
return createSchema(new Source[] { resolve(url) });
}
/**
* Liefert das definiert Schema für die Szenario-Konfiguration
*
* @return Scenario-Schema
*/
public Schema getScenarioSchema() {
return createSchema(ContentRepository.class.getResource("/xsd/scenarios.xsd"));
}
/**
* Liefert das definierte Schema für die Validierung des [@link CreateReportInput}
*
* @return ReportInput-Schema
*/
public Schema getReportInputSchema() {
if (reportInputSchema == null) {
final Source source = resolve(ContentRepository.class.getResource("/xsd/createReportInput.xsd"));
reportInputSchema = createSchema(new Source[] { source }, new ClassPathResourceResolver("/xsd"));
}
return reportInputSchema;
}
/**
* Erzeugt ein Schema auf Basis der übegebenen URIs
*
* @param uris die uris in String-Repräsentation
* @return das Schema
*/
public Schema createSchema(Collection<String> uris) {
return createSchema(uris.stream().map(s -> resolve(URI.create(s))).toArray(Source[]::new));
}
private Source resolve(URI source) {
return new StreamSource(repository.resolve(source).toASCIIString());
}
/**
* Erzeugt einen [@link XPathExecutable} auf Basis der angegebenen Informationen.
*
* @param expression der XPATH-Ausdruck
* @param namespaces optionale Namespace-Mappings
* @return ein kompiliertes Executable
*/
public XPathExecutable createXPath(String expression, Map<String, String> namespaces) {
try {
final XPathCompiler compiler = getProcessor().newXPathCompiler();
if (namespaces != null) {
namespaces.entrySet().forEach(n -> compiler.declareNamespace(n.getKey(), n.getValue()));
}
return compiler.compile(expression);
} catch (SaxonApiException e) {
throw new IllegalStateException(String.format("Can not compile xpath match expression '%s'",
StringUtils.isNotBlank(expression) ? expression : "EMPTY EXPRESSION"), e);
}
}
}

View file

@ -0,0 +1,241 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.StringJoiner;
import javax.xml.bind.*;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import org.w3c.dom.Document;
import lombok.extern.slf4j.Slf4j;
/**
* JAXB Conversion Utility.
*/
@Slf4j
public class ConversionService {
// context setup
private JAXBContext jaxbContext;
private static <T> QName createQName(final T model) {
return new QName(model.getClass().getSimpleName().toLowerCase());
}
private void checkInputEmpty(final URI xml) {
if (xml == null) {
throw new ConversionExeption("Can not unmarshal from empty input");
}
}
private <T> void checkTypeEmpty(final Class<T> type) {
if (type == null) {
throw new ConversionExeption("Can not unmarshal without type information. Need to specify a target type");
}
}
/**
* Initialisiert den default context; Alle Packages mit {@link XmlRegistry XmlRegistries}.
*/
public void initialize() {
Collection<Package> p = new ArrayList<>();
p.add(de.kosit.validationtool.model.reportInput.ObjectFactory.class.getPackage());
p.add(de.kosit.validationtool.model.scenarios.ObjectFactory.class.getPackage());
initialize(p);
}
public void initialize(final Package... context) {
initialize(Arrays.asList(context));
}
/**
* Initialisiert den conversion service mit den angegegebenen Packages.
*
* @param context packages für den JAXB Kontext
*/
public void initialize(final Collection<Package> context) {
final String[] packages = context != null ? context.stream().map(Package::getName).toArray(String[]::new) : new String[0];
StringJoiner joiner = new StringJoiner(":");
Arrays.stream(packages).forEach(p -> joiner.add(p));
initialize(joiner.toString());
}
/**
* Initialsiert den conversion service mit dem angegebenen Kontextpfad
*
* @param contextPath der Kontextpfad
*/
private void initialize(final String contextPath) {
try {
this.jaxbContext = JAXBContext.newInstance(contextPath);
} catch (final JAXBException e) {
throw new IllegalStateException(String.format("Can not create JAXB context for given context: %s", contextPath), e);
}
}
private JAXBContext getJaxbContext() {
if (this.jaxbContext == null) {
initialize();
}
return this.jaxbContext;
}
/**
* Unmarshalls a specifc xml model into a defined java object.
*
* @param xml the xml
* @param type the expected type created
* @param <T> type information
* @return the created object
*/
public <T> T readXml(final URI xml, final Class<T> type) {
return readXml(xml, type, null, null);
}
public <T> T readXml(final URI xml, final Class<T> type, Schema schema) {
return readXml(xml, type, schema, null);
}
public <T> T readXml(final URI xml, final Class<T> type, Schema schema, ValidationEventHandler handler) {
checkInputEmpty(xml);
checkTypeEmpty(type);
CollectingErrorEventHandler defaultHandler = null;
ValidationEventHandler handler2Use = handler;
if (schema != null && handler == null) {
defaultHandler = new CollectingErrorEventHandler();
handler2Use = defaultHandler;
}
try {
final XMLInputFactory inputFactory = XMLInputFactory.newFactory();
inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
final XMLStreamReader xsr = inputFactory.createXMLStreamReader(new StreamSource(xml.toASCIIString()));
final Unmarshaller u = getJaxbContext().createUnmarshaller();
u.setSchema(schema);
u.setEventHandler(handler2Use);
final T value = u.unmarshal(xsr, type).getValue();
if (defaultHandler != null && defaultHandler.hasErrors()) {
throw new ConversionExeption(
String.format("Schema errors while reading content from %s: %s", xml, defaultHandler.getErrorDescription()));
}
return value;
} catch (final JAXBException | XMLStreamException e) {
throw new ConversionExeption(String.format("Can not unmarshal to type %s from %s", type.getSimpleName(), xml.toString()), e);
}
}
/**
* Serializing an object to xml.
*
* @param model the object
* @param <T> type of the object
* @return the serialized form.
*/
public <T> String writeXml(final T model) {
return writeXml(model, null, null);
}
public <T> String writeXml(final T model, Schema schema, ValidationEventHandler handler) {
if (model == null) {
throw new ConversionExeption("Can not serialize null");
}
try ( StringWriter w = new StringWriter() ) {
final JAXBIntrospector introspector = getJaxbContext().createJAXBIntrospector();
final Marshaller marshaller = getJaxbContext().createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
marshaller.setSchema(schema);
marshaller.setEventHandler(handler);
final XMLOutputFactory xof = XMLOutputFactory.newFactory();
final XMLStreamWriter xmlStreamWriter = xof.createXMLStreamWriter(w);
if (null == introspector.getElementName(model)) {
final JAXBElement jaxbElement = new JAXBElement(createQName(model), model.getClass(), model);
marshaller.marshal(jaxbElement, xmlStreamWriter);
} else {
marshaller.marshal(model, xmlStreamWriter);
}
xmlStreamWriter.flush();
return w.toString();
} catch (JAXBException | IOException | XMLStreamException e) {
throw new ConversionExeption(String.format("Error serializing Object %s", model.getClass().getName()), e);
}
}
public <T> Document writeDocument(T input) {
if (input == null) {
throw new ConversionExeption("Can not serialize null");
}
DocumentBuilder builder = ObjectFactory.createDocumentBuilder(false);
Document document = builder.newDocument();
// Marshal the Object to a Document
Marshaller marshaller = null;
try {
marshaller = getJaxbContext().createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(input, document);
return document;
} catch (JAXBException e) {
throw new ConversionExeption(String.format("Error serializing Object %s to document", input.getClass().getName()), e);
}
}
/**
* Exception while serializing/deserializing with jaxb.
*/
public class ConversionExeption extends RuntimeException {
/**
* Constructor.
*
* @param message the message.
* @param cause the cause
*/
public ConversionExeption(final String message, final Exception cause) {
super(message, cause);
}
/**
* Constructor.
*
* @param message the message.
*/
public ConversionExeption(final String message) {
super(message);
}
}
}

View file

@ -0,0 +1,137 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.w3c.dom.Document;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.Check;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.impl.tasks.*;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.DocumentIdentificationType;
import de.kosit.validationtool.model.reportInput.EngineType;
import de.kosit.validationtool.model.reportInput.ProcessingError;
import net.sf.saxon.s9api.Processor;
/**
* Die Referenz-Implementierung für den Prüfprozess. Nach initialer Konfiguration ist diese Klasse threadsafe und kann
* in Server-Umgebungen eingesetzt werden
*
* @author Andreas Penski
*/
@Slf4j
public class DefaultCheck implements Check {
private static final String ENGINE_NAME = "KoSIT Prüftool";
private static final String ENGINE_VERSION = "1.0.0";
private ScenarioRepository repository;
@Getter
private ContentRepository contentRepository;
private ConversionService conversionService;
@Getter
private List<CheckAction> checkSteps;
/**
* Erzeugt eine neue Instanz mit der angegebenen Konfiguration.
*
* @param configuration die Konfiguration
*/
public DefaultCheck(CheckConfiguration configuration) {
Processor processor = ObjectFactory.createProcessor();
conversionService = new ConversionService();
contentRepository = new ContentRepository(processor, configuration.getScenarioRepository());
repository = new ScenarioRepository(processor, contentRepository);
repository.initialize(configuration);
checkSteps = new ArrayList<>();
checkSteps.add(this::createDocumentIdentification);
checkSteps.add(new DocumentParseAction());
checkSteps.add(new ScenarioSelectionAction(repository));
checkSteps.add(new SchemaValidationAction());
checkSteps.add(new SchematronValidationAction(processor, configuration.getScenarioRepository()));
checkSteps.add(new ValidateReportInputAction(conversionService, contentRepository.getReportInputSchema()));
checkSteps.add(new CreateReportAction(processor, conversionService, repository, configuration.getScenarioRepository()));
}
protected static CreateReportInput createReport() {
CreateReportInput type = new CreateReportInput();
EngineType e = new EngineType();
e.setName(ENGINE_NAME);
type.setEngine(e);
type.setTimestamp(ObjectFactory.createTimestamp());
type.setFrameworkVersion(ENGINE_VERSION);
return type;
}
@Override
public Document check(Input input) {
CheckAction.Bag t = new CheckAction.Bag(input, createReport());
return runCheckInternal(t);
}
protected Document runCheckInternal(CheckAction.Bag t) {
long started = System.currentTimeMillis();
log.info("Checking content of {}", t.getInput().getName());
Iterator<CheckAction> it = checkSteps.iterator();
while (it.hasNext()) {
final CheckAction action = it.next();
if (!action.isSkipped(t)) {
action.check(t);
}
if (t.isStopped()) {
final ProcessingError processingError = t.getReportInput().getProcessingError();
log.error("Error processing input {}: {}", t.getInput().getName(),
processingError != null ? processingError.getError().stream().collect(Collectors.joining("\n")) : "");
break;
}
}
t.setFinished(true);
log.info("Finished check of {} in {}ms\n", t.getInput().getName(), System.currentTimeMillis() - started);
return t.getReport();
}
private boolean createDocumentIdentification(CheckAction.Bag transporter) {
DocumentIdentificationType i = new DocumentIdentificationType();
DocumentIdentificationType.DocumentHash h = new DocumentIdentificationType.DocumentHash();
h.setHashAlgorithm(transporter.getInput().getDigestAlgorithm());
h.setHashValue(transporter.getInput().getHashCode());
i.setDocumentHash(h);
i.setDocumentReference(transporter.getInput().getName());
transporter.getReportInput().setDocumentIdentification(i);
return true;
}
}

View file

@ -0,0 +1,256 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.XMLConstants;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import lombok.extern.slf4j.Slf4j;
import net.sf.saxon.Configuration;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.*;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.trans.XPathException;
/**
* Eine Factory für häufig verwendete Objekte mit XML. Zentralisiert die XML Security Konfiguration. Die Konfiguration
* basiert auf den <a href="https://www.owasp.org/index.php/XML_Security_Cheat_Sheet">OWASP-Empfehlungen</a>.
*
* Diese Klasse ist stark abhängig von der Verwendung eines Oracle JDK. Alternative JDKs haben u.U. eine andere SAX- /
* StAX- / XML-Implementierug und profitieren entsprechend NICHT von den hier getroffenen Einstellungen.
*
* @author Andreas Penski
*/
@Slf4j
public class ObjectFactory {
private static final String ORACLE_XERCES_CLASS = "com.sun.org.apache.xerces.internal.impl.Constants";
private static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
private static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
private static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing";
private static Processor processor;
static {
try {
Class.forName(ORACLE_XERCES_CLASS);
} catch (ClassNotFoundException e) {
log.warn("No oracle JDK version of XERCES found. Configured security features may not have any effect.");
log.warn("Please take care of XML security while checking your xml contents");
}
}
private ObjectFactory() {
// hide, it's a factory
}
private static DocumentBuilderFactory createDocumentBuilderFactory(boolean validating) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
dbf.setValidating(validating);
dbf.setNamespaceAware(true);
// explicitly enable secure processing
dbf.setFeature(FEATURE_SECURE_PROCESSING, true);
// This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
dbf.setFeature(DISSALLOW_DOCTYPE_DECL_FEATURE, true);
// Disable external DTDs as well
dbf.setFeature(LOAD_EXTERNAL_DTD_FEATURE, false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
return dbf;
} catch (ParserConfigurationException e) {
throw new IllegalStateException("Can not create DocumentBuilderFactory due to underlying configuration error", e);
}
}
/**
* Transformer für die Ausgabe. Nutzt nicht Saxon!
*
* @param prettyPrint pretty-printing der Ausgabe
* @return einen vorkonfigurierten Transformer
*/
public static Transformer createTransformer(boolean prettyPrint) {
Transformer transformer = null;
try {
transformer = TransformerFactory.newInstance().newTransformer();
if (prettyPrint) {
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
}
return transformer;
} catch (TransformerConfigurationException e) {
throw new IllegalStateException("Can not create Transformer due to underlying configuration error", e);
}
}
/**
* Erzeugt einen Zeitstempel zur Verwendung in XML-Objekten
*
* @return eine Instanz {@link XMLGregorianCalendar}
*/
public static XMLGregorianCalendar createTimestamp() {
try {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date());
return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
} catch (DatatypeConfigurationException e) {
throw new IllegalStateException("Can not create timestamp", e);
}
}
public static DocumentBuilder createDocumentBuilder(boolean validating) {
try {
return createDocumentBuilderFactory(validating).newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new IllegalStateException("Can not create DocumentFactory due to underlying configuration error", e);
}
}
private static String encode(String input) {
try {
return URLEncoder.encode(input, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Error encoding property while initializing saxon", e);
}
}
public static Processor createProcessor() {
if (processor == null) {
processor = new Processor(false);
//verhindere global im Prinzip alle resolving strategien
SecureUriResolver resolver = new SecureUriResolver();
processor.getUnderlyingConfiguration().setCollectionFinder(resolver);
processor.getUnderlyingConfiguration().setOutputURIResolver(resolver);
//hier fehlt eigentlich noch der UriResolver für unparsed text, wird erst ab Saxon 9.8 unterstützt
//grundsätzlich Feature-konfiguration:
processor.setConfigurationProperty(FeatureKeys.DTD_VALIDATION, false);
processor.setConfigurationProperty(FeatureKeys.ENTITY_RESOLVER_CLASS, "");
processor.setConfigurationProperty(FeatureKeys.XINCLUDE, false);
processor.setConfigurationProperty(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, false);
// Konfiguration des zu verwendenden Parsers, wenn Saxon selbst einen erzeugen muss, bspw. beim XSL parsen
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(FEATURE_SECURE_PROCESSING), true);
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(DISSALLOW_DOCTYPE_DECL_FEATURE), false);
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(LOAD_EXTERNAL_DTD_FEATURE), false);
}
return processor;
}
/**
* Erzeugt einen Validier für das angegebenen Schema.
*
* @param schema das Schema mit dem validiert werden soll
* @return einen vorkonfigurierten Validator
*/
public static Validator createValidator(Schema schema) {
final Validator validator = schema.newValidator();
try {
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
} catch (SAXNotRecognizedException | SAXNotSupportedException e) {
log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
log.debug(e.getMessage(), e);
}
try {
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (SAXNotRecognizedException | SAXNotSupportedException e) {
log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
log.debug(e.getMessage(), e);
}
return validator;
}
public static SchemaFactory createSchemaFactory() {
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file");
} catch (SAXException e) {
log.warn("Can not disable external DTD access, maybe an unsupported JAXP implementation is used", e);
}
return sf;
}
private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver {
public static final String MESSAGE = "Configuration error. Resolving ist not allowed";
@Override
public OutputURIResolver newInstance() {
return this;
}
@Override
public Result resolve(String href, String base) throws TransformerException {
throw new IllegalStateException(MESSAGE);
}
@Override
public void close(Result result) throws TransformerException {
throw new IllegalStateException(MESSAGE);
}
@Override
public Reader resolve(URI absoluteURI, String encoding, Configuration config) throws XPathException {
throw new IllegalStateException(MESSAGE);
}
@Override
public ResourceCollection findCollection(XPathContext context, String collectionURI) throws XPathException {
throw new IllegalStateException(MESSAGE);
}
}
}

View file

@ -0,0 +1,85 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;
import lombok.RequiredArgsConstructor;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.UnparsedTextURIResolver;
import net.sf.saxon.trans.XPathException;
/**
* {@link URIResolver} that resolves artifacts relative to a given base uri. The resolved URI must be resolving as child
* e.g. the baseUri must be a parent of the resolved artifact.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
public class RelativeUriResolver implements URIResolver, UnparsedTextURIResolver {
/** the base uri */
private final URI baseUri;
@Override
public Source resolve(final String href, final String base) throws TransformerException {
final URI resolved = URI.create(base).resolve(href);
if (isUnderBaseUri(resolved)) {
try {
return new StreamSource(resolved.toURL().openStream());
} catch (final IOException e) {
throw new IllegalStateException(String.format("Can not resolve required %s", href), e);
}
} else {
throw new IllegalStateException(
String.format("The resolved transformation artifact %s is not within the configured repository %s", resolved, baseUri));
}
}
private boolean isUnderBaseUri(URI resolved) {
String base = baseUri.toASCIIString().replaceAll("file:/+", "");
String r = resolved.toASCIIString().replaceAll("file:/+", "");
return r.startsWith(base);
}
@Override
public Reader resolve(URI absoluteURI, String encoding, Configuration config) throws XPathException {
if (isUnderBaseUri(absoluteURI)) {
try {
return new InputStreamReader(absoluteURI.toURL().openStream(), encoding);
} catch (IOException e) {
throw new IllegalStateException(String.format("Can not resolve required %s", absoluteURI), e);
}
} else {
throw new IllegalStateException(
String.format("The resolved transformation artifact %s is not within the configured repository %s", absoluteURI, baseUri));
}
}
}

View file

@ -0,0 +1,173 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.DocumentParseAction;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import de.kosit.validationtool.model.scenarios.Scenarios;
import net.sf.saxon.s9api.*;
/**
* Repository for die aktiven Szenario einer Prüfinstanz.
*
* @author Andreas Penski
*/
@Slf4j
@RequiredArgsConstructor
public class ScenarioRepository {
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";
@Getter(value = AccessLevel.PRIVATE)
private final Processor processor;
@Getter(value = AccessLevel.PRIVATE)
private final ContentRepository repository;
private XsltExecutable noScenarioReport;
@Getter(value = AccessLevel.PACKAGE)
private Scenarios scenarios;
private static boolean isSupportedDocument(Document doc) {
final Element root = doc.getDocumentElement();
return root.hasAttribute("frameworkVersion") && root.getAttribute("frameworkVersion").startsWith(SUPPORTED_MAJOR_VERSION)
&& doc.getDocumentElement().getNamespaceURI().equals(SUPPORTED_MAJOR_VERSION_SCHEMA);
}
private static void checkVersion(URI scenarioDefinition) {
DocumentParseAction p = new DocumentParseAction();
try {
final Result<Document, XMLSyntaxError> result = p.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 (MalformedURLException e) {
throw new IllegalStateException("Error reading definition file");
}
}
public XsltExecutable getNoScenarioReport() {
if (noScenarioReport == null) {
noScenarioReport = repository.loadXsltScript(URI.create(scenarios.getNoScenarioReport().getResource().getLocation()));
}
return noScenarioReport;
}
/**
* Initialisiert das Repository mit der angegebenen Konfiguration.
*
* @param config die Konfiguration
*/
public void initialize(CheckConfiguration config) {
ConversionService conversionService = new ConversionService();
checkVersion(config.getScenarioDefinition());
log.info("Loading scenarios from {}", config.getScenarioDefinition());
CollectingErrorEventHandler handler = new CollectingErrorEventHandler();
this.scenarios = conversionService.readXml(config.getScenarioDefinition(), Scenarios.class, repository.getScenarioSchema(),
handler);
if (!handler.hasErrors()) {
log.info("Loaded scenarios for {} by {} from {}. The following scenarios are available:\n\n{}", scenarios.getName(),
scenarios.getAuthor(), scenarios.getDate(), summarizeScenarios());
log.info("Loading scenario content from {}", config.getScenarioRepository());
getScenarios().getScenario().forEach(s -> s.initialize(repository, false));
} else {
throw new IllegalStateException(String.format("Can not load scenarios from %s due to %s", config.getScenarioDefinition(),
handler.getErrorDescription()));
}
// initialize fallback report eager
getNoScenarioReport();
}
private String summarizeScenarios() {
StringBuilder b = new StringBuilder();
scenarios.getScenario().forEach(s -> {
b.append(s.getName());
b.append("\n");
});
return b.toString();
}
/**
* Ermittelt für das angegebene Dokument das passende Szenario.
*
* @param document das Eingabedokument
* @return ein Ergebnis-Objekt zur weiteren Verarbeitung
*/
public Result<ScenarioType, String> selectScenario(Document document) {
Result<ScenarioType, String> result = new Result<>();
final List<ScenarioType> collect = scenarios.getScenario().stream().filter(s -> match(document, s)).collect(Collectors.toList());
if (collect.size() == 1) {
result = new Result<>(collect.get(0));
} else if (collect.isEmpty()) {
result.getErrors().add("None of the loaded scenarios matches the specified document");
} else {
result.getErrors().add("More than on scenario matches the specified document");
}
return result;
}
private boolean match(Document document, ScenarioType scenario) {
try {
final XPathSelector selector = scenario.getSelector();
DocumentBuilder documentBuilder = getProcessor().newDocumentBuilder();
final XdmNode xdmSource = documentBuilder.build(new DOMSource(document));
selector.setContextItem(xdmSource);
return selector.effectiveBooleanValue();
} catch (SaxonApiException e) {
log.error("Error evaluating xpath expression", e);
}
return false;
}
void initialize(Scenarios def) {
this.scenarios = def;
}
}

View file

@ -0,0 +1,187 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.model;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.xml.validation.Schema;
import org.apache.commons.lang3.NotImplementedException;
import org.w3c.dom.Document;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.ScenarioRepository;
import de.kosit.validationtool.model.scenarios.*;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XsltExecutable;
/**
* Eine Basis-Klasse für Szenarien. Wiederverwendbare Objekte mit Bezug zum Szenario werden hier gecached. Die Klasse
* stellt im eigentlichen Sinne eine Erweiterung der durch JAXB generierten Strukturen dar.
*
* @author Andreas Penski
*/
public abstract class BaseScenario {
private XPathExecutable xPathExecutable;
private Schema schema;
private List<Transformation> schematronValidations;
private ContentRepository repository;
private Transformation reportTransformation;
/**
* Gibt eine Transformation zurück.
* @return initialisierte Transformation
*/
public Transformation getReportTransformation() {
if (reportTransformation == null) {
final ResourceType resource = getCreateReport().getResource();
final XsltExecutable executable = repository.loadXsltScript(URI.create(resource.getLocation()));
reportTransformation = new Transformation(executable, resource);
}
return reportTransformation;
}
/**
* Lieferrt das Schema zu diesem Szenario.
* @return das passende Schema
*/
public Schema getSchema() {
if (schema == null) {
final List<String> schemaResources = getValidateWithXmlSchema().getResource().stream().map(r -> r.getLocation())
.collect(Collectors.toList());
schema = repository.createSchema(schemaResources);
}
return schema;
}
/**
* Initialisiert das Szenario auf Basis eines [@link ContentRepository}
* @param repository das Repository mit den Szenario-Artefakten
* @param lazy optionales lazy loading der XML-Artefakte
* @return true wenn erfolgreich
*/
public boolean initialize(ContentRepository repository, boolean lazy) {
this.repository = repository;
if (!lazy) {
getSchema();
getSelector();
getSchematronValidations();
getReportTransformation();
}
return true;
}
/**
* Liefer eine Liste mit Schematron Validierungs-Transformationen
* @return liste mit initialisierten Transformationsinformationen
*/
public List<Transformation> getSchematronValidations() {
if (schematronValidations == null) {
schematronValidations = new ArrayList<>();
getValidateWithSchematron().forEach(v -> {
if (v.isPsvi()) {
throw new NotImplementedException("This implemenation does not support PSVI usage");
}
final XsltExecutable xsltExecutable = repository.loadXsltScript(URI.create(v.getResource().getLocation()));
schematronValidations.add(new Transformation(xsltExecutable, v.getResource()));
});
}
return schematronValidations;
}
/**
* Der XPath-Selector zur Identifikation des Scenarios.
*
* @return vorbereiteten selector
* @see {@link ScenarioRepository#selectScenario(Document)}.
*/
public XPathSelector getSelector() {
if (xPathExecutable == null) {
final Map<String, String> namespaces = getNamespace().stream().collect(Collectors.toMap(NamespaceType::getPrefix, NamespaceType::getValue));
xPathExecutable = repository.createXPath(getMatch(), namespaces);
}
return xPathExecutable.load();
}
/**
* Getter aus dem schema.
*
* @return xpath match
*/
public abstract String getMatch();
/**
* Getter aus dem schema.
*
* @return {@link List} of {@link NamespaceType}
*/
public abstract List<NamespaceType> getNamespace();
/**
* Getter aus dem schema.
*
* @return Validierungsanweisungen
*/
public abstract ValidateWithXmlSchema getValidateWithXmlSchema();
/**
* Getter aus dem schema.
*
* @return Validierungsanweisungne
*/
public abstract List<ValidateWithSchematron> getValidateWithSchematron();
/**
* Getter aus dem schema.
*
* @return report informationen
*/
public abstract CreateReportType getCreateReport();
/**
* Laufzeitinformationen über eine Transformation.
*/
@Getter
@Setter
@AllArgsConstructor
public class Transformation {
private XsltExecutable executable;
private ResourceType resourceType;
}
}

View file

@ -0,0 +1,82 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.model;
import org.slf4j.Logger;
import de.kosit.validationtool.model.reportInput.XMLSyntaxErrorSeverity;
/**
* Basis-Klasse für Syntax-Error. Wird über die JAXB-generierte Klasse
* {@link de.kosit.validationtool.model.reportInput.XMLSyntaxError} erweitert.
*
* @author Andreas Penski
*/
public abstract class BaseXMLSyntaxError {
/**
* Logged den Syntax-Fehler über einen definierten Logger.
*
* @param logger der Logger
*/
public void log(Logger logger) {
String msgTemplate = "{} At row {} at pos {}";
Object[] params = { getMessage(), getRowNumber(), getColumnNumber() };
if (getSeverity() == XMLSyntaxErrorSeverity.SEVERITY_WARNING) {
logger.warn(msgTemplate, params);
} else {
logger.error(msgTemplate, params);
}
}
@Override
public String toString() {
return String.format("%s At row %s at pos %s", getMessage(), getRowNumber(), getColumnNumber());
}
/**
* Getter aus dem schema
*
* @return Spalte des Fehlers
*/
public abstract Integer getColumnNumber();
/**
* Getter aus dem schema
*
* @return Zeile des Fehlers
*/
public abstract Integer getRowNumber();
/**
* Getter aus dem schema
*
* @return Fehlermeldung
*/
public abstract String getMessage();
/**
* Getter aus dem schema
*
* @return severity
*/
public abstract XMLSyntaxErrorSeverity getSeverity();
}

View file

@ -0,0 +1,80 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Ein Ergebnisobjekte, dass das eigentliche Ergebnis hält und optional auch verschiedene Fehlerobjekte.
*
* @param <T> der Typ des Ergebnis-Objekts
* @param <E> der Typ des Fehler-Objekts
*/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Result<T, E> {
private T object;
private Collection<E> errors = new ArrayList<>();
/**
* Erzeugt ein neues Ergebnis mit Fehler
*
* @param errors die Fehler
*/
public Result(Collection<E> errors) {
this(null, errors);
}
/**
* Erzeugt ein neues Ergebnis mit einem Ergebnisobjekt
*
* @param o
*/
public Result(T o) {
this(o, Collections.emptyList());
}
/**
* Zeigt an, ob das Ergebnis valide, also ohne Fehler ist.
*
* @return true wenn erfolgreich
*/
public boolean isValid() {
return object != null && errors.isEmpty();
}
/**
* Zeigt an, ob das Ergebnis nicht valide ist, als entsprechend Fehler gesammelt wurden.
*
* @return true wenn erfolgreich wenn Fehler vorhanden sind.
*/
public boolean isInvalid() {
return !isValid();
}
}

View file

@ -0,0 +1,120 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.tasks;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import lombok.Getter;
import lombok.Setter;
import org.w3c.dom.Document;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Interface, welches von allen Prüfschritten implementiert wird. Der Parameter vom Typ {@link Bag} dient dabei sowohl
* als Quellce für Eingabe Parameter als auch für die Aufnahme von Ergebnisse, die an weitere Schritte weitergeleitet
* werden sollen.
*
* @author Andreas Penski
*/
@FunctionalInterface
public interface CheckAction {
/**
* Ausfürhung des Prüfschrittes und Erweiterung der gesammelten Informationen.
*
* @param results die Informationssammlung
*/
void check(Bag results);
/**
* Ermittlung, ob ein Schritt u.U. ausgelassen werden kann. Die Funktion wird vor der eigentlichen Prüfaktion aufgerufen
* und kann somit eine Ausführung des Prüfschrittes verhindern. Entwickler können diese Funktion überschreiben, um den
* Prüfschritt bedingt auszuführen.
*
* @param results die bisher gesammelten Information
* @return <tt>true</tt> wenn der Schritt ausgelassen werden soll
*/
default boolean isSkipped(Bag results) {
return false;
}
/**
* Transport-Klasse für Eingabe und Ausgabe-Objekte für die einzelnen Prüfschritte.
*/
@Getter
@Setter
class Bag {
private Result<ScenarioType, String> scenarioSelectionResult;
private CreateReportInput reportInput;
/** Das finale Ergebnis */
private Document report;
private boolean finished;
private boolean stopped;
/** Das zu prüfende Dokument */
private Input input;
private Result<Document, XMLSyntaxError> parserResult;
private Result<Integer, String> assertionResult;
private Result<Boolean, XMLSyntaxError> schemaValidationResult;
public Bag(Input input) {
this(input, new CreateReportInput());
}
public Bag(Input input, CreateReportInput reportInput) {
this.input = input;
this.reportInput = reportInput;
}
/**
* Signalisiert einen vorzeitigen Stop der Vearbeitung.
*/
public void stopProcessing() {
this.stopped = true;
}
/**
* Gibt den Namen des Prüflings zurück, dabei werden etwaige Pfadinformationen abgeschnitten.
*
* @return der Name des Prüflings
*/
public String getName() {
final String fileName = getInput().getName().replaceAll(".*/|.*\\\\", "");
final Matcher matcher = Pattern.compile("(.*)\\..+").matcher(fileName);
if (matcher.matches()) {
return matcher.group(1);
}
return fileName;
}
}
}

View file

@ -0,0 +1,94 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.tasks;
import java.net.URI;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.Document;
import lombok.RequiredArgsConstructor;
import de.kosit.validationtool.impl.*;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.*;
/**
* Erzeugt den Report auf Basis der gesammelten Informationen über den Prüfling. Sollte kein Szenario identifiziert
* worden sein, so wird ein {@link ScenarioRepository#getNoScenarioReport() default report} erzeugt.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
public class CreateReportAction implements CheckAction {
private final Processor processor;
private final ConversionService conversionService;
private final ScenarioRepository repository;
private final URI contentRepository;
private static XsltExecutable loadFromScenario(ScenarioType object) {
return object.getReportTransformation().getExecutable();
}
@Override
public void check(Bag results) {
final DocumentBuilder documentBuilder = processor.newDocumentBuilder();
try {
final Document inputDoc = results.getParserResult().isValid() ? results.getParserResult().getObject()
: ObjectFactory.createDocumentBuilder(true).newDocument();
final XdmNode parsedDocument = documentBuilder.build(new DOMSource(inputDoc));
final Document reportInput = conversionService.writeDocument(results.getReportInput());
final XdmNode root = documentBuilder.build(new DOMSource(reportInput));
final XsltTransformer transformer = getTransformation(results).load();
transformer.setInitialContextNode(root);
CollectingErrorEventHandler e = new CollectingErrorEventHandler();
RelativeUriResolver resolver = new RelativeUriResolver(contentRepository);
transformer.setMessageListener(e);
transformer.setURIResolver(resolver);
transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver);
transformer.setParameter(new QName("input-document"), parsedDocument);
Document result = ObjectFactory.createDocumentBuilder(false).newDocument();
transformer.setDestination(new DOMDestination(result));
transformer.transform();
results.setReport(result);
} catch (SaxonApiException e) {
throw new IllegalStateException("Can not create final report", e);
}
}
private XsltExecutable getTransformation(Bag results) {
final Result<ScenarioType, String> scenario = results.getScenarioSelectionResult();
return scenario != null && scenario.isValid() ? loadFromScenario(scenario.getObject()) : loadFallback();
}
private XsltExecutable loadFallback() {
return repository.getNoScenarioReport();
}
}

View file

@ -0,0 +1,90 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.tasks;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.ValidationResultsWellformedness;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.reportInput.XMLSyntaxErrorSeverity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
/**
* Setzt Parsing-Funktionalitäten um. Prüft auf well-formedness
*
* @author Andreas Penski
*/
@Slf4j
@RequiredArgsConstructor
public class DocumentParseAction implements CheckAction {
/**
* Parsed und überprüft ein übergebenes Dokument darauf ob es well-formed ist. Dies stellt den ersten
* Verarbeitungsschritt des Prüf-Tools dar. Diese Funktion verzichtet explizit auf das Validierung gegenüber ein Schema.
*
* @param content ein Dokument
* @return Ergebnis des Parsings inklusive etwaiger Fehler
*/
public Result<Document, XMLSyntaxError> parseDocument(Input content) {
if (content == null) {
throw new IllegalArgumentException("Url may not be null");
}
Result<Document, XMLSyntaxError> result;
CollectingErrorEventHandler errorHandler = new CollectingErrorEventHandler();
try ( InputStream input = new ByteArrayInputStream(content.getContent()) ) {
DocumentBuilder db = ObjectFactory.createDocumentBuilder(false);
db.setErrorHandler(errorHandler);
Document doc = db.parse(input);
result = new Result<>(doc, errorHandler.getErrors());
} catch (SAXException e) {
log.debug("SAXException while parsing {}", content.getName(), e);
result = new Result<>(errorHandler.getErrors());
} catch (IOException e) {
log.debug("IOException while parsing {}", content, e);
XMLSyntaxError error = new XMLSyntaxError();
error.setSeverity(XMLSyntaxErrorSeverity.SEVERITY_FATAL_ERROR);
error.setMessage(String.format("IOException while reading resource %s", content.getName()));
result = new Result<>(Collections.singleton(error));
}
return result;
}
@Override
public void check(Bag results) {
Result<Document, XMLSyntaxError> parserResult = parseDocument(results.getInput());
ValidationResultsWellformedness v = new ValidationResultsWellformedness();
results.setParserResult(parserResult);
v.getXmlSyntaxError().addAll(parserResult.getErrors());
results.getReportInput().setValidationResultsWellformedness(v);
}
}

View file

@ -0,0 +1,60 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.tasks;
import lombok.RequiredArgsConstructor;
import de.kosit.validationtool.impl.ScenarioRepository;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.ProcessingError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
/**
* Identifiziert das der Eingabe entsprechende Szenario, sofern eines konfiguriert ist.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
public class ScenarioSelectionAction implements CheckAction {
private final ScenarioRepository repository;
@Override
public void check(Bag results) {
final CreateReportInput report = results.getReportInput();
final Result<ScenarioType, String> scenarioTypeResult = repository.selectScenario(results.getParserResult().getObject());
results.setScenarioSelectionResult(scenarioTypeResult);
if (scenarioTypeResult.isValid()) {
final ScenarioType scenario = scenarioTypeResult.getObject();
report.setScenario(scenario);
} else {
if (report.getProcessingError() == null) {
report.setProcessingError(new ProcessingError());
}
report.getProcessingError().getError().addAll(scenarioTypeResult.getErrors());
}
}
@Override
public boolean isSkipped(Bag results) {
return results.getParserResult().isInvalid();
}
}

View file

@ -0,0 +1,86 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.tasks;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Validator;
import org.xml.sax.SAXException;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.ValidationResultsXmlSchema;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
/**
* Schema-Validierung der Eingabe-Datei mittels Schema-Definition aus dem identifizierten Szenario.
*
* @author Andreas Penski
*/
@Slf4j
public class SchemaValidationAction implements CheckAction {
private Result<Boolean, XMLSyntaxError> validate(byte[] document, ScenarioType scenarioType) {
log.debug("Validating document using scenario {}", scenarioType.getName());
final CollectingErrorEventHandler errorHandler = new CollectingErrorEventHandler();
try ( InputStream input = new ByteArrayInputStream(document) ) {
final Validator validator = ObjectFactory.createValidator(scenarioType.getSchema());
validator.setErrorHandler(errorHandler);
validator.validate(new StreamSource(input));
return new Result<>(!errorHandler.hasErrors(), errorHandler.getErrors());
} catch (SAXException | IOException e) {
throw new IllegalStateException("Error validating document", e);
}
}
@Override
public void check(Bag results) {
final CreateReportInput report = results.getReportInput();
final ScenarioType scenario = results.getScenarioSelectionResult().getObject();
final Result<Boolean, XMLSyntaxError> validateResult = validate(results.getInput().getContent(), scenario);
results.setSchemaValidationResult(validateResult);
ValidationResultsXmlSchema result = new ValidationResultsXmlSchema();
report.setValidationResultsXmlSchema(result);
result.getResource().addAll(scenario.getValidateWithXmlSchema().getResource());
if (!validateResult.isValid()) {
result.getXmlSyntaxError().addAll(validateResult.getErrors());
}
}
@Override
public boolean isSkipped(Bag results) {
return hasNoScenario(results);
}
private static boolean hasNoScenario(Bag results) {
return results.getScenarioSelectionResult() == null || results.getScenarioSelectionResult().isInvalid();
}
}

View file

@ -0,0 +1,97 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.tasks;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.Document;
import lombok.RequiredArgsConstructor;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.RelativeUriResolver;
import de.kosit.validationtool.impl.model.BaseScenario;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.ValidationResultsSchematron;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.*;
/**
* Ausführung von konfigurierten Schematron Validierungen eines Szenarios.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
public class SchematronValidationAction implements CheckAction {
private final Processor processor;
private final URI repository;
private List<ValidationResultsSchematron> validate(Document document, ScenarioType scenario) {
return scenario.getSchematronValidations().stream().map(v -> validate(document, v)).collect(Collectors.toList());
}
private ValidationResultsSchematron validate(Document document, BaseScenario.Transformation validation) {
final DocumentBuilder documentBuilder = processor.newDocumentBuilder();
try {
final XdmNode root = documentBuilder.build(new DOMSource(document));
final XsltTransformer transformer = validation.getExecutable().load();
//resolving nur relative zum Repository
final RelativeUriResolver resolver = new RelativeUriResolver(repository);
transformer.setURIResolver(resolver);
CollectingErrorEventHandler e = new CollectingErrorEventHandler();
transformer.setMessageListener(e);
Document result = ObjectFactory.createDocumentBuilder(false).newDocument();
transformer.setDestination(new DOMDestination(result));
transformer.setInitialContextNode(root);
transformer.transform();
ValidationResultsSchematron s = new ValidationResultsSchematron();
s.setResource(validation.getResourceType());
ValidationResultsSchematron.Results r = new ValidationResultsSchematron.Results();
r.setAny(result.getDocumentElement());
s.setResults(r);
return s;
} catch (SaxonApiException e) {
throw new IllegalStateException("Can not run schematron validation", e);
}
}
@Override
public void check(Bag results) {
final CreateReportInput report = results.getReportInput();
final List<ValidationResultsSchematron> bla = validate(results.getParserResult().getObject(),
results.getScenarioSelectionResult().getObject());
report.getValidationResultsSchematron().addAll(bla);
}
@Override
public boolean isSkipped(Bag results) {
return results.getSchemaValidationResult() == null || results.getSchemaValidationResult().isInvalid();
}
}

View file

@ -0,0 +1,68 @@
/*
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
* one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. KoSIT licenses this file
* to you 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.impl.tasks;
import javax.xml.validation.Schema;
import org.apache.commons.lang3.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ConversionService;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
/**
* Validiert die gesammelten Informationen über den Prüfling. Zusätzlich Check.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
@Slf4j
public class ValidateReportInputAction implements CheckAction {
private final ConversionService conversionService;
private final Schema schema;
@Override
public void check(Bag bag) {
final Result<Boolean, XMLSyntaxError> results = validate(bag.getReportInput());
if (!results.isValid()) {
log.error("Report input has errors {}", results.getErrors());
bag.stopProcessing();
}
}
/**
* Validatiert das gegebene JAXB-Objekt gegen das konfigurierte Schema
*
* @param object das JAXB-Objekt
* @param <T> der Typ des Objekts
* @return ein Validierungsergebnis
*/
private <T> Result<Boolean, XMLSyntaxError> validate(T object) {
CollectingErrorEventHandler h = new CollectingErrorEventHandler();
final String result = conversionService.writeXml(object, schema, h);
return new Result<>(StringUtils.isNotBlank(result), h.getErrors());
}
}

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
~ one or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. KoSIT licenses this file
~ to you 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.
-->
<jaxb:bindings
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance"
jaxb:extensionBindingPrefixes="inheritance"
version="2.1">
<jaxb:globalBindings>
<xjc:serializable uid="-1"/>
</jaxb:globalBindings>
<jaxb:bindings schemaLocation="../xsd/createReportInput.xsd">
<jaxb:schemaBindings>
<jaxb:package name="de.kosit.validationtool.model.reportInput"/>
</jaxb:schemaBindings>
<jaxb:bindings node="//xs:complexType[@name='XMLSyntaxError']">
<inheritance:extends>de.kosit.validationtool.impl.model.BaseXMLSyntaxError</inheritance:extends>
</jaxb:bindings>
</jaxb:bindings>
<jaxb:bindings schemaLocation="../xsd/scenarios.xsd">
<jaxb:schemaBindings>
<jaxb:package name="de.kosit.validationtool.model.scenarios"/>
</jaxb:schemaBindings>
<jaxb:bindings node="//xs:complexType[@name='ScenarioType']">
<inheritance:extends>de.kosit.validationtool.impl.model.BaseScenario</inheritance:extends>
</jaxb:bindings>
</jaxb:bindings>
<jaxb:bindings schemaLocation="../xsd/assertions.xsd">
<jaxb:schemaBindings>
<jaxb:package name="de.kosit.validationtool.cmd.assertions"/>
</jaxb:schemaBindings>
</jaxb:bindings>
</jaxb:bindings>

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
~ one or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. KoSIT licenses this file
~ to you 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.
-->
<!-- $Id: assertions.xsd 7554 2017-09-13 14:27:21Z fbuettner $ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:a="http://www.xoev.de/de/validator/framework/1/assertions"
targetNamespace="http://www.xoev.de/de/validator/framework/1/assertions" version="1.0.0"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="assertions">
<xs:annotation>
<xs:documentation>In diesem Dokument werden zum Test einer Prüftoolkonfiguration Zusicherungen zu einzelnen
Prüfberichten beschrieben. Ein
solches Dokument kann der Kommandozeilenversion des Prüftools über --check-assertions übergeben werden.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="namespace" type="a:NamespaceType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="assertion" type="a:AssertionType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="NamespaceType">
<xs:annotation>
<xs:documentation>Definition eines Präfix für einen Namensraum, zur Verwendung in nachfolgenden
assertion-Elementen.
</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attribute name="prefix" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="AssertionType">
<xs:annotation>
<xs:documentation>Zusicherung: für das in report-doc angegebene Dokument (relativ zum aktuellen
Ausgabeverzeichnis) hat der angebene
XPath-Test den "Effective Truth Value" true(). Im Textknoten kann eine textuelle Zusammenfassung des
Tests stehen.
</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:token">
<xs:attribute name="report-doc" type="xs:anyURI" use="required"/>
<xs:attribute name="test" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
~ one or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. KoSIT licenses this file
~ to you 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.
-->
<!-- $Id$ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:in="http://www.xoev.de/de/validator/framework/1/createreportinput"
xmlns:s="http://www.xoev.de/de/validator/framework/1/scenarios" targetNamespace="http://www.xoev.de/de/validator/framework/1/createreportinput" version="1.0.0"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:import namespace="http://www.xoev.de/de/validator/framework/1/scenarios" schemaLocation="scenarios.xsd"/>
<xs:element name="createReportInput">
<xs:complexType>
<xs:sequence>
<xs:element name="engine" type="in:EngineType"/>
<xs:element name="timestamp" type="xs:dateTime"/>
<xs:element name="documentIdentification" type="in:DocumentIdentificationType"/>
<xs:element ref="s:scenario" minOccurs="0"/>
<xs:element name="validationResultsWellformedness" type="in:ValidationResultsWellformedness" minOccurs="0"/>
<xs:element name="validationResultsXmlSchema" type="in:ValidationResultsXmlSchema" minOccurs="0"/>
<xs:element name="validationResultsSchematron" type="in:ValidationResultsSchematron" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="processingError" type="in:ProcessingError" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="frameworkVersion" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:complexType name="Document">
<xs:sequence>
<xs:any processContents="skip" namespace="##any"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ValidationResultsWellformedness">
<xs:sequence>
<xs:element name="xmlSyntaxError" type="in:XMLSyntaxError" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ValidationResultsXmlSchema">
<xs:sequence>
<xs:element ref="s:resource" maxOccurs="unbounded"/>
<xs:element name="xmlSyntaxError" type="in:XMLSyntaxError" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:annotation>
<xs:documentation>In alignment with
http://docs.oracle.com/javase/8/docs/api/org/xml/sax/ErrorHandler.html and
http://docs.oracle.com/javase/8/docs/api/org/xml/sax/SAXParseException.html</xs:documentation>
</xs:annotation>
<xs:complexType name="XMLSyntaxError">
<xs:sequence>
<xs:element name="message" type="xs:normalizedString"/>
<xs:element name="severity" type="in:XMLSyntaxErrorSeverity"/>
<xs:element name="rowNumber" type="xs:int" minOccurs="0"/>
<xs:element name="columnNumber" type="xs:int" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="XMLSyntaxErrorSeverity">
<xs:restriction base="xs:token">
<xs:enumeration value="SEVERITY_WARNING"/>
<xs:enumeration value="SEVERITY_ERROR"/>
<xs:enumeration value="SEVERITY_FATAL_ERROR"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="ValidationResultsSchematron">
<xs:sequence>
<xs:element ref="s:resource"/>
<xs:element name="results">
<xs:complexType>
<xs:sequence>
<xs:any namespace="##any" processContents="skip"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DocumentIdentificationType">
<xs:annotation>
<xs:documentation>Dient der eindeutigen Identifikation des geprüften Dokuments anhand seines Hashwertes, der durch eine Dokumentenreferenz
ergänzt werden kann.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="documentHash">
<xs:annotation>
<xs:documentation>Angaben zum Hashwert des geprüften Dokuments.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="hashAlgorithm" type="xs:normalizedString">
<xs:annotation>
<xs:documentation>Benennung eines Algorithmus zur Berechnung des Hashwerts.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="hashValue" type="xs:base64Binary">
<xs:annotation>
<xs:documentation>Der Hashwert des geprüften Dokuments bei Anwendung des bezeichneten Algorithmus.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="documentReference" type="xs:normalizedString">
<xs:annotation>
<xs:documentation>Eine von der prüfenden Organisationseinheit festgelegte, möglichst eindeutige Referenz des geprüften
Dokuments.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="EngineType">
<xs:sequence>
<xs:element name="name" type="xs:normalizedString"/>
<xs:element name="info" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:normalizedString">
<xs:attribute name="key" type="xs:NMTOKEN" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ProcessingError">
<xs:sequence>
<xs:element name="error" type="xs:string" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
~ one or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. KoSIT licenses this file
~ to you 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.
-->
<!-- $Id$ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.xoev.de/de/validator/framework/1/scenarios"
targetNamespace="http://www.xoev.de/de/validator/framework/1/scenarios" version="1.0.0" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="scenarios">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:token"/>
<xs:element minOccurs="0" name="author" type="xs:token"/>
<xs:element name="date" type="xs:date"/>
<xs:element name="description" type="s:DescriptionType"/>
<xs:element ref="s:scenario" minOccurs="1" maxOccurs="unbounded"/>
<xs:element name="noScenarioReport" type="s:NoScenarioReportType"/>
</xs:sequence>
<xs:attribute name="frameworkVersion" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="scenario" type="s:ScenarioType"/>
<xs:complexType name="NoScenarioReportType">
<xs:sequence>
<xs:element ref="s:resource"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DescriptionType">
<xs:choice maxOccurs="unbounded">
<xs:element name="p" type="xs:normalizedString"/>
<xs:element name="ol">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="li" type="xs:normalizedString"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ul">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="li" type="xs:normalizedString"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<!-- TODO: Prüfen, ob restriction passt -->
<xs:simpleType name="ErrorLevelType">
<xs:restriction base="xs:token">
<xs:enumeration value="error"/>
<xs:enumeration value="warning"/>
<xs:enumeration value="information"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="Tokens">
<xs:list itemType="xs:token"/>
</xs:simpleType>
<xs:complexType name="ScenarioType">
<xs:sequence>
<xs:element name="name" type="xs:token"/>
<xs:element minOccurs="0" name="description" type="s:DescriptionType"/>
<xs:element minOccurs="0" maxOccurs="unbounded" name="namespace" type="s:NamespaceType"/>
<xs:element name="match" type="xs:string"/>
<xs:element name="validateWithXmlSchema" type="s:ValidateWithXmlSchema"/>
<xs:element name="validateWithSchematron" maxOccurs="unbounded" minOccurs="0" type="s:ValidateWithSchematron"/>
<xs:element name="createReport" type="s:CreateReportType"/>
</xs:sequence>
</xs:complexType>
<xs:element name="resource" type="s:ResourceType"/>
<xs:complexType name="NamespaceType">
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attribute name="prefix" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="ResourceType">
<xs:sequence>
<xs:element name="name" type="xs:token"/>
<xs:element name="location" type="xs:anyURI"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ValidateWithXmlSchema">
<xs:sequence>
<xs:element ref="s:resource" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ValidateWithSchematron">
<xs:sequence>
<xs:element ref="s:resource"/>
</xs:sequence>
<xs:attribute name="psvi" type="xs:boolean" default="false" use="optional"/>
</xs:complexType>
<xs:complexType name="CreateReportType">
<xs:sequence>
<xs:element ref="s:resource"/>
<xs:element maxOccurs="3" minOccurs="0" name="customLevel" type="s:CustomErrorLevel"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CustomErrorLevel">
<xs:simpleContent>
<xs:extension base="s:Tokens">
<xs:attribute name="level" type="s:ErrorLevelType" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,51 @@
#
# Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
# one or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. KoSIT licenses this file
# to you 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.
#
# SLF4J's SimpleLogger configuration file for the command line client
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
# Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info".
org.slf4j.simpleLogger.defaultLogLevel=info
# Set to true if you want the current date and time to be included in output messages.
# Default is false, and will output the number of milliseconds elapsed since startup.
org.slf4j.simpleLogger.showDateTime=true
# The date and time format to be used in the output messages.
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
# If the format is not specified or is invalid, the default format is used.
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss
# Set to true if you want to output the current thread name.
# Defaults to true.
org.slf4j.simpleLogger.showThreadName=false
# Set to true if you want the Logger instance name to be included in output messages.
# Defaults to true.
org.slf4j.simpleLogger.showLogName=false
# Set to true if you want the last component of the name to be included in output messages.
# Defaults to false.
org.slf4j.simpleLogger.showShortLogName=false
org.slf4j.simpleLogger.levelInBrackets=true