diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index a26cfb6..bea581f 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -2,11 +2,13 @@
+
+
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 63fc954..ecaea3d 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -2,6 +2,7 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index aebea3c..3ab06a4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,9 @@
+
+
+
@@ -76,7 +79,7 @@
-
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ec9e2ce..7e1c78f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,19 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## next version (unreleased)
+## 1.3.0
### Added
-
-- Support java.xml.transform.Source/java.xml.transform.StreamSource as Input
+- Added a builder style configuration API to configure scenarios
+- Added an option to configure xml security e.g. to load from http sources or not from a specific repository
+(so loading is configurable less restrictive, default strategy is to only load from a local repository)
+- Support java.xml.transform.Source as Input
### Changed
-
- Inputs are NOT read into memory (e.g. Byte-Array) prior processing within the validator. This reduces memory consumption.
+- CheckConfiguration is deprecated now. Use Configuration.load(...) or Configuration.build(...)
+- Overall processing of xml files is based on Saxon s9api. No JAXP or SAX classes are used by
+the validator (this further improves performance and memory consumption)
-## UNRELEASED
### Fixed
-- Validator was creating invalid createReportInput xml in case of no scenrio match
+- Validator was creating invalid createReportInput xml in case of no scenrio match
+
## 1.2.0
### Added
diff --git a/README.md b/README.md
index 483ba8b..be743c4 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,8 @@
The validator is an XML validation-engine. It validates XML documents against XML Schema and Schematron Rules depending on self defined [scenarios](docs/configurations.md) which are used to fully configure the validation process.
The validator always outputs a [validation report in XML](docs/configurations.md#validators-report) including all validation errors and data about the validation.
+See [architecture](docs/architecture.md) for informations about the actual validation process.
+
## Packages
The validator distribution contains the following artifacts:
@@ -12,33 +14,21 @@ The validator distribution contains the following artifacts:
1. **validationtool-`-java8-standalone.jar**: Uber-JAR for standalone usage with Java JDK 8 containing all dependencies in one jar file. This file file *does not* contain JAXB and depends on the bundled version of the JDK.
1. **libs/***: directory containing all (incl. optional) dependencies of the validator
-## Build
-
-### Requirements
-
-* Maven > 3.0.0
-* Java > 8 update 111
-
-### Procedure
-
- `mvn install` generates two different packages in the `dist` directory:
## Validation Configurations
The validator is just an engine and does not know anything about XML Documents and has no own validation rules.
-
Validation rules and details are defined in [validation scenarios](docs/configurations.md) which are used to fully configure the validation process.
-
-All configurations are self-contained modules and deployed on their own.
+All configurations are self-contained modules which are deployed and developed on their own.
### Third Party Validation Configurations
Currently, there are two public third party validation configurations available.
-* Validation Configuration for [XRechnung](http://www.xoev.de/de/xrechnung) is available on
+* Validation Configuration for [XRechnung](http://www.xoev.de/de/xrechnung):
* Source code is available on [GitHub](https://github.com/itplr-kosit/validator-configuration-xrechnung)
* [Releases](https://github.com/itplr-kosit/validator-configuration-xrechnung/releases) can also be downloaded
-* Validation Configuration for XGewerbeanzeige
+* Validation Configuration for [XGewerbeanzeige](https://xgewerbeanzeige.de/)
* Source code is available on [GitHub](https://github.com/itplr-kosit/validator-configuration-xgewerbeanzeige)
* [Releases](https://github.com/itplr-kosit/validator-configuration-xgewerbeanzeige/releases) can also be downloaded
@@ -58,36 +48,40 @@ The general way using the CLI is:
java -jar validationtool--standalone.jar -s [OPTIONS] [FILE] [FILE] [FILE] ...
```
-You can more CLI options by
+The help option displays further CLI options to customize the process:
```shell
java -jar validationtool--standalone.jar --help
```
-A concrete example with a specific validator configuration can be found on [GitHub](https://github.com/itplr-kosit/validator-configuration-xrechnung)
+A concrete example with a specific validator configuration can be found on
+[GitHub](https://github.com/itplr-kosit/validator-configuration-xrechnung)
### Application User Interface (API / embedded usage)
-The validator can also be used in own Java Applications via the API. Details can be [found here](./docs/api.md).
+The validator can also be used in own Java Applications via the API. An example use of the API as follows:
+
+```java
+Path scenarios = Paths.get("scenarios.xml");
+Configuration config = Configuration.load(scenarios.toUri());
+Input document = InputFactory.read(testDocument);
+
+Check validator = new DefaultCheck(config);
+Result validationResult = validator.checkInput(document);
+
+// examine the result here
+```
+
+The [API documentation](./docs/api.md) shows further configuration options.
### Daemon-Mode
-You can also start the validator as an HTTP-Server. Just start it in _Daemon-Mode_ with the `-D` option.
+You can also start the validator as a HTTP-Server. Just start it in _Daemon-Mode_ with the `-D` option.
```shell
java -jar validationtool--standalone.jar -s -D
```
-Per default the HTTP-Server listens on _localhost_ at Port 8080.
-
-You can configure it with `-H` for IP Adress and `-P` for port number:
-
-```shell
-java -jar validationtool--standalone.jar -s -D -H 192.168.1.x -P 8081
-```
-
-You can HTTP-POST to `/` and the response will return the report document as defined in your validator configuration.
-
-Additionally there is the GET `/health` endpoint which can be used by monitoring systems.
+The [daemon documentation](./docs/daemon.md) shows more usage details and further configuration options.
diff --git a/docs/api.md b/docs/api.md
index 31d05ab..3b5e16f 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1,11 +1,10 @@
# Validator API
-The Validator offers an API which allows you to integrate Validator in your own applications.
+The Validator offers an API which allows you to integrate the Validator in your own applications.
## Dependency Management
-Currently, we *do not* deploy to Maven Central or similar. Hence you need to build and optionally deploy the Validator artifacts to your own
-shared (or local) repository (see for example [Maven Documentation](https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html)).
+Currently, we *do not* deploy to Maven Central or similar. Hence, you need to build and optionally deploy the Validator artifacts to your own shared (or local) repository (see for example [Maven Documentation](https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html)).
### Maven
@@ -29,8 +28,7 @@ dependencies {
## Usage
-Prerequisite for use is a valid [scenario definition](configurations.md) and the a folder with all necessary artifacts for validation
-(repository) either on the filesystem or on the classpath.
+Prerequisite for use is a valid [scenario definition](configurations.md) and the a folder with all necessary artifacts for validation (repository) either on the filesystem or on the classpath.
The following example demonstrates loading scenario.xml and whole configuration from classpath and validating one XML document:
@@ -56,7 +54,7 @@ public class StandardExample {
// Load scenarios.xml from classpath
URL scenarios = this.getClass().getClassLoader().getResource("scenarios.xml");
// Load the rest of the specific Validator configuration from classpath
- CheckConfiguration config = new CheckConfiguration(scenarios.toURI());
+ Configuration config = Configuration.load(scenarios.toURI());
// Use the default validation procedure
Check validator = new DefaultCheck(config);
// Validate a single document
@@ -86,20 +84,23 @@ public class StandardExample {
The `Result` interface has convenience methods to retrieve details about XSD validation errors and Schematron messages and other processing results. See
[Result.java](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/Result.java) for details.
-Initializing all XML artifacts and XSLT-executables is expensive. The `Check` instance is *threadsafe* and keeps all artifacts. Therefore,
-we recommend the re-use of an `Check` instance.
+Initializing all XML artifacts and XSLT-executables is expensive. The `Check` instance is *threadsafe* and keeps all artifacts. Therefore,
+we recommend the re-use of a `Check` instance.
-The only input `de.kosit.validationtool.api.Input` which can be created by various methods of `de.kosit.validationtool.api.InputFactory`.
-The `InputFactory` calculates a hash sum for each Input which is also written to the Report. _SHA-256_ from the JDK is the default algorithm.
-It can be changed using the `read`-methods of `InputFactory`.
+Beside the validator's configuration the only input are instances of [Input](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/Input.java)
+which can be created by various methods of the [InputFactory](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/InputFactory.java).
+The [InputFactory](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/InputFactory.java)
+ calculates a hash sum for each Input which is also written to the Report. _SHA-256_ from the JDK is the default algorithm.
+It can be changed using other `read`-methods of [InputFactory](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/InputFactory.java).
-The main interface [Check.java](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/Check.java)
+The main interface [Check.java](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/Check.java)
allows using a batch interface (processing list of [Inputs](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/Input.java)).
However, there is no parallel processing implemented at the moment.
## Accept Recommendation and Accept Match
-A tri-state Object `AcceptRecommendation` can be retrieved from the `Result` using `getAcceptRecommendation()`.
+A tri-state object [AcceptRecommendation](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/AcceptRecommendation.java)
+can be retrieved from the [Result](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/api/Result.java) using `getAcceptRecommendation()`.
The three defined states are:
@@ -109,15 +110,101 @@ The three defined states are:
The accept recommendation is based on either:
-1. schema and schematron validation result
-1. if configured based on _acceptMatch_ configuration of the scenario (see below)
+1. Schema and Schematron validation results
+1. or on _acceptMatch_ configuration of the scenario (see below)
-### Accept Match in Scenario Configuration
+### Accept match in scenario configuration
-For your own configuration you can add an `acceptMatch` element in each scenario. It can contain in XPATH expression over your own
-defined `Report` to compute a boolean. An XPATH expression evaluating to true will lead to an `ACCEPTABLE` and otherwise to a `REJECT`
-recommendation.
+For your own configuration you can add an `acceptMatch` element in each scenario. It can contain an XPATH expression over your own defined `Report` to compute a boolean value. An XPATH expression evaluating to true will lead to an `ACCEPTABLE` and otherwise to a `REJECT` recommendation.
-This allows to have control over what validation result is to be considered _acceptable_ for your own application context. E.g. you can
-overrule schematron validation errors with _acceptMatch_ configuration and consider certain errors as _acceptable_. Nevertheless you can *not*
-overrule schema errors with accept match.
+This allows to have control over what validation result is to be considered _acceptable_ for your own application context. E.g. you can overrule Schematron validation errors with _acceptMatch_ configuration and consider certain errors as _acceptable_. Nevertheless you can *not* overrule schema errors with accept match.
+
+## Building scenario configurations with the Builder API
+
+Instead of pre-configured [scenario files](configurations.md) it is possible to create a validator configuration using a builder API. A valid configuration consists of the following:
+
+* at least one valid scenario configuration, this includes
+ * a valid match configuration to identify/activate this scenario
+ * a valid XML schema configuration
+ * a valid report transformation configuration
+ * valid schematron validation configurations (optional)
+ * a valid accept match configuration to compute acceptance information (optional)
+* a valid fallback scenario configuration
+
+A simple configuration looks like this:
+
+```java
+import static de.kosit.validationtool.config.ConfigurationBuilder.*;
+import de.kosit.validationtool.api.Configuration;
+import java.net.URI;
+import java.nio.file.Path;
+
+public class MyValidator {
+
+ public static void main(String[] args) {
+ Configuration config = Configuration.create().name("myconfiguration")
+ .with(scenario("firstScenario")
+ .match("//myNode")
+ .validate(schema("Sample Schema").schemaLocation(URI.create("simple.xsd")))
+ .validate(schematron("my rules").source("myRules.xsl"))
+ .with(report("my report").source("report.xsl")))
+ .with(fallback().name("default-report").source("fallback.xsl"))
+ .useRepository(Paths.get("/opt/myrepository"))
+ .build();
+ Check validator = new DefaultCheck(config);
+ // .. run your checks
+ }
+}
+```
+
+The build API provides various methods to configure your scenarios and the validation process.
+
+It is also possible to provide runtime artifacts like `XsltExecutable`, `XPathExecutable` or `Schema` to configure the validator.
+This gives you complete control over loading these artifacts.
+
+---
+**Note:** Creating these objects requires usage of the same instance of the saxon `Processor` as used during validation later. Therefore, you need to supply a custom `ResolvingConfigurationStrategy` or use the internal one to create these objects. See below.
+
+---
+
+## Configure SML Security and Resolving
+
+When using XML related technologies you are supposed to handle certain security issues properly. The KoSIT validator pursues a rather strict strategy. The default configuration:
+
+* disables DTD validation completely
+* allows loading/resolving only from a configured local content repository (a specific folder)
+* tries to prevent known XML security issues (see [OWASP XML_Security_Cheat_Sheet.html](https://cheatsheetseries.owasp.org/cheatsheets/XML_Security_Cheat_Sheet.html))
+* only works with OpenJDK based XML stacks
+
+However, you can configure certain aspects related to resolving and security yourself. The validator uses a single interface for accessing or creating the necessary XML API objects like `SchemaFactory`, `Validator`,`URIResolver` or `Processor`: [ResolvingConfigurationStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java)
+
+There are 3 implementations available out of the box:
+
+1. [StrictRelativeResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingStrategy.java)
+which is the **default**, prevents known XML attacks and only allows loading from a specific local repository location
+1. [StrictLocalResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingStrategy.java)
+which opens the first strategy to load resources from local locations
+1. [RemoteResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategy.java)
+which further opens the second to load resources also from remote locations via http and https
+
+You can configure usage of one of these implementations using the `ResolvingMode` via
+
+````java
+Conifuguration config = Configuration.load(URI.create("myscenarios.xml"))
+ .resolvingMode(ResolvingMode.STRICT_LOCAL)
+ .build();
+````
+
+If you decide to implement your own strategy, you can configure this via:
+
+````java
+Conifuguration config = Configuration.load(URI.create("myscenarios.xml"))
+ .resolvingStrategy(new MyCustomResolvingConfigurationStrategy())
+ .build();
+````
+
+---
+
+:warning: **Attention:** If you decide to implement a custom strategy you need to handle XML security risks on your own. Please make sure, that you prevent XXE and other kind of attacks. Consider using [BaseResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/BaseResolvingStrategy.java) and the protected methods within to disable certain features.
+
+---
diff --git a/docs/contribute.md b/docs/contribute.md
index 191f2e4..76d0d95 100644
--- a/docs/contribute.md
+++ b/docs/contribute.md
@@ -25,3 +25,14 @@ due to historical reasons. This not only works in Eclipse but also in IntelliJ (
The configuration can be found in `.settings`-directory. For IntelliJ this is all set up. Additionally this should work in Eclipse out of the box.
Another potential usage scenario would be to integrate the formatter via git hooks into the commit-pipeline (e.g [Example Hook](https://gist.github.com/ktoso/708972) ).
For other IDEs you are on your own.
+
+## Build
+
+### Requirements
+
+* Maven > 3.0.0
+* Java > 8 update 111
+
+### Procedure
+
+ `mvn install` generates two different packages in the `dist` directory:
\ No newline at end of file
diff --git a/docs/daemon.md b/docs/daemon.md
new file mode 100644
index 0000000..8702caa
--- /dev/null
+++ b/docs/daemon.md
@@ -0,0 +1,108 @@
+# Validator HTTP Daemon
+
+You can start the validator as an HTTP-Server. This server is based on the [JDK HTTP server](https://docs.oracle.com/javase/8/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/HttpServer.html) functionality
+and should work with OpenJDK based distributions. Keep this in mind, if you want to deploy this in production scenarios with heavy load.
+
+## Basic usage
+
+To use the validator daemon as is, start the _Daemon-Mode_ with the `-D` option and supply a suitable
+ [validator configuration](configurations.md).
+
+```shell
+java -jar validationtool--standalone.jar -s -D
+```
+
+Per default the HTTP-Server listens on _localhost_ at Port 8080.
+
+You can configure the daemon with `-H` for IP Adress and `-P` for port number:
+
+```shell
+java -jar validationtool--standalone.jar -s -D -H 192.168.1.x -P 8081
+```
+
+## Customized usage
+
+You can also leverage the API to create a customized version of the daemon. Just instantiate, configure and start the daemon like this:
+
+````java
+Configuration config = Configuration.load(...);
+
+Daemon daemon = new Daemon();
+daemon.setPort("8090");
+// further config goes here
+daemon.startServer(config);
+````
+
+The possible customizations are:
+
+* `bindAddress` - the interface to bind the daemon to
+* `port` - the port to expose
+* `threadCount` - number of worker threads to handle results
+* `guiEnabled` - enable or disable the basic GUI with usage information
+
+## Access the HTTP interface
+
+The validation service listens to `POST`-requests on any server URL. You need to supply the xml/object to validate in the HTTP body.
+
+The service expects a single XML input in the HTTP body, e.g. `multipart/form-data` is not supported.
+
+Examples:
+
+* `cURL`
+```shell script
+curl --location --request POST 'http://localhost:8080' \
+--header 'Content-Type: application/xml' \
+--data-binary '@/target.xml'
+```
+
+* `java` (Apache HttpClient)
+```java
+HttpClient httpClient = HttpClientBuilder.create().build();
+HttpPost postRequest = new HttpPost("http://localhost:8080/");
+FileEntity entity = new FileEntity(Paths.get("some.xml").toFile(), ContentType.APPLICATION_XML);
+postRequest.setEntity(entity);
+HttpResponse response = httpClient.execute(postRequest);
+System.out.println(IOUtils.toString(response.getEntity().getContent()));
+```
+
+* `javascript`
+```javascript
+var myHeaders = new Headers();
+myHeaders.append("Content-Type", "application/xml");
+
+var file = "";
+
+var requestOptions = {
+ method: 'POST',
+ headers: myHeaders,
+ body: file,
+ redirect: 'follow'
+};
+
+fetch("http://localhost:8080", requestOptions)
+ .then(response => response.text())
+ .then(result => console.log(result))
+ .catch(error => console.log('error', error));
+```
+
+## Authorization
+There is no mechanism to check, whether client is allowed to consume the service or not. The user is responsible to secure access to the service.
+This can be done using infrastructural service like a forwarding proxies (e.g. `nginx` or `Apache http server`) or by implementing a custom solution.
+
+## Monitoring and administration
+
+The validation service can be integrated in monitoring solutions like `Icinga` or `Nagios`. There is a `health` endpoint exposed under `/server/health` wich returns some basic information about the service like memory consumption, general information about the version and a status `UP` as an XML file.
+
+## GUI
+
+The daemon provides a simple GUI when issuing `GET` requests providing the following:
+
+ 1. usage information
+ 1. information about the actual [validator configuration](configurations.md) used by this daemon
+ 1. a simple form to test the daemon with custom inputs
+
+ The GUI can be disabled with using the API (see above) or via CLI
+
+ ```shell script
+java -jar validationtool--standalone.jar -s -D --disable-gui
+```
diff --git a/pom.xml b/pom.xml
index ab51b8e..694557a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,7 +39,7 @@
UTF-8
- 0.8.4
+ 0.8.51.18.89.9.1-31.7.25
@@ -129,6 +129,12 @@
1.0.0test
+
+org.apache.httpcomponents
+httpclient
+ 4.5.8
+
+
@@ -145,6 +151,26 @@
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.1.0
+
+
+ reserve-network-port
+
+ reserve-network-port
+
+ process-resources
+
+
+ validator.server.port
+ jacoco.tcp.port
+
+
+
+
+ org.apache.maven.pluginsmaven-enforcer-plugin
@@ -266,28 +292,6 @@
-
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
- 3.0.0
-
-
- add-source
- generate-sources
-
- add-source
-
-
-
- src/generated/java
-
-
-
-
-
- org.jvnet.jaxb2.maven2
@@ -304,7 +308,6 @@
truesrc/main/model/xsdsrc/main/model/binding
- src/generated/javafalse-Xinheritance
@@ -332,14 +335,40 @@
- prepareJacocoJUnitArgLine
+ prepareJacocoSurefireArgLineprepare-agent
- jacocoArgumentsJUnit
+ jacocoSurefire
+
+ prepareJacocoFailsafeArgLine
+ pre-integration-test
+
+ prepare-agent
+
+
+ jacocoFailsafe
+
+ localhost
+ ${jacoco.tcp.port}
+
+
+
+ dump
+ post-integration-test
+
+ dump
+
+
+
+ localhost
+ ${jacoco.tcp.port}
+ true
+
+ generateJacocoReport
@@ -355,7 +384,7 @@
2.22.0
- -Dfile.encoding=UTF-8 ${jacocoArgumentsJUnit}
+ -Dfile.encoding=UTF-8 ${jacocoSurefire}
@@ -387,20 +416,21 @@
exec-maven-plugin1.6.0
-
+ run
- pre-integration-test
-
+ pre-integration-test
+ exec
-
+
-
+ javatruetruetrue
+ ${jacocoFailsafe}-classpathde.kosit.validationtool.cmd.CommandLineApplication
@@ -408,6 +438,8 @@
${project.build.testOutputDirectory}/examples/simple/scenarios.xml-r${project.build.testOutputDirectory}/examples/simple/repository
+ --port
+ ${validator.server.port}-D
@@ -421,7 +453,8 @@
-
+
+ ${jacoco.tcp.port}
@@ -445,6 +478,10 @@
integration-testverify
+
+
+ -Dfile.encoding=UTF-8 -Ddaemon.port=${validator.server.port}
+
diff --git a/src/main/java/de/kosit/validationtool/api/AcceptRecommendation.java b/src/main/java/de/kosit/validationtool/api/AcceptRecommendation.java
index c491201..479733d 100644
--- a/src/main/java/de/kosit/validationtool/api/AcceptRecommendation.java
+++ b/src/main/java/de/kosit/validationtool/api/AcceptRecommendation.java
@@ -1,21 +1,22 @@
package de.kosit.validationtool.api;
/**
- * Tri-state describtion of a Recommendation.
+ * Tri-state recommendation whether to accept the {@link Input} or not.
*/
public enum AcceptRecommendation {
+
/**
* The evaluation of the overall validation could not be computed.
*/
UNDEFINED,
/**
- * Recommendation is to accept input based on the evaluation of the overall validation.
+ * Recommendation is to accept {@link Input} based on the evaluation of the overall validation.
*/
ACCEPTABLE,
/**
- * Recommendation is to reject input based on the evaluation of the overall validation.
+ * Recommendation is to reject {@link Input} based on the evaluation of the overall validation.
*/
REJECT
}
diff --git a/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java b/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java
index e507daf..10b3d56 100644
--- a/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java
+++ b/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java
@@ -20,24 +20,30 @@
package de.kosit.validationtool.api;
import java.net.URI;
+import java.util.List;
+import java.util.Map;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
-import de.kosit.validationtool.impl.RelativeUriResolver;
+import de.kosit.validationtool.config.ConfigurationLoader;
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.Scenario;
/**
* Zentrale Konfigration einer Prüf-Instanz.
*
* @author Andreas Penski
+ * @deprecated since 2.0 use {@link Configuration} instead
*/
@Getter
@Setter
@Slf4j
@RequiredArgsConstructor
-public class CheckConfiguration {
+@Deprecated
+public class CheckConfiguration implements Configuration {
/**
* URL, die auf die scenerio.xml Datei zeigt.
@@ -49,22 +55,51 @@ public class CheckConfiguration {
*/
private URI scenarioRepository;
+ private ConfigurationLoader loader;
- /**
- * Liefert das Repository mit den Artefakten der einzelnen Szenarien.
- *
- * @return uri die durch entsprechende resolver aufgelöst werden kann
- */
- public URI getScenarioRepository() {
- if (this.scenarioRepository == null) {
- this.scenarioRepository = createDefaultRepository();
+ private Configuration delegate;
+
+ private Configuration getDelegate() {
+ if (this.delegate == null) {
+ this.delegate = Configuration.load(this.scenarioDefinition, this.scenarioRepository).build();
}
- return this.scenarioRepository;
+ return this.delegate;
}
- private URI createDefaultRepository() {
- log.info("Creating default scenario repository (alongside scenario definition)");
- return RelativeUriResolver.resolve(URI.create("."), this.scenarioDefinition);
+ @Override
+ public List getScenarios() {
+ return getDelegate().getScenarios();
}
+ @Override
+ public Scenario getFallbackScenario() {
+ return getDelegate().getFallbackScenario();
+ }
+
+ @Override
+ public String getDate() {
+ return getDelegate().getDate();
+ }
+
+ @Override
+ public Map getAdditionalParameters() {
+ return this.delegate.getAdditionalParameters();
+ }
+
+ @Override
+ public String getName() {
+ return getDelegate().getName();
+ }
+
+ @Override
+ public String getAuthor() {
+ return getDelegate().getAuthor();
+ }
+
+
+
+ @Override
+ public ContentRepository getContentRepository() {
+ return getDelegate().getContentRepository();
+ }
}
diff --git a/src/main/java/de/kosit/validationtool/api/Configuration.java b/src/main/java/de/kosit/validationtool/api/Configuration.java
new file mode 100644
index 0000000..9aa6f70
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/api/Configuration.java
@@ -0,0 +1,108 @@
+package de.kosit.validationtool.api;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import de.kosit.validationtool.config.ConfigurationBuilder;
+import de.kosit.validationtool.config.ConfigurationLoader;
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.Scenario;
+
+/**
+ * Configuration of the actual {@link Check} instance. This is an interface and can be implemented by custom
+ * configuration classes. There are two implementations supported out of the box:
+ *
+ *
+ *
{@link ConfigurationLoader} implements loading {@link Check} configurations from a scenario.xml file
+ *
Using a builder style api {@link de.kosit.validationtool.config.ConfigurationBuilder}to configure the
+ * {@link Check}
+ *
+ *
+ * Both methods can be used via convinience methods. See below.
+ *
+ * @author Andreas Penski
+ */
+
+public interface Configuration {
+
+ /**
+ * Returns a list of configured scenarios.
+ *
+ * @return the list of scenarios
+ */
+ List getScenarios();
+
+ /**
+ * Returns the configured fallback scenario to use, in case no configured scenario match.
+ *
+ * @return the fallback scenario
+ */
+ Scenario getFallbackScenario();
+
+ /**
+ * Returns the author of this configuration.
+ *
+ * @return the author
+ */
+ String getAuthor();
+
+ /**
+ * Returns the name of the specification
+ *
+ * @return the name
+ */
+ String getName();
+
+ /**
+ * The creation date of the config
+ *
+ * @return the date
+ */
+ String getDate();
+
+ /**
+ * Add some additional parameters to the validator configuration. Parameter usage depends on actual implementation of
+ * {@link Check}
+ *
+ * @return
+ */
+ Map getAdditionalParameters();
+
+ /**
+ * The content repository including resolving strategies.
+ *
+ * @return the configured {@link ContentRepository}
+ */
+ ContentRepository getContentRepository();
+
+ /**
+ * Loads an XML based scenario definition from the file specified via URI.
+ *
+ * @param scenarioDefinition the XML file with scenario definition
+ * @return the loaded configuration
+ */
+ static ConfigurationLoader load(final URI scenarioDefinition) {
+ return load(scenarioDefinition, null);
+ }
+
+ /**
+ * Loads an XML based scenario definition from the file with an specific repository / source location specified via
+ * URIs.
+ *
+ * @param scenarioDefinition the XML file with scenario definition
+ * @return the loaded configuration
+ */
+ static ConfigurationLoader load(final URI scenarioDefinition, final URI repository) {
+ return new ConfigurationLoader(scenarioDefinition, repository);
+ }
+
+ /**
+ * Creates a {@link Configuration} based on a builder style API using {@link ConfigurationBuilder}
+ *
+ * @return the Builder
+ */
+ static ConfigurationBuilder create() {
+ return new ConfigurationBuilder();
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/api/Input.java b/src/main/java/de/kosit/validationtool/api/Input.java
index ca27ebf..83794b6 100644
--- a/src/main/java/de/kosit/validationtool/api/Input.java
+++ b/src/main/java/de/kosit/validationtool/api/Input.java
@@ -20,7 +20,6 @@
package de.kosit.validationtool.api;
import java.io.IOException;
-import java.io.InputStream;
import javax.xml.transform.Source;
@@ -54,10 +53,10 @@ public interface Input {
String getDigestAlgorithm();
/**
- * Opens a new {@link InputStream } for this input which carries the actual data
+ * Creates a new {@link Source } for this input which carries the actual data
*
- * @return an open {@link InputStream}
- * @throws IOException on I/O while opening the stream
+ * @return an open {@link Source}
+ * @throws IOException on I/O while opening the source
*/
Source getSource() throws IOException;
diff --git a/src/main/java/de/kosit/validationtool/api/InputFactory.java b/src/main/java/de/kosit/validationtool/api/InputFactory.java
index 77d99d0..b491cb2 100644
--- a/src/main/java/de/kosit/validationtool/api/InputFactory.java
+++ b/src/main/java/de/kosit/validationtool/api/InputFactory.java
@@ -54,10 +54,6 @@ public class InputFactory {
static final String DEFAULT_ALGORITH = "SHA-256";
- private static final int EOF = -1;
-
- private static final int DEFAULT_BUFFER_SIZE = 4096;
-
private static final String MESSAGE_OPEN_STREAM_ERROR = "Can not open stream from";
@Getter
@@ -108,7 +104,6 @@ public class InputFactory {
return read(file, DEFAULT_ALGORITH);
}
-
/**
* Liest einen Prüfling von der übergebenen URI. Es wird der Default-Prüfsummenalgorithmus zur Ermittlung der Prüfsumme
* genutzt.
@@ -169,8 +164,10 @@ public class InputFactory {
}
/**
- * Reads a test document from a {@link Source}.
- *
+ * Reads a test document from a {@link Source}.
+ * Note: computing the hashcode is only supported for {@link StreamSource}. You can not directly use other {@link Source
+ * Soures}. You need to supply the hashcode for identification then.
+ *
* @param source source
* @return an {@link Input}
*/
@@ -180,6 +177,9 @@ public class InputFactory {
/**
* Reads a test document from a {@link Source} using a specified digest algorithm.
+ *
+ * Note: computing the hashcode is only supported for {@link StreamSource}. You can not directly use other {@link Source
+ * Soures}. You need to supply the hashcode for identification then.
*
* @param source source
* @param digestAlgorithm the digest algorithm
diff --git a/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java b/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java
new file mode 100644
index 0000000..86cad76
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java
@@ -0,0 +1,70 @@
+package de.kosit.validationtool.api;
+
+import java.net.URI;
+
+import javax.xml.transform.URIResolver;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import net.sf.saxon.s9api.Processor;
+
+/**
+ * Centralized construction and configuration of XML related infrastructure components. This interface allows to use
+ * custom implementations and configurations of internal xml related factories and objects.
+ *
+ * The KoSIT Validator provides out of the box implementations with various security levels based on openjdk SAX stack.
+ *
+ * If you decide to implement a custom strategy, please be aware of XML security within your stack. The validator
+ * components beyond this strategy asume secured implementation of the interfaces provided by this strategy. There is no
+ * effort to mitigate or prevent xml related security issues such as XXE, loading external sources etc. Your would be
+ * responsible for this!
+ *
+ * @see de.kosit.validationtool.impl.ResolvingMode
+ * @author Andreas Penski
+ */
+public interface ResolvingConfigurationStrategy {
+
+ /**
+ * Creates a preconfigured {@link SchemaFactory} for loading {@link javax.xml.validation.Schema} objects. The
+ * implementation is responsible for xml security. Take care
+ *
+ * @return preconfigured {@link SchemaFactory}
+ */
+ SchemaFactory createSchemaFactory();
+
+ /**
+ * Returns a preconfigured {@link Processor Saxon Processor} for various tasks within the Validator. The validator
+ * leverages the saxon s9api for internal processing e.g. xml reading and writing. So this is the main object to secure
+ * for reading, transforming and writing xml files.
+ *
+ * Note: you need exactly one instance for all validator related processing.
+ *
+ * @return a preconfigured {@link Processor}
+ */
+ Processor getProcessor();
+
+ /**
+ * Creates a specific implementation for resolving referenced objects in XML files. The URIResolver is used for
+ * dereferencing an absolute URI (after resolution) to return a {@link javax.xml.transform.Source}. It can be
+ * used for resolving relative URIs against a base URI or restrict access to certain URIs.
+ *
+ * This URIResolver is used to dereference the URIs appearing in xsl:import, xsl:include, and
+ * xsl:import-schema declarations.
+ *
+ *
+ * @param scenarioRepository an optional repository, your implementation might not need this
+ * @return a preconfigured {@link URIResolver}
+ */
+ URIResolver createResolver(URI scenarioRepository);
+
+ /**
+ * Creates a preconfigured {@link Validator } instance for a given schema for xml file validation. The implementation
+ * takes care about security and reference resolving strategies.
+ *
+ * @param schema the scheme to create a {@link Validator} for
+ * @return a preconfigured {@link Validator}
+ */
+ Validator createValidator(Schema schema);
+
+}
diff --git a/src/main/java/de/kosit/validationtool/api/Result.java b/src/main/java/de/kosit/validationtool/api/Result.java
index dcbe19d..a32adb2 100644
--- a/src/main/java/de/kosit/validationtool/api/Result.java
+++ b/src/main/java/de/kosit/validationtool/api/Result.java
@@ -72,7 +72,7 @@ public interface Result {
/**
* Returns {@link org.oclc.purl.dsdl.svrl.FailedAssert FailedAsserts} of a schematron evaluation.
- *
+ *
* @return list of {@link org.oclc.purl.dsdl.svrl.FailedAssert FailedAsserts}, if any, empty list otherwise
*/
List getFailedAsserts();
@@ -93,7 +93,7 @@ public interface Result {
/**
* Returns true, if schematron has been checked and the result does not contain any {@link FailedAssert FailedAsserts}.
- *
+ *
* @return true, if valid
*/
boolean isSchematronValid();
diff --git a/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java b/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java
index 21527f9..2e254aa 100644
--- a/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java
+++ b/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java
@@ -42,12 +42,15 @@ import org.apache.commons.lang3.StringUtils;
import lombok.extern.slf4j.Slf4j;
-import de.kosit.validationtool.api.CheckConfiguration;
+import de.kosit.validationtool.api.Configuration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.cmd.assertions.Assertions;
+import de.kosit.validationtool.config.ConfigurationLoader;
+import de.kosit.validationtool.daemon.Daemon;
import de.kosit.validationtool.impl.ConversionService;
-import de.kosit.validationtool.impl.ObjectFactory;
+
+import net.sf.saxon.s9api.Processor;
/**
* Commandline Version des Prüftools. Parsed die Kommandozeile und führt die konfigurierten Aktionen aus.
@@ -93,6 +96,9 @@ public class CommandLineApplication {
private static final Option WORKER_COUNT = Option.builder("T").longOpt("threads").hasArg()
.desc("Number of threads processing validation requests").build();
+ private static final Option DISABLE_GUI = Option.builder("G").longOpt("disable-gui").desc("Disables the GUI of the daemon mode")
+ .build();
+
public static final int DAEMON_SIGNAL = 100;
private static final Option PRINT_MEM_STATS = Option.builder("m").longOpt("memory-stats").desc("Prints some memory stats").build();
@@ -169,9 +175,12 @@ public class CommandLineApplication {
private static int startDaemonMode(final CommandLine cmd) {
final Option[] unavailable = new Option[] { PRINT, CHECK_ASSERTIONS, DEBUG, OUTPUT, EXTRACT_HTML };
warnUnusedOptions(cmd, unavailable, true);
- final Daemon validDaemon = new Daemon(determineDefinition(cmd), determineRepository(cmd), determineHost(cmd), determinePort(cmd),
- determineThreads(cmd));
- validDaemon.startServer();
+ final ConfigurationLoader config = Configuration.load(determineDefinition(cmd), determineRepository(cmd));
+ final Daemon validDaemon = new Daemon(determineHost(cmd), determinePort(cmd), determineThreads(cmd));
+ if (cmd.hasOption(DISABLE_GUI.getOpt())) {
+ validDaemon.setGuiEnabled(false);
+ }
+ validDaemon.startServer(config.build());
return DAEMON_SIGNAL;
}
@@ -203,25 +212,26 @@ public class CommandLineApplication {
long start = System.currentTimeMillis();
final Option[] unavailable = new Option[] { HOST, PORT, WORKER_COUNT };
warnUnusedOptions(cmd, unavailable, false);
- final CheckConfiguration d = new CheckConfiguration(determineDefinition(cmd));
- d.setScenarioRepository(determineRepository(cmd));
- final InternalCheck check = new InternalCheck(d);
+ final Configuration config = Configuration.load(determineDefinition(cmd), determineRepository(cmd)).build();
+
+ final InternalCheck check = new InternalCheck(config);
final Path outputDirectory = determineOutputDirectory(cmd);
+ final Processor processor = config.getContentRepository().getProcessor();
if (cmd.hasOption(EXTRACT_HTML.getOpt())) {
- check.getCheckSteps().add(new ExtractHtmlContentAction(check.getContentRepository(), outputDirectory));
+ check.getCheckSteps().add(new ExtractHtmlContentAction(processor, outputDirectory));
}
- check.getCheckSteps().add(new SerializeReportAction(outputDirectory));
+ check.getCheckSteps().add(new SerializeReportAction(outputDirectory, processor));
if (cmd.hasOption(SERIALIZE_REPORT_INPUT.getOpt())) {
check.getCheckSteps().add(new SerializeReportInputAction(outputDirectory, check.getConversionService()));
}
if (cmd.hasOption(PRINT.getOpt())) {
- check.getCheckSteps().add(new PrintReportAction());
+ check.getCheckSteps().add(new PrintReportAction(processor));
}
if (cmd.hasOption(CHECK_ASSERTIONS.getOpt())) {
final Assertions assertions = loadAssertions(cmd.getOptionValue(CHECK_ASSERTIONS.getOpt()));
- check.getCheckSteps().add(new CheckAssertionAction(assertions, ObjectFactory.createProcessor()));
+ check.getCheckSteps().add(new CheckAssertionAction(assertions, processor));
}
if (cmd.hasOption(PRINT_MEM_STATS.getOpt())) {
check.getCheckSteps().add(new PrintMemoryStats());
@@ -374,6 +384,7 @@ public class CommandLineApplication {
options.addOption(CHECK_ASSERTIONS);
options.addOption(PRINT_MEM_STATS);
options.addOption(WORKER_COUNT);
+ options.addOption(DISABLE_GUI);
return options;
}
}
diff --git a/src/main/java/de/kosit/validationtool/cmd/Daemon.java b/src/main/java/de/kosit/validationtool/cmd/Daemon.java
deleted file mode 100644
index 560c039..0000000
--- a/src/main/java/de/kosit/validationtool/cmd/Daemon.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package de.kosit.validationtool.cmd;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicLong;
-
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
-import org.w3c.dom.Document;
-
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import com.sun.net.httpserver.HttpServer;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-
-import de.kosit.validationtool.api.Check;
-import de.kosit.validationtool.api.CheckConfiguration;
-import de.kosit.validationtool.api.InputFactory;
-import de.kosit.validationtool.impl.DefaultCheck;
-import de.kosit.validationtool.impl.ObjectFactory;
-import de.kosit.validationtool.impl.input.SourceInput;
-import de.kosit.validationtool.model.scenarios.Scenarios;
-
-/**
- * HTTP-Daemon für die Bereitstellung der Prüf-Funktionalität via http.
- *
- * @author Roula Antoun
- */
-@RequiredArgsConstructor
-@Setter
-@Getter
-@Slf4j
-class Daemon {
-
- /**
- * Wir benötigen einen Handler, der zur Verarbeitung von HTTP-Anforderungen aufgerufen wird um hier die Verarbeitung des
- * POST Request zu realisieren.
- */
- @Slf4j
- private static class HttpServerHandler implements HttpHandler {
-
- private static final AtomicLong counter = new AtomicLong(0);
-
- private final Check implemenation;
-
- HttpServerHandler(final Check check) {
- this.implemenation = check;
- }
-
- /**
- * Methode, die eine gegebene Anforderung verarbeitet und eine entsprechende Antwort generiert
- *
- * @param httpExchange kapselt eine empfangene HTTP-Anforderung und eine Antwort, die in einem Exchange generiert werden
- * soll.
- */
- @Override
- public void handle(final HttpExchange httpExchange) throws IOException {
- try {
- log.debug("Incoming request");
- final String requestMethod = httpExchange.getRequestMethod();
- if (requestMethod.equals("POST")) {
- final InputStream inputStream = httpExchange.getRequestBody();
- final SourceInput serverInput = (SourceInput) InputFactory.read(inputStream, "Prüfling" + counter.incrementAndGet());
-
- if (inputStream.available() > 0) {
- writeOutputstreamArray(httpExchange, this.implemenation.check(serverInput));
- } else {
- writeError(httpExchange, 400, "XML-Inhalt erforderlich!");
- }
-
- } else {
- writeError(httpExchange, 405, "Es ist nur die POST-Methode erlaubt!");
- }
- } catch (final Exception e) {
- writeError(httpExchange, 500, "Interner Fehler bei der Verarbeitung des Requests: " + e.getMessage());
- log.error("Es ist ein Fehler aufgetreten. Das Dokument kann nicht geprüft werden", e);
- }
- }
-
- }
-
- /**
- * Wir benötigen einen Handler, der zur Verarbeitung von HTTP-Anforderungen aufgerufen wird , und hier für Verarbeitung
- * das GET Request um Health-Endpunkt zu erstellen. Die Klasse HealthHandler implementiert diese Schnittstelle
- */
- @Slf4j
- static class HealthHandler implements HttpHandler {
-
- private final Scenarios scenarios;
-
- HealthHandler(final Scenarios scenarios) {
- this.scenarios = scenarios;
- }
-
- @Override
- public void handle(final HttpExchange httpExchange) throws IOException {
- final Health health = new Health(this.scenarios);
- final Document doc = health.writeHealthXml();
- try {
- writeOutputstreamArray(httpExchange, doc);
- } catch (final TransformerException e) {
- writeError(httpExchange, 500, e.getMessage());
- log.error("Fehler beim Erzeugen der Status-Information", e);
- }
- }
- }
-
- private final URI scenarioDefinition;
-
- private final URI repository;
-
- private final String hostName;
-
- private final int port;
-
- private final int threadCount;
-
- /**
- * Methode, die die Antwort als String-Text schreibt
- *
- * @param httpExchange um den Antwort Body zu erhalten
- * @param rCode der Code-Status
- * @param response die String antwort, die ich anzeigen möchte
- */
- private static void writeError(final HttpExchange httpExchange, final int rCode, final String response) throws IOException {
- httpExchange.sendResponseHeaders(rCode, response.length());
- final OutputStream os = httpExchange.getResponseBody();
- os.write(response.getBytes());
- os.close();
- }
-
- /**
- * Methode, die die Antwort als String-Text schreibt
- *
- * @param httpExchange um den Antwort Body zu erhalten
- * @param doc der Report
- */
- private static void writeOutputstreamArray(final HttpExchange httpExchange, final Document doc)
- throws IOException, TransformerException {
- final byte[] bytes = serialize(doc);
- final OutputStream os = httpExchange.getResponseBody();
- httpExchange.getResponseHeaders().add("Content-Type", "application/xml");
- httpExchange.sendResponseHeaders(200, bytes.length);
- os.write(bytes);
- os.close();
- log.debug("Xml File erzeugen ist Fertig ");
- }
-
- /**
- * Methode zum Serialisieren des Dokuments.
- *
- * @param report Vom Typ Dokument, aka Report .
- */
- private static byte[] serialize(final Document report) throws TransformerException {
-
- try ( final ByteArrayOutputStream bArrayOS = new ByteArrayOutputStream() ) {
- final DOMSource source = new DOMSource(report);
- final StreamResult streamResult = new StreamResult(bArrayOS);
- final Transformer transformer = ObjectFactory.createTransformer(true);
- transformer.transform(source, streamResult);
- return bArrayOS.toByteArray();
- } catch (final IOException e) {
- log.error("Report {}", e.getMessage(), e);
- throw new IllegalStateException(e);
- }
- }
-
- /**
- * Methode zum Starten des Servers
- */
- void startServer() {
- final CheckConfiguration config = new CheckConfiguration(this.scenarioDefinition);
- config.setScenarioRepository(this.repository);
- HttpServer server = null;
- try {
- server = HttpServer.create(new InetSocketAddress(this.hostName, this.port), 0);
- final DefaultCheck check = new DefaultCheck(config);
- server.createContext("/", new HttpServerHandler(check));
- server.createContext("/health", new HealthHandler(check.getRepository().getScenarios()));
- server.setExecutor(Executors.newFixedThreadPool(this.threadCount));
- server.start();
- log.info("Server unter Port {} ist erfolgreich gestartet", this.port);
- } catch (final IOException e) {
- log.error("Fehler beim HttpServer erstellen: {}", e.getMessage(), e);
- }
- }
-}
diff --git a/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java b/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java
index 09e84b6..537a3be 100644
--- a/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java
+++ b/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java
@@ -24,10 +24,10 @@ import java.nio.file.Path;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.HtmlExtractor;
import de.kosit.validationtool.impl.tasks.CheckAction;
+import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
@@ -49,12 +49,12 @@ class ExtractHtmlContentAction implements CheckAction {
private HtmlExtractor htmlExtraction;
- private ContentRepository repository;
+ private Processor processor;
- public ExtractHtmlContentAction(final ContentRepository repository, final Path outputDirectory) {
+ public ExtractHtmlContentAction(final Processor p, final Path outputDirectory) {
this.outputDirectory = outputDirectory;
- this.htmlExtraction = new HtmlExtractor(repository);
- this.repository = repository;
+ this.htmlExtraction = new HtmlExtractor(p);
+ this.processor = p;
}
@Override
@@ -66,12 +66,12 @@ class ExtractHtmlContentAction implements CheckAction {
final XdmNode node = (XdmNode) xdmItem;
final String name = origName + "-" + node.getAttributeValue(NAME_ATTRIBUTE);
final Path file = this.outputDirectory.resolve(name + ".html");
- final Serializer serializer = this.repository.getProcessor().newSerializer(file.toFile());
+ final Serializer serializer = this.processor.newSerializer(file.toFile());
try {
log.info("Writing report html '{}' to {}", name, file.toAbsolutePath());
serializer.serializeNode(node);
} catch (final SaxonApiException e) {
- log.info("Error extracting html content to {}", file.toAbsolutePath(), e);
+ log.error("Error extracting html content to {}", file.toAbsolutePath(), e);
}
}
diff --git a/src/main/java/de/kosit/validationtool/cmd/Health.java b/src/main/java/de/kosit/validationtool/cmd/Health.java
deleted file mode 100644
index e3056a6..0000000
--- a/src/main/java/de/kosit/validationtool/cmd/Health.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package de.kosit.validationtool.cmd;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import lombok.extern.slf4j.Slf4j;
-
-import de.kosit.validationtool.model.scenarios.Scenarios;
-
-/**
- * Klasse zur Erzeugung Health Xml , die optiamle Status.
- *
- * @author Roula Antoun
- */
-@Slf4j
-class Health {
-
- private final long freeMemory;
-
- private final long maxMemory;
-
- private final long totalMemory;
-
- private final Scenarios scenarios;
-
- Health(Scenarios scenarios) {
-
- Runtime runtime = Runtime.getRuntime();
- freeMemory = runtime.freeMemory();
- maxMemory = runtime.maxMemory();
- totalMemory = runtime.totalMemory();
- this.scenarios = scenarios;
- }
-
- /**
- * Methode, die schreibt das Health Xml für optimale Status
- *
- */
- Document writeHealthXml() {
- DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
- DocumentBuilder dBuilder;
- Document doc = null;
- try {
- dBuilder = dbFactory.newDocumentBuilder();
- doc = dBuilder.newDocument();
- Element rootElement = doc.createElementNS("https://localhost:8080/Health", "Health");
- doc.appendChild(rootElement);
- rootElement.appendChild(getMemory(doc, freeMemory, maxMemory, totalMemory));
- rootElement.appendChild(getState(doc));
- rootElement.appendChild(getScenario(doc, scenarios));
- } catch (ParserConfigurationException e) {
- log.error("Fehler beim Schreiben der Status-Informationen", e);
- }
- return doc;
- }
-
- /**
- * Methode, die schreibt das System Status Node im Xml File
- *
- * @param doc Vom Typ Dokument.
- *
- */
- private Node getState(Document doc) {
- Element state = doc.createElement("state");
- state.setAttribute("indicator", "OK");
- Element stateNode = doc.createElement("message");
- stateNode.appendChild(doc.createTextNode("System is up and running normally"));
- state.appendChild(stateNode);
- return state;
- }
-
- /**
- * Methode, die schreibt das Scnarios Information Node im Xml File
- *
- * @param doc Vom Typ Dokument .
- * @param scenarios Vom Typ {@link Scenarios} das verwendete scenario.
- *
- */
- private Node getScenario(Document doc, Scenarios scenarios) {
- Element scenario = doc.createElement("scenario");
- Element scenarioNameNode = doc.createElement("name");
- scenarioNameNode.appendChild(doc.createTextNode(scenarios.getName()));
- scenario.appendChild(scenarioNameNode);
- return scenario;
- }
-
- /**
- * Methode, die schreibt das Scnarios Information Node im Xml File
- *
- * @param doc Vom Typ Dokument .
- * @param freeMemory Vom Typ long , der freier Speicher.
- * @param maxMemory Vom Typ long , der maximaler Speicher
- * @param totalMemory Vom Typ long , der Gesamte speicher.
- *
- */
- private static Node getMemory(Document doc, long freeMemory, long maxMemory, long totalMemory) {
- Element memory = doc.createElement("memoryState");
- String freeM = Long.toString(freeMemory);
- Element freeMNode = doc.createElement("freeMemory");
- freeMNode.appendChild(doc.createTextNode(freeM));
- memory.appendChild(freeMNode);
- String maxM = Long.toString(maxMemory);
- Element maxMNode = doc.createElement("maxMemory");
- maxMNode.appendChild(doc.createTextNode(maxM));
- memory.appendChild(maxMNode);
- String totalM = Long.toString(totalMemory);
- Element totalMNode = doc.createElement("totalMemory");
- totalMNode.appendChild(doc.createTextNode(totalM));
- memory.appendChild(totalMNode);
- return memory;
- }
-}
diff --git a/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java b/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java
index db9a7b1..2a2c007 100644
--- a/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java
+++ b/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java
@@ -21,7 +21,7 @@ package de.kosit.validationtool.cmd;
import lombok.extern.slf4j.Slf4j;
-import de.kosit.validationtool.api.CheckConfiguration;
+import de.kosit.validationtool.api.Configuration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.Result;
import de.kosit.validationtool.impl.DefaultCheck;
@@ -45,7 +45,7 @@ class InternalCheck extends DefaultCheck {
*
* @param configuration die Konfiguration
*/
- InternalCheck(final CheckConfiguration configuration) {
+ InternalCheck(final Configuration configuration) {
super(configuration);
}
diff --git a/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java b/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java
index 2c3faa2..0659397 100644
--- a/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java
+++ b/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java
@@ -21,11 +21,12 @@ package de.kosit.validationtool.cmd;
import java.io.StringWriter;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
+import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
@@ -35,13 +36,16 @@ import net.sf.saxon.s9api.Serializer;
* @author Andreas Penski
*/
@Slf4j
+@RequiredArgsConstructor
class PrintReportAction implements CheckAction {
+ private final Processor processor;
+
@Override
public void check(Bag results) {
try {
final StringWriter writer = new StringWriter();
- final Serializer serializer = ObjectFactory.createProcessor().newSerializer(writer);
+ final Serializer serializer = processor.newSerializer(writer);
serializer.serializeNode(results.getReport());
System.out.print(writer.toString());
} catch (SaxonApiException e) {
diff --git a/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java b/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java
index 1c26479..cc2ef04 100644
--- a/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java
+++ b/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java
@@ -24,9 +24,9 @@ import java.nio.file.Path;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
+import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
@@ -41,12 +41,14 @@ class SerializeReportAction implements CheckAction {
private final Path outputDirectory;
+ private final Processor processor;
+
@Override
public void check(Bag results) {
final Path file = outputDirectory.resolve(results.getName() + "-report.xml");
try {
log.info("Serializing result to {}", file.toAbsolutePath());
- final Serializer serializer = ObjectFactory.createProcessor().newSerializer(file.toFile());
+ final Serializer serializer = processor.newSerializer(file.toFile());
serializer.serializeNode(results.getReport());
} catch (SaxonApiException e) {
log.error("Can not serialize result report to {}", file.toAbsolutePath(), e);
diff --git a/src/main/java/de/kosit/validationtool/config/Builder.java b/src/main/java/de/kosit/validationtool/config/Builder.java
new file mode 100644
index 0000000..6182e30
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/Builder.java
@@ -0,0 +1,20 @@
+package de.kosit.validationtool.config;
+
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.model.Result;
+
+/**
+ * Internal interface for creating object builders.
+ *
+ * @author Andreas Penski
+ */
+interface Builder {
+
+ /**
+ * Creates an object based on artifacts provided via a defined {@link ContentRepository}.
+ *
+ * @param repository the {@link ContentRepository}
+ * @return the result of building the object
+ */
+ Result build(ContentRepository repository);
+}
diff --git a/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java
new file mode 100644
index 0000000..ce199b3
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java
@@ -0,0 +1,370 @@
+package de.kosit.validationtool.config;
+
+import static de.kosit.validationtool.impl.DateFactory.createTimestamp;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+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.apache.commons.lang3.StringUtils;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.api.Configuration;
+import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.ResolvingMode;
+import de.kosit.validationtool.impl.Scenario;
+import de.kosit.validationtool.impl.model.Result;
+import de.kosit.validationtool.model.scenarios.DescriptionType;
+import de.kosit.validationtool.model.scenarios.NoScenarioReportType;
+import de.kosit.validationtool.model.scenarios.ObjectFactory;
+import de.kosit.validationtool.model.scenarios.Scenarios;
+
+import net.sf.saxon.s9api.Processor;
+
+/**
+ * Implements a builder style creation of a {@link Configuration}.
+ *
+ * @author Andreas Penski
+ */
+@Slf4j
+@Getter(AccessLevel.PACKAGE)
+public class ConfigurationBuilder {
+
+ private final List scenarios = new ArrayList<>();
+
+ private FallbackBuilder fallbackBuilder;
+
+ private ResolvingConfigurationStrategy resolvingConfigurationStrategy;
+
+ private ResolvingMode resolvingMode = ResolvingMode.STRICT_RELATIVE;
+
+ private Processor processor;
+
+ private String author = "API";
+
+ private String date = LocalDate.now().toString();
+
+ private String name = "Custom";
+
+ private final Map parameters = new HashMap<>();
+
+ private URI repository;
+
+ private String description;
+
+ /**
+ * Add a specific author name to this configuration.
+ *
+ * @param authorName the name of the author
+ * @return this
+ */
+ public ConfigurationBuilder author(final String authorName) {
+ this.author = authorName;
+ return this;
+ }
+
+ /**
+ * Add a specific nam to this configuration
+ *
+ * @param name the name of the configuration
+ * @return this
+ */
+ public ConfigurationBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Sets the date for this configuration.
+ *
+ * @param date the date
+ * @return this
+ */
+ public ConfigurationBuilder date(final LocalDate date) {
+ if (date != null) {
+ this.date = date.toString();
+ }
+ return this;
+ }
+
+ /**
+ * Sets the date for this configuration.
+ *
+ * @param date the date
+ * @return this
+ */
+ public ConfigurationBuilder date(final Date date) {
+ return date(date != null ? LocalDate.ofEpochDay(date.getTime()) : null);
+ }
+
+ /**
+ * Adds a {@link Scenario} to this list of know scenarios. Note: order of calling this methods defines order of
+ * scenarios when determining the target scenario for a given xml file.
+ *
+ * @param scenarioBuilder the {@link ScenarioBuilder} building the {@link Scenario}
+ * @return this
+ */
+ public ConfigurationBuilder with(final ScenarioBuilder scenarioBuilder) {
+ this.scenarios.add(scenarioBuilder);
+ return this;
+ }
+
+ /**
+ * Sets a specific fallback scenario configuration. Note: calling this more than once is possible, but the last call
+ * will define the actual fallback scenario used. There can be only one
+ *
+ * @param builder the {@link FallbackBuilder}
+ * @return this
+ */
+ public ConfigurationBuilder with(final FallbackBuilder builder) {
+ if (this.fallbackBuilder != null) {
+ log.warn("Overriding previously created fallback scenario");
+ }
+ this.fallbackBuilder = builder;
+ return this;
+ }
+
+ /**
+ * Adds a description to this configuration.
+ *
+ * @param description the descriptioin
+ * @return this
+ */
+ public ConfigurationBuilder description(final String description) {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * Create a fallback scenario configuration.
+ *
+ * @return the builder
+ */
+ public static FallbackBuilder fallback() {
+ return new FallbackBuilder();
+ }
+
+ /**
+ * Create the default fallback configuration if new scenario match. Note: this is public for explicit usage. If no
+ * fallback is configured, this is the still default fallback.
+ *
+ * @return a fallback configuration
+ */
+ public static FallbackBuilder defaultFallback() {
+ throw new NotImplementedException("Not yet defined");
+ }
+
+ /**
+ * Create a named schematron configuration.
+ *
+ * @param name the name of the schematron configuration
+ * @return new {@link SchemaBuilder}
+ */
+ public static SchematronBuilder schematron(final String name) {
+ return new SchematronBuilder().name(name);
+ }
+
+ /**
+ * Create a new schema validation configuration.
+ *
+ * @return a configuration builder for schema
+ */
+ public static SchemaBuilder schema() {
+ return new SchemaBuilder();
+ }
+
+ /**
+ * Create a new schema validation configuration.
+ *
+ * @param name the name of the schema
+ * @param schema the actual precompiled schema to use
+ * @return a configuration builder for schema
+ */
+ public static SchemaBuilder schema(final String name, final Schema schema) {
+ return new SchemaBuilder().name(name).schema(schema);
+ }
+
+ /**
+ * Create a new schema validation configuration.
+ *
+ * @param name the name of the schema
+ * @return a configuration builder for schema
+ */
+ public static SchemaBuilder schema(final String name) {
+ return new SchemaBuilder().name(name);
+ }
+
+ /**
+ * Create a new schema validation configuration.
+ *
+ * @param uri the uri location of the schema
+ * @return a configuration builder for schema
+ */
+ public static SchemaBuilder schema(final URI uri) {
+ return new SchemaBuilder().schemaLocation(uri);
+ }
+
+ /**
+ * Create a new named scenario configuration.
+ *
+ * @param name the name of the scenario
+ * @return the scenario configuration builder
+ */
+ public static ScenarioBuilder scenario(final String name) {
+ return new ScenarioBuilder().name(name);
+ }
+
+ /**
+ * Create a new scenario configuration.
+ *
+ * @return the scenario configuration builder
+ */
+ public static ScenarioBuilder scenario() {
+ return scenario(null);
+ }
+
+ /**
+ * Create named report configuration.
+ *
+ * @param name the name of the report
+ * @return the report configuration builder
+ */
+ public static ReportBuilder report(final String name) {
+ return new ReportBuilder().name(name);
+ }
+
+ /**
+ * Builds the actual {@link Configuration} by validating all builder inputs and constructing neccessary objects.
+ *
+ * @return a valid configuration
+ * @throws IllegalStateException when the configuration is not valid/complete
+ */
+ public Configuration build() {
+ final ResolvingConfigurationStrategy resolving = getResolvingConfigurationStrategy();
+ if (this.processor == null) {
+ this.processor = resolving.getProcessor();
+ }
+ final ContentRepository contentRepository = new ContentRepository(resolving, this.repository);
+
+ final List list = initializeScenarios(contentRepository);
+ final Scenario fallbackScenario = initializeFallback(contentRepository);
+ final DefaultConfiguration configuration = new DefaultConfiguration(list, fallbackScenario);
+ configuration.setAdditionalParameters(this.parameters);
+ configuration.setAuthor(this.author);
+ configuration.setDate(this.date);
+ configuration.setName(this.name);
+ configuration.setContentRepository(contentRepository);
+ configuration.getAdditionalParameters().put(Keys.SCENARIO_DEFINITION, createDefinition(configuration));
+ return (configuration);
+ }
+
+ private Scenarios createDefinition(final DefaultConfiguration configuration) {
+ final Scenarios s = new Scenarios();
+ s.setAuthor(configuration.getAuthor());
+ s.setDate(createTimestamp());
+ final DescriptionType d = new DescriptionType();
+ d.getPOrOlOrUl().add(new ObjectFactory().createDescriptionTypeP(StringUtils.defaultIfBlank(this.description, "")));
+ s.setDescription(d);
+ s.setName(configuration.getName());
+ s.getScenario().addAll(configuration.getScenarios().stream().map(Scenario::getConfiguration).collect(Collectors.toList()));
+ s.setNoScenarioReport(createNoScenarioReportType(configuration.getFallbackScenario()));
+ return s;
+ }
+
+ private static NoScenarioReportType createNoScenarioReportType(final Scenario fallbackScenario) {
+ final NoScenarioReportType no = new NoScenarioReportType();
+ no.setResource(fallbackScenario.getConfiguration().getCreateReport().getResource());
+ return no;
+ }
+
+ private Scenario initializeFallback(final ContentRepository contentRepository) {
+ if (this.fallbackBuilder == null) {
+ throw new IllegalStateException("No fallback configuration specified");
+ }
+ final Result result = this.fallbackBuilder.build(contentRepository);
+ if (result.isInvalid()) {
+ throw new IllegalStateException("Invalid fallback configuration: " + String.join(",", result.getErrors()));
+ }
+ return result.getObject();
+ }
+
+ private List initializeScenarios(final ContentRepository contentRepository) {
+ if (this.scenarios.isEmpty()) {
+ throw new IllegalStateException("No scenario specified");
+ }
+ return this.scenarios.stream().map(s -> {
+ final Result result = s.build(contentRepository);
+ if (result.isInvalid()) {
+ final String msg = String.join(",", result.getErrors());
+ throw new IllegalStateException(String.format("Invalid configuration for scenario %s found: %s", s.getName(), msg));
+ }
+ return result.getObject();
+ }).collect(Collectors.toList());
+ }
+
+ private ResolvingConfigurationStrategy getResolvingConfigurationStrategy() {
+ if (this.resolvingConfigurationStrategy != null) {
+ log.info("Custom resolving strategy supplied. Please take care of xml security!");
+ return this.resolvingConfigurationStrategy;
+ }
+ log.info("Using resolving strategy {}", this.resolvingMode);
+ return this.resolvingMode.getStrategy();
+ }
+
+ /**
+ * Sets a specific resolving mode, for resolving xml artifacts for this configuration. See {@link ResolvingMode} for
+ * details.
+ *
+ * @param mode the mode
+ * @return this
+ */
+ public ConfigurationBuilder resolvingMode(final ResolvingMode mode) {
+ this.resolvingMode = mode;
+ return this;
+ }
+
+ /**
+ * Sets a specific strategy to use for resolving artefacts for scenarios.
+ *
+ * @param strategy the strategy
+ * @return this
+ */
+ public ConfigurationBuilder resolvingStrategy(final ResolvingConfigurationStrategy strategy) {
+ this.resolvingConfigurationStrategy = strategy;
+ return this;
+ }
+
+ /**
+ * Set a specific repository location for resolving artifacts for scenarios.
+ *
+ * @param repository the repository location
+ * @return this
+ */
+ public ConfigurationBuilder useRepository(final URI repository) {
+ this.repository = repository;
+ return this;
+ }
+
+ /**
+ * Set a specific repository location for resolving artifacts for scenarios.
+ *
+ * @param repository the repository location
+ * @return this
+ */
+ public ConfigurationBuilder useRepository(final Path repository) {
+ return useRepository(repository.toUri());
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java b/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java
new file mode 100644
index 0000000..0457459
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java
@@ -0,0 +1,203 @@
+package de.kosit.validationtool.config;
+
+import static org.apache.commons.lang3.StringUtils.startsWith;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.xml.validation.Schema;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.api.Check;
+import de.kosit.validationtool.api.Configuration;
+import de.kosit.validationtool.api.InputFactory;
+import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
+import de.kosit.validationtool.impl.CollectingErrorEventHandler;
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.ConversionService;
+import de.kosit.validationtool.impl.ResolvingMode;
+import de.kosit.validationtool.impl.Scenario;
+import de.kosit.validationtool.impl.model.Result;
+import de.kosit.validationtool.impl.tasks.DocumentParseAction;
+import de.kosit.validationtool.impl.xml.RelativeUriResolver;
+import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
+import de.kosit.validationtool.model.scenarios.ResourceType;
+import de.kosit.validationtool.model.scenarios.ScenarioType;
+import de.kosit.validationtool.model.scenarios.Scenarios;
+
+import net.sf.saxon.s9api.Processor;
+import net.sf.saxon.s9api.QName;
+import net.sf.saxon.s9api.XdmNode;
+import net.sf.saxon.s9api.XdmNodeKind;
+
+/**
+ * Configuration class that loads neccessary {@link Check} configuration from an existing scenario.xml specification.
+ * This is the recommended option when an official configuration exists as is the case with 'xrechnung'.
+ *
+ * @author Andreas Penski
+ */
+@RequiredArgsConstructor
+@Slf4j
+public class ConfigurationLoader {
+
+ private static final String SUPPORTED_MAJOR_VERSION = "1";
+
+ private static final String SUPPORTED_MAJOR_VERSION_SCHEMA = "http://www.xoev.de/de/validator/framework/1/scenarios";
+
+ /**
+ * URL, die auf die scenerio.xml Datei zeigt.
+ */
+ @Getter(AccessLevel.PACKAGE)
+ private final URI scenarioDefinition;
+
+ /**
+ * Root-Ordner mit den von den einzelnen Szenarien benötigten Dateien
+ */
+ private final URI scenarioRepository;
+
+ protected ResolvingMode resolvingMode = ResolvingMode.STRICT_RELATIVE;
+
+ protected ResolvingConfigurationStrategy resolvingConfigurationStrategy;
+
+ protected final Map parameters = new HashMap<>();
+
+ URI getScenarioRepository() {
+ if (this.scenarioRepository == null) {
+ log.info("Creating default scenario repository (alongside scenario definition)");
+ return RelativeUriResolver.resolve(URI.create("."), this.scenarioDefinition);
+ }
+ return this.scenarioRepository;
+ }
+
+ private static void checkVersion(final URI scenarioDefinition, final Processor processor) {
+ try {
+ final Result result = new DocumentParseAction(processor)
+ .parseDocument(InputFactory.read(scenarioDefinition.toURL()));
+ if (result.isValid() && !isSupportedDocument(result.getObject())) {
+ throw new IllegalStateException(String.format(
+ "Specified scenario configuration %s is not supported.%nThis version only supports definitions of '%s'",
+ scenarioDefinition, SUPPORTED_MAJOR_VERSION_SCHEMA));
+
+ }
+ } catch (final MalformedURLException e) {
+ throw new IllegalStateException("Error reading definition file");
+ }
+ }
+
+ private static XdmNode findRoot(final XdmNode doc) {
+ for (final XdmNode node : doc.children()) {
+ if (node.getNodeKind() == XdmNodeKind.ELEMENT) {
+ return node;
+ }
+ }
+ throw new IllegalArgumentException("Kein root element gefunden");
+ }
+
+ private static boolean isSupportedDocument(final XdmNode doc) {
+ final XdmNode root = findRoot(doc);
+ final String frameworkVersion = root.getAttributeValue(new QName("frameworkVersion"));
+ return startsWith(frameworkVersion, SUPPORTED_MAJOR_VERSION)
+ && root.getNodeName().getNamespaceURI().equals(SUPPORTED_MAJOR_VERSION_SCHEMA);
+ }
+
+ private static Scenario createFallback(final Scenarios scenarios, final ContentRepository repository) {
+ final ResourceType noscenarioResource = scenarios.getNoScenarioReport().getResource();
+ return new FallbackBuilder().source(noscenarioResource.getLocation()).name(noscenarioResource.getName()).build(repository)
+ .getObject();
+
+ }
+
+ public Configuration build() {
+ final ResolvingConfigurationStrategy resolving = getResolvingConfigurationStrategy();
+ final Processor processor = resolving.getProcessor();
+ final ContentRepository contentRepository = new ContentRepository(resolving, getScenarioRepository());
+
+ final Scenarios def = loadScenarios(contentRepository.getScenarioSchema(), processor);
+ final List scenarios = initializeScenarios(def, contentRepository);
+ final Scenario fallbackScenario = createFallback(def, contentRepository);
+ final DefaultConfiguration configuration = new DefaultConfiguration(scenarios, fallbackScenario);
+ configuration.setAdditionalParameters(this.parameters);
+ configuration.setAuthor(def.getAuthor());
+ configuration.setDate(def.getDate().toString());
+ configuration.setName(def.getName());
+ configuration.setContentRepository(contentRepository);
+ configuration.getAdditionalParameters().put(Keys.SCENARIOS_FILE, this.scenarioDefinition);
+ configuration.getAdditionalParameters().put(Keys.SCENARIO_DEFINITION, def);
+ return (configuration);
+ }
+
+ private static List initializeScenarios(final Scenarios def, final ContentRepository contentRepository) {
+ return def.getScenario().stream().map(s -> initialize(s, contentRepository)).collect(Collectors.toList());
+ }
+
+ private ResolvingConfigurationStrategy getResolvingConfigurationStrategy() {
+ if (this.resolvingConfigurationStrategy != null) {
+ log.info("Custom resolving strategy supplied. Please take care of xml security!");
+ return this.resolvingConfigurationStrategy;
+ }
+ log.info("Using resolving strategy {}", this.resolvingMode);
+ return this.resolvingMode.getStrategy();
+ }
+
+ private Scenarios loadScenarios(final Schema scenarioSchema, final Processor processor) {
+ final ConversionService conversionService = new ConversionService();
+ checkVersion(this.scenarioDefinition, processor);
+ log.info("Loading scenarios from {}", this.scenarioDefinition);
+ final CollectingErrorEventHandler handler = new CollectingErrorEventHandler();
+ final Scenarios scenarios = conversionService.readXml(this.scenarioDefinition, Scenarios.class, scenarioSchema, handler);
+ if (!handler.hasErrors()) {
+ log.info("Loading scenario content from {}", this.getScenarioRepository());
+ } else {
+ throw new IllegalStateException(
+ String.format("Can not load scenarios from %s due to %s", getScenarioDefinition(), handler.getErrorDescription()));
+ }
+ return scenarios;
+
+ }
+
+ private static Scenario initialize(final ScenarioType def, final ContentRepository repository) {
+ final Scenario s = new Scenario(def);
+ s.setMatchExecutable(repository.createMatchExecutable(def));
+ s.setSchema(repository.createSchema(def));
+ s.setSchematronValidations(repository.createSchematronTransformations(def));
+ s.setReportTransformation(repository.createReportTransformation(def));
+ if (def.getAcceptMatch() != null) {
+ s.setAcceptExecutable(repository.createAccepptExecutable(def));
+ }
+ return s;
+ }
+
+ /**
+ * Sets actual {@link ResolvingMode}, when the validator needs to resolve stuff on startup.
+ * @param mode the resolving mode
+ * @return this
+ */
+ public ConfigurationLoader setResolvingMode(final ResolvingMode mode) {
+ this.resolvingMode = mode;
+ return this;
+ }
+
+ public ConfigurationLoader setResolvingStrategy(final ResolvingConfigurationStrategy strategy){
+ this.resolvingConfigurationStrategy = strategy;
+ return this;
+ }
+
+ /**
+ * Add a parameter to the configuration.
+ * @param name the name of the parameter
+ * @param value the parameter value object
+ * @return this
+ */
+ public ConfigurationLoader addParameter(final String name, final Object value) {
+ this.parameters.put(name, value);
+ return this;
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/config/DefaultConfiguration.java b/src/main/java/de/kosit/validationtool/config/DefaultConfiguration.java
new file mode 100644
index 0000000..830f86b
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/DefaultConfiguration.java
@@ -0,0 +1,40 @@
+package de.kosit.validationtool.config;
+
+import java.util.List;
+import java.util.Map;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.api.Configuration;
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.Scenario;
+
+/**
+ * Default implementation class for {@link Configuration}. This class contains all information to run a
+ * {@link de.kosit.validationtool.impl.DefaultCheck}.
+ *
+ * @author Andreas Penski
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Getter
+@Setter
+public class DefaultConfiguration implements Configuration {
+
+ private final List scenarios;
+
+ private final Scenario fallbackScenario;
+
+ private ContentRepository contentRepository;
+
+ private String name;
+
+ private String author;
+
+ private String date;
+
+ private Map additionalParameters;
+}
diff --git a/src/main/java/de/kosit/validationtool/config/FallbackBuilder.java b/src/main/java/de/kosit/validationtool/config/FallbackBuilder.java
new file mode 100644
index 0000000..d81b096
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/FallbackBuilder.java
@@ -0,0 +1,97 @@
+package de.kosit.validationtool.config;
+
+import java.net.URI;
+import java.nio.file.Path;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.Scenario;
+import de.kosit.validationtool.impl.Scenario.Transformation;
+import de.kosit.validationtool.impl.model.Result;
+import de.kosit.validationtool.model.scenarios.CreateReportType;
+import de.kosit.validationtool.model.scenarios.ScenarioType;
+
+/**
+ * Create a fallback {@link Scenario} configuration.
+ *
+ * @author Andreas Penski
+ */
+public class FallbackBuilder implements Builder {
+
+ private final ReportBuilder internal = new ReportBuilder().name("fallback");
+
+ @Override
+ public Result build(final ContentRepository repository) {
+ final ScenarioType object = createObject();
+ final Result, String> build = this.internal.build(repository);
+ final Result result;
+ if (build.isValid()) {
+ object.setCreateReport(build.getObject().getLeft());
+ final Scenario s = new Scenario(object);
+ s.setFallback(true);
+ s.setReportTransformation(build.getObject().getRight());
+ result = new Result<>(s);
+ } else {
+ result = new Result<>(build.getErrors());
+ }
+ return result;
+ }
+
+ private static ScenarioType createObject() {
+ final ScenarioType t = new ScenarioType();
+ t.setName("Fallback-Scenario");
+ t.setMatch("count(/)<0");
+ // always reject
+ t.setAcceptMatch("count(/)<0");
+ return t;
+ }
+
+ /**
+ * Specifices a source for this report. This is either used to compile the report transformation or as documentation for
+ * a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public FallbackBuilder source(final String source) {
+ this.internal.source(source);
+ return this;
+ }
+
+ /**
+ * Specifices a source for this report. This is either used to compile the report transformation or as documentation for
+ * a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public FallbackBuilder source(final URI source) {
+ this.internal.source(source);
+ return this;
+ }
+
+ /**
+ * Specifices a source for this report. This is either used to compile the report transformation or as documentation for
+ * a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public FallbackBuilder source(final Path source) {
+ this.internal.source(source);
+ return this;
+ }
+
+ /**
+ * Sets the name of the report source to a specific value.
+ *
+ * @param name the name
+ * @return this
+ */
+ public FallbackBuilder name(final String name) {
+ this.internal.name(name);
+ return this;
+ }
+
+}
diff --git a/src/main/java/de/kosit/validationtool/config/Keys.java b/src/main/java/de/kosit/validationtool/config/Keys.java
new file mode 100644
index 0000000..bd37c5c
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/Keys.java
@@ -0,0 +1,23 @@
+package de.kosit.validationtool.config;
+
+/**
+ * Defines some keys used for supplying additional parameters internally.
+ *
+ * @author Andreas Penski
+ */
+public final class Keys {
+
+ /**
+ * The actual scenarios file location as used with {@link ConfigurationLoader}.
+ */
+ public static final String SCENARIOS_FILE = "scenarios_file";
+ /**
+ * The actual scenarios configuration represented as serializable tree. This either loaded from file or build manually
+ * via {@link ConfigurationBuilder}
+ */
+ public static final String SCENARIO_DEFINITION = "scenario_definition";
+
+ private Keys() {
+ // hide
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/config/ReportBuilder.java b/src/main/java/de/kosit/validationtool/config/ReportBuilder.java
new file mode 100644
index 0000000..b512877
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/ReportBuilder.java
@@ -0,0 +1,116 @@
+package de.kosit.validationtool.config;
+
+import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.Scenario.Transformation;
+import de.kosit.validationtool.impl.model.Result;
+import de.kosit.validationtool.model.scenarios.CreateReportType;
+import de.kosit.validationtool.model.scenarios.ResourceType;
+
+import net.sf.saxon.s9api.XsltExecutable;
+
+/**
+ * Builder style configuration for the report transformation.
+ *
+ * @author Andreas Penski
+ */
+@Slf4j
+public class ReportBuilder implements Builder> {
+
+ private static final String DEFAULT_NAME = "manually created report";
+
+ private XsltExecutable executable;
+
+ private URI source;
+
+ private String name;
+
+ @Override
+ public Result, String> build(final ContentRepository repository) {
+ if (this.executable == null && this.source == null) {
+ return createError(String.format("Must supply source location and/or executable for report '%s'", this.name));
+ }
+ final CreateReportType object = createObject();
+ Result, String> result;
+
+ try {
+ if (this.executable == null) {
+ this.executable = repository.createTransformation(object.getResource()).getExecutable();
+ }
+ result = new Result<>(new ImmutablePair<>(object, new Transformation(this.executable, object.getResource())));
+ } catch (final IllegalStateException e) {
+ log.error(e.getMessage(), e);
+ result = createError(
+ String.format("Can not create report configuration based on %s. Exception is %s", this.source, e.getMessage()));
+ }
+ return result;
+ }
+
+ private CreateReportType createObject() {
+ final CreateReportType o = new CreateReportType();
+ final ResourceType r = new ResourceType();
+ r.setLocation(this.source.toASCIIString());
+ r.setName(isNotEmpty(this.name) ? this.name : DEFAULT_NAME);
+ o.setResource(r);
+ return o;
+ }
+
+ private static Result, String> createError(final String msg) {
+ return new Result<>(null, Collections.singletonList(msg));
+ }
+
+ /**
+ * Specifices a source for this report. This is either used to compile the report transformation or as documentation for
+ * a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public ReportBuilder source(final String source) {
+ return source(URI.create(source));
+ }
+
+ /**
+ * Specifices a source for this report. This is either used to compile the report transformation or as documentation for
+ * a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public ReportBuilder source(final URI source) {
+ this.source = source;
+ return this;
+ }
+
+ /**
+ * Specifices a source for this report. This is either used to compile the report transformation or as documentation for
+ * a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public ReportBuilder source(final Path source) {
+ return source(source.toUri());
+ }
+
+ /**
+ * Sets the name of the report source to a specific value.
+ *
+ * @param name the name
+ * @return this
+ */
+ public ReportBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/config/ScenarioBuilder.java b/src/main/java/de/kosit/validationtool/config/ScenarioBuilder.java
new file mode 100644
index 0000000..cd68369
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/ScenarioBuilder.java
@@ -0,0 +1,280 @@
+package de.kosit.validationtool.config;
+
+import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.xml.validation.Schema;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.Scenario;
+import de.kosit.validationtool.impl.Scenario.Transformation;
+import de.kosit.validationtool.impl.model.Result;
+import de.kosit.validationtool.model.scenarios.CreateReportType;
+import de.kosit.validationtool.model.scenarios.DescriptionType;
+import de.kosit.validationtool.model.scenarios.NamespaceType;
+import de.kosit.validationtool.model.scenarios.ObjectFactory;
+import de.kosit.validationtool.model.scenarios.ScenarioType;
+import de.kosit.validationtool.model.scenarios.ValidateWithSchematron;
+import de.kosit.validationtool.model.scenarios.ValidateWithXmlSchema;
+
+import net.sf.saxon.s9api.XPathExecutable;
+
+/**
+ * Builder for {@link Scenario} configuration.
+ *
+ * @author Andreas Penski
+ */
+@RequiredArgsConstructor
+@Slf4j
+@Getter(AccessLevel.PACKAGE)
+public class ScenarioBuilder implements Builder {
+
+ private static int nameCount = 0;
+
+ private static final String DEFAULT_DESCRIPTION = "Dieses Scenario wurde per API erstellt";
+
+ private final Map namespaces = new HashMap<>();
+
+ private final XPathBuilder matchConfig = new XPathBuilder("match");
+
+ private final XPathBuilder acceptConfig = new XPathBuilder("accept");
+
+ private String name;
+
+ private SchemaBuilder schemaBuilder;
+
+ private final List schematronBuilders = new ArrayList<>();
+
+ private ReportBuilder reportBuilder;
+
+ private String description;
+
+ @Override
+ public Result build(final ContentRepository repository) {
+ final List errors = new ArrayList<>();
+ final Scenario scenario = new Scenario(createType());
+ buildMatch(repository, errors, scenario);
+ buildSchema(repository, errors, scenario);
+ buildSchematron(repository, errors, scenario);
+ buildReport(repository, errors, scenario);
+ buildAccept(repository, errors, scenario);
+ buildNamespaces(scenario);
+ return new Result<>(scenario, errors);
+ }
+
+ /**
+ * Add a preconfiguration {@link XPathExecutable} to match the scenario
+ *
+ * @param executable the xpath executable
+ * @return this
+ */
+ public ScenarioBuilder match(final XPathExecutable executable) {
+ this.matchConfig.setExecutable(executable);
+ return this;
+ }
+
+ /**
+ * Add an xpath expression to match the scenario. You can leverage declared namespaces.
+ *
+ * @param xpath the expression
+ * @return this
+ */
+ public ScenarioBuilder match(final String xpath) {
+ this.matchConfig.setXpath(xpath);
+ return this;
+ }
+
+ /**
+ * Declare a namespace to use for match and accept configurations.
+ *
+ * @param prefix the prefix to use
+ * @param uri the uri of this namespace
+ * @return this
+ */
+ public ScenarioBuilder declareNamespace(final String prefix, final String uri) {
+ this.namespaces.put(prefix, uri);
+ return this;
+ }
+
+ /**
+ * Add a preconfiguration {@link XPathExecutable} to compute acceptance for the scenario
+ *
+ * @param executable the xpath executable
+ * @return this
+ */
+ public ScenarioBuilder acceptWith(final XPathExecutable executable) {
+ this.acceptConfig.setExecutable(executable);
+ return this;
+ }
+
+ /**
+ * Add an xpath expression to compute acceptance for the scenario. You can leverage declared namespaces.
+ *
+ * @param acceptXpath the xpath expresison
+ * @return this
+ */
+ public ScenarioBuilder acceptWith(final String acceptXpath) {
+ this.acceptConfig.setXpath(acceptXpath);
+ return this;
+ }
+
+ /**
+ * Add a schematron validation configuration for this scenario.
+ *
+ * @param schematron the schematron configuration
+ * @return this
+ */
+ public ScenarioBuilder validate(final SchematronBuilder schematron) {
+ if (schematron != null) {
+ this.schematronBuilders.add(schematron);
+ }
+ return this;
+ }
+
+ /**
+ * Validate matching {@link de.kosit.validationtool.api.Input Inputs} with the specified schema configuration.
+ *
+ * @param schema the schema configuration
+ * @return this
+ */
+ public ScenarioBuilder validate(final SchemaBuilder schema) {
+ this.schemaBuilder = schema;
+ return this;
+ }
+
+ /**
+ * Add description for this scenario. This is part of the
+ * {@link de.kosit.validationtool.model.reportInput.CreateReportInput} configuration and can be used while creating the
+ * report
+ *
+ * @param description the description
+ * @return this
+ */
+ public ScenarioBuilder description(final String description) {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * Add a configuration for generating the final report for the {@link de.kosit.validationtool.api.Input}.
+ *
+ * @param reportBuilder the report configuration
+ * @return this
+ */
+ public ScenarioBuilder with(final ReportBuilder reportBuilder) {
+ this.reportBuilder = reportBuilder;
+ return this;
+ }
+
+ private static String generateName() {
+ return "manually created scenario " + nameCount++;
+ }
+
+ private void buildNamespaces(final Scenario scenario) {
+ this.namespaces.putAll(this.acceptConfig.getNamespaces());
+ this.namespaces.putAll(this.matchConfig.getNamespaces());
+ final List all = this.namespaces.entrySet().stream().map(e -> {
+ final NamespaceType n = new NamespaceType();
+ n.setPrefix(e.getKey());
+ n.setValue(e.getValue());
+ return n;
+ }).collect(Collectors.toList());
+ scenario.getConfiguration().getNamespace().addAll(all);
+ }
+
+ private void buildMatch(final ContentRepository repository, final List errors, final Scenario scenario) {
+ this.matchConfig.setNamespaces(this.namespaces);
+ final Result result = this.matchConfig.build(repository);
+ if (result.isValid()) {
+ scenario.setMatchExecutable(result.getObject());
+ scenario.getConfiguration().setMatch(this.matchConfig.getXPath());
+ this.namespaces.putAll(this.matchConfig.getNamespaces());
+ } else {
+ errors.addAll(result.getErrors());
+ }
+ }
+
+ private void buildAccept(final ContentRepository repository, final List errors, final Scenario scenario) {
+ this.acceptConfig.setNamespaces(this.namespaces);
+ if (this.acceptConfig.isAvailable()) {
+ final Result result = this.acceptConfig.build(repository);
+ if (result.isValid()) {
+ scenario.setAcceptExecutable(result.getObject());
+ scenario.getConfiguration().setAcceptMatch(this.acceptConfig.getXPath());
+ this.namespaces.putAll(this.acceptConfig.getNamespaces());
+ } else {
+ errors.addAll(result.getErrors());
+ }
+ } else {
+ log.debug("No accept configuration available");
+ }
+ }
+
+ private void buildReport(final ContentRepository repository, final List errors, final Scenario scenario) {
+ if (this.reportBuilder == null) {
+ errors.add("Must supply report configuration");
+ } else {
+ final Result, String> result = this.reportBuilder.build(repository);
+ if (result.isValid()) {
+ scenario.setReportTransformation(result.getObject().getRight());
+ scenario.getConfiguration().setCreateReport(result.getObject().getLeft());
+ } else {
+ errors.addAll(result.getErrors());
+ }
+ }
+ }
+
+ private void buildSchematron(final ContentRepository repository, final List errors, final Scenario scenario) {
+ this.schematronBuilders.forEach(e -> {
+ final Result, String> result = e.build(repository);
+ if (result.isValid()) {
+ scenario.getConfiguration().getValidateWithSchematron().add(result.getObject().getLeft());
+ scenario.getSchematronValidations().add(result.getObject().getRight());
+ } else {
+ errors.addAll(result.getErrors());
+ }
+ });
+ }
+
+ private void buildSchema(final ContentRepository repository, final List errors, final Scenario scenario) {
+ if (this.schemaBuilder == null) {
+ errors.add("Must supply schema for validation");
+ } else {
+ final Result, String> result = this.schemaBuilder.build(repository);
+ if (result.isValid()) {
+ scenario.setSchema(result.getObject().getRight());
+ scenario.getConfiguration().setValidateWithXmlSchema(result.getObject().getLeft());
+ } else {
+ errors.addAll(result.getErrors());
+ }
+ }
+ }
+
+ private ScenarioType createType() {
+ final ScenarioType type = new ScenarioType();
+ type.setName(isNotEmpty(this.name) ? this.name : generateName());
+ final DescriptionType desc = new DescriptionType();
+ desc.getPOrOlOrUl()
+ .add(new ObjectFactory().createDescriptionTypeP(StringUtils.defaultIfBlank(this.description, DEFAULT_DESCRIPTION)));
+ type.setDescription(desc);
+ return type;
+ }
+
+ public ScenarioBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/config/SchemaBuilder.java b/src/main/java/de/kosit/validationtool/config/SchemaBuilder.java
new file mode 100644
index 0000000..3c7aa1d
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/SchemaBuilder.java
@@ -0,0 +1,121 @@
+package de.kosit.validationtool.config;
+
+import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import javax.xml.validation.Schema;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.model.Result;
+import de.kosit.validationtool.model.scenarios.ResourceType;
+import de.kosit.validationtool.model.scenarios.ValidateWithXmlSchema;
+
+/**
+ * Builder for Schema validation configuration.
+ *
+ * @author Andreas Penski
+ */
+@Slf4j
+public class SchemaBuilder implements Builder> {
+
+ private static final String DEFAULT_NAME = "manually configured";
+
+ private Schema schema;
+
+ private URI schemaLocation;
+
+ private String name;
+
+ @Override
+ public Result, String> build(final ContentRepository repository) {
+ if (this.schema == null && this.schemaLocation == null) {
+ return createError(String.format("Must supply source location and/or executable for schema '%s'", this.name));
+ }
+ Result, String> result;
+ try {
+ if (this.schema == null) {
+ this.schema = repository.createSchema(this.schemaLocation);
+ }
+ result = new Result<>(new ImmutablePair<>(createObject(), this.schema));
+ } catch (final IllegalStateException e) {
+ log.error(e.getMessage(), e);
+ result = createError(String.format("Can not create schema based %s. Exception is %s", this.schemaLocation, e.getMessage()));
+ }
+
+ return result;
+ }
+
+ private ValidateWithXmlSchema createObject() {
+ final ValidateWithXmlSchema o = new ValidateWithXmlSchema();
+ final ResourceType r = new ResourceType();
+ r.setName(isNotEmpty(this.name) ? this.name : DEFAULT_NAME);
+ r.setLocation(this.schemaLocation != null ? this.schemaLocation.toASCIIString() : "manuelly configured");
+ o.getResource().add(r);
+ return o;
+ }
+
+ private static Result, String> createError(final String msg) {
+ return new Result<>(null, Collections.singletonList(msg));
+ }
+
+ /**
+ * Set a specific precompiled schema to check.
+ *
+ * @param schema the {@link Schema}
+ * @return this
+ */
+ public SchemaBuilder schema(final Schema schema) {
+ this.schema = schema;
+ return this;
+ }
+
+ /**
+ * Set a specific schema location either to compile or to document the precompiled one .
+ *
+ * @param schemaLocation the schema location as uri
+ * @return this
+ */
+ public SchemaBuilder schemaLocation(final URI schemaLocation) {
+ this.schemaLocation = schemaLocation;
+ return this;
+ }
+
+ /**
+ * Set a specific schema location either to compile or to document the precompiled one .
+ *
+ * @param schemaLocation the schema location as uri
+ * @return this
+ */
+ public SchemaBuilder schemaLocation(final String schemaLocation) {
+ return schemaLocation(URI.create(schemaLocation));
+ }
+
+ /**
+ * Set a specific schema location either to compile or to document the precompiled one .
+ *
+ * @param schemaLocation the schema location as uri
+ * @return this
+ */
+ public SchemaBuilder schemaLocation(final Path schemaLocation) {
+ return schemaLocation(schemaLocation.toUri());
+ }
+
+ /**
+ * Set a specific name to identify this schema.
+ *
+ * @param name the name of the schema
+ * @return this
+ */
+ public SchemaBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/config/SchematronBuilder.java b/src/main/java/de/kosit/validationtool/config/SchematronBuilder.java
new file mode 100644
index 0000000..73c187b
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/SchematronBuilder.java
@@ -0,0 +1,116 @@
+package de.kosit.validationtool.config;
+
+import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.Scenario.Transformation;
+import de.kosit.validationtool.impl.model.Result;
+import de.kosit.validationtool.model.scenarios.ResourceType;
+import de.kosit.validationtool.model.scenarios.ValidateWithSchematron;
+
+import net.sf.saxon.s9api.XsltExecutable;
+
+/**
+ * Builder for schematron validation configuration.
+ *
+ * @author Andreas Penski
+ */
+@Slf4j
+public class SchematronBuilder implements Builder> {
+
+ private static final String DEFAULT_NAME = "manually configured";
+
+ private XsltExecutable executable;
+
+ private URI source;
+
+ private String name;
+
+ @Override
+ public Result, String> build(final ContentRepository repository) {
+ if (this.executable == null && this.source == null) {
+ return createError(String.format("Must supply source location and/or executable for schematron '%s'", this.name));
+ }
+ final ValidateWithSchematron object = createObject();
+ Result, String> result;
+
+ try {
+ if (this.executable == null) {
+ this.executable = repository.createSchematronTransformation(object).getExecutable();
+ }
+ result = new Result<>(new ImmutablePair<>(object, new Transformation(this.executable, object.getResource())));
+ } catch (final IllegalStateException e) {
+ log.error(e.getMessage(), e);
+ result = createError(
+ String.format("Can not create schematron configuration based on %s. Exception is %s", this.source, e.getMessage()));
+ }
+ return result;
+ }
+
+ private ValidateWithSchematron createObject() {
+ final ValidateWithSchematron o = new ValidateWithSchematron();
+ final ResourceType r = new ResourceType();
+ r.setLocation(this.source.toASCIIString());
+ r.setName(isNotEmpty(this.name) ? this.name : DEFAULT_NAME);
+ o.setResource(r);
+ return o;
+ }
+
+ private static Result, String> createError(final String msg) {
+ return new Result<>(null, Collections.singletonList(msg));
+ }
+
+ /**
+ * Specifices a source for this schematron validation. This is either used to compile the schematron transformation or
+ * as documentation for a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public SchematronBuilder source(final String source) {
+ return source(URI.create(source));
+ }
+
+ /**
+ * Specifices a source for this schematron validation. This is either used to compile the schematron transformation or
+ * as documentation for a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public SchematronBuilder source(final URI source) {
+ this.source = source;
+ return this;
+ }
+
+ /**
+ * Specifices a source for this schematron validation. This is either used to compile the schematron transformation or
+ * as documentation for a precompiled tranformation.
+ *
+ * @param source the source
+ * @return this
+ */
+ public SchematronBuilder source(final Path source) {
+ return source(source.toUri());
+ }
+
+ /**
+ * Sets the name of the schematron source to a specific value.
+ *
+ * @param name the name
+ * @return this
+ */
+ public SchematronBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/config/XPathBuilder.java b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java
new file mode 100644
index 0000000..7dc6509
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java
@@ -0,0 +1,109 @@
+package de.kosit.validationtool.config;
+
+import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.stream.StreamSupport;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.impl.ContentRepository;
+import de.kosit.validationtool.impl.model.Result;
+
+import net.sf.saxon.s9api.XPathExecutable;
+
+/**
+ * Internal class to represent xpath configuration.
+ *
+ * @author Andreas Penski
+ */
+@RequiredArgsConstructor
+@Getter
+@Setter
+@Slf4j
+class XPathBuilder implements Builder {
+
+ private static final String[] IGNORED_PREFIXES = new String[] { "xsd", "saxon", "xsl", "xs" };
+
+ private final String name;
+
+ private String xpath;
+
+ private XPathExecutable executable;
+
+ @Setter(AccessLevel.PACKAGE)
+ private Map namespaces;
+
+ Map getNamespaces() {
+ if (this.namespaces == null) {
+ this.namespaces = new HashMap<>();
+ }
+ return this.namespaces;
+ }
+
+ /**
+ * Returns the xpath expression.
+ *
+ * @return xpath expression
+ */
+ public String getXPath() {
+ return this.xpath == null && this.executable != null ? this.executable.getUnderlyingExpression().getInternalExpression().toString()
+ : this.xpath;
+ }
+
+ public boolean isAvailable() {
+ return this.executable != null || isNotEmpty(this.xpath);
+ }
+
+ @Override
+ public Result build(final ContentRepository repository) {
+ if (!isAvailable()) {
+ return createError(String.format("No configuration for %s xpath expression found", this.name));
+ }
+ try {
+ if (this.executable == null) {
+ this.executable = repository.createXPath(this.xpath, getNamespaces());
+ } else {
+ this.xpath = extractExpression();
+ extractNamespaces();
+ }
+ } catch (final IllegalStateException e) {
+ final String msg = String.format("Error creating %s xpath: %s", this.name, e.getMessage());
+ log.error(msg, e);
+ return new Result<>(Collections.singletonList(msg));
+
+ }
+ return new Result<>(this.executable);
+ }
+
+ private void extractNamespaces() {
+
+ final Map ns = new HashMap<>();
+ final Iterator iterator = this.executable.getUnderlyingExpression().getInternalExpression().getRetainedStaticContext()
+ .iteratePrefixes();
+ final Iterable iterable = () -> iterator;
+ StreamSupport.stream(iterable.spliterator(), false).filter(e -> !ArrayUtils.contains(IGNORED_PREFIXES, e))
+ .filter(StringUtils::isNotBlank).forEach(e -> ns.put(e, this.executable.getUnderlyingExpression().getInternalExpression()
+ .getRetainedStaticContext().getURIForPrefix(e, false)));
+ getNamespaces().putAll(ns);
+
+ }
+
+ private String extractExpression() {
+ return this.executable.getUnderlyingExpression().getInternalExpression().toString();
+ }
+
+ private static Result createError(final String msg) {
+ return new Result<>(null, Collections.singletonList(msg));
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/daemon/BaseHandler.java b/src/main/java/de/kosit/validationtool/daemon/BaseHandler.java
new file mode 100644
index 0000000..9aaae1c
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/daemon/BaseHandler.java
@@ -0,0 +1,46 @@
+package de.kosit.validationtool.daemon;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * Simple base implemenation for http handlers. Doing I/O stuff.
+ *
+ * @author Andreas Penski
+ */
+abstract class BaseHandler implements HttpHandler {
+
+ protected static final String APPLICATION_XML = "application/xml";
+
+ protected static final int OK = 200;
+
+ protected static void write(final HttpExchange exchange, final byte[] content, final String contentType) throws IOException {
+ write(exchange, contentType, os -> os.write(content));
+ }
+
+ protected static void write(final HttpExchange exchange, final String contentType, Write write) throws IOException {
+ exchange.getResponseHeaders().add("Content-Type", contentType);
+ exchange.sendResponseHeaders(OK, 0);
+ final OutputStream os = exchange.getResponseBody();
+ write.write(os);
+ os.close();
+ }
+
+ protected static void error(final HttpExchange exchange, final int statusCode, final String message) throws IOException {
+ final byte[] bytes = message.getBytes();
+ exchange.getResponseHeaders().add("Content-Type", "text/plain");
+ exchange.sendResponseHeaders(statusCode, bytes.length);
+ final OutputStream os = exchange.getResponseBody();
+ os.write(bytes);
+ os.close();
+ }
+
+ @FunctionalInterface
+ protected interface Write {
+
+ public void write(OutputStream out) throws IOException;
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java b/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java
new file mode 100644
index 0000000..1476d8c
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java
@@ -0,0 +1,77 @@
+package de.kosit.validationtool.daemon;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.sun.net.httpserver.HttpExchange;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.api.Check;
+import de.kosit.validationtool.api.InputFactory;
+import de.kosit.validationtool.api.Result;
+import de.kosit.validationtool.impl.input.SourceInput;
+
+import net.sf.saxon.s9api.Processor;
+import net.sf.saxon.s9api.SaxonApiException;
+import net.sf.saxon.s9api.Serializer;
+
+/**
+ * Wir benötigen einen Handler, der zur Verarbeitung von HTTP-Anforderungen aufgerufen wird um hier die Verarbeitung des
+ * POST Request zu realisieren.
+ */
+@Slf4j
+@RequiredArgsConstructor
+class CheckHandler extends BaseHandler {
+
+ private static final AtomicLong counter = new AtomicLong(0);
+
+ private final Check implemenation;
+
+ private final Processor processor;
+
+ /**
+ * Methode, die eine gegebene Anforderung verarbeitet und eine entsprechende Antwort generiert
+ *
+ * @param httpExchange kapselt eine empfangene HTTP-Anforderung und eine Antwort, die in einem Exchange generiert werden
+ * soll.
+ */
+ @Override
+ public void handle(final HttpExchange httpExchange) throws IOException {
+ try {
+ log.debug("Incoming request");
+ final String requestMethod = httpExchange.getRequestMethod();
+ if (requestMethod.equals("POST")) {
+ final InputStream inputStream = httpExchange.getRequestBody();
+ if (inputStream.available() > 0) {
+ final SourceInput serverInput = (SourceInput) InputFactory.read(inputStream,
+ "supplied_instance_" + counter.incrementAndGet());
+ final Result result = this.implemenation.checkInput(serverInput);
+ write(httpExchange, serialize(result), APPLICATION_XML);
+ } else {
+ error(httpExchange, 400, "No content supplied");
+ }
+
+ } else {
+ error(httpExchange, 405, "Method not supported");
+ }
+ } catch (final Exception e) {
+ error(httpExchange, 500, "Internal error: " + e.getMessage());
+ }
+ }
+
+ private byte[] serialize(final Result result) {
+ try ( final ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
+ final Serializer serializer = this.processor.newSerializer(out);
+ serializer.serializeNode(result.getReport());
+ return out.toByteArray();
+ } catch (final SaxonApiException | IOException e) {
+ log.error("Error serializing result", e);
+ throw new IllegalStateException("Can not serialize result", e);
+ }
+ }
+
+}
diff --git a/src/main/java/de/kosit/validationtool/daemon/ConfigHandler.java b/src/main/java/de/kosit/validationtool/daemon/ConfigHandler.java
new file mode 100644
index 0000000..696c70e
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/daemon/ConfigHandler.java
@@ -0,0 +1,77 @@
+package de.kosit.validationtool.daemon;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.net.URI;
+import java.util.Optional;
+
+import org.apache.commons.io.IOUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.api.Configuration;
+import de.kosit.validationtool.config.Keys;
+import de.kosit.validationtool.impl.ConversionService;
+import de.kosit.validationtool.model.scenarios.Scenarios;
+
+/**
+ * Handler that returns the actual configuration used for this daemon instance.
+ *
+ * @author Andreas Penski
+ */
+@Slf4j
+@RequiredArgsConstructor
+ class ConfigHandler extends BaseHandler {
+
+ private final Configuration configuration;
+
+ private final ConversionService conversionService;
+
+ @Override
+ public void handle(final HttpExchange exchange) throws IOException {
+ try {
+ final Optional xml = getSource();
+ if (xml.isPresent()) {
+ write(exchange, xml.get().getBytes(), APPLICATION_XML);
+ } else {
+ error(exchange, 404, "No configuration found");
+ }
+ } catch (final Exception e) {
+ log.error("Error grabbing configuration", e);
+ error(exchange, 500, "Error grabbing configuration: " + e.getMessage());
+ }
+ }
+
+ private Optional getSource() {
+ final URI fileUri = (URI) this.configuration.getAdditionalParameters().get(Keys.SCENARIOS_FILE);
+ return fileUri != null ? loadFile(fileUri) : loadFromConfig();
+ }
+
+ private static Optional loadFile(final URI fileUri) {
+ try ( final Reader in = new InputStreamReader(fileUri.toURL().openStream());
+ final StringWriter out = new StringWriter() ) {
+ IOUtils.copy(in, out);
+ return Optional.of(out.toString());
+ } catch (final IOException e) {
+ return Optional.empty();
+ }
+ }
+
+ private Optional loadFromConfig() {
+ final Optional result;
+ final Scenarios scenarios = (Scenarios) this.configuration.getAdditionalParameters().get(Keys.SCENARIO_DEFINITION);
+ if (scenarios != null) {
+ final String s = this.conversionService.writeXml(scenarios);
+ result = Optional.of(s);
+ } else {
+ result = Optional.empty();
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/de/kosit/validationtool/daemon/Daemon.java b/src/main/java/de/kosit/validationtool/daemon/Daemon.java
new file mode 100644
index 0000000..fa0dcbe
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/daemon/Daemon.java
@@ -0,0 +1,101 @@
+package de.kosit.validationtool.daemon;
+
+import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.api.Configuration;
+import de.kosit.validationtool.impl.ConversionService;
+import de.kosit.validationtool.impl.DefaultCheck;
+import de.kosit.validationtool.model.daemon.HealthType;
+
+/**
+ * HTTP-Daemon für die Bereitstellung der Prüf-Funktionalität via http.
+ *
+ * @author Roula Antoun
+ */
+@RequiredArgsConstructor
+@Setter
+@Slf4j
+public class Daemon {
+
+ private static final String DEFAULT_HOST = "localhost";
+
+ private static final int DEFAULT_PORT = 8080;
+
+ private String bindAddress;
+
+ private int port;
+
+ private int threadCount;
+
+ private boolean guiEnabled = true;
+
+ /**
+ * Create a new daemon.
+ * @param hostname the interface to bind to
+ * @param port the port to expose
+ * @param threadCount the number of working threads
+ */
+ public Daemon(String hostname, int port, int threadCount) {
+ this.bindAddress = hostname;
+ this.port = port;
+ this.threadCount = threadCount;
+ }
+
+ /**
+ * Methode zum Starten des Servers
+ *
+ * @param config the configuration to use
+ */
+ public void startServer(final Configuration config) {
+ HttpServer server = null;
+ try {
+ final ConversionService healthConverter = new ConversionService();
+ healthConverter.initialize(HealthType.class.getPackage());
+ final ConversionService converter = new ConversionService();
+
+ server = HttpServer.create(getSocket(), 0);
+ server.createContext("/", createRootHandler(config));
+ server.createContext("/server/health", new HealthHandler(config, healthConverter));
+ server.createContext("/server/config", new ConfigHandler(config, converter));
+ server.setExecutor(createExecutor());
+ server.start();
+ log.info("Server {} started", server.getAddress());
+ } catch (final IOException e) {
+ log.error("Error starting HttpServer for Valdidator: {}", e.getMessage(), e);
+ }
+ }
+
+ private HttpHandler createRootHandler(Configuration config) {
+ HttpHandler rootHandler;
+ final DefaultCheck check = new DefaultCheck(config);
+ final CheckHandler checkHandler = new CheckHandler(check, config.getContentRepository().getProcessor());
+ if (guiEnabled) {
+ GuiHandler gui = new GuiHandler();
+ rootHandler = new RoutingHandler(checkHandler, gui);
+ } else {
+ rootHandler = checkHandler;
+ }
+ return rootHandler;
+ }
+
+ private ExecutorService createExecutor() {
+ return Executors.newFixedThreadPool(this.threadCount > 0 ? this.threadCount : Runtime.getRuntime().availableProcessors());
+ }
+
+ private InetSocketAddress getSocket() {
+ return new InetSocketAddress(defaultIfBlank(this.bindAddress, DEFAULT_HOST), this.port > 0 ? this.port : DEFAULT_PORT);
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/daemon/GuiHandler.java b/src/main/java/de/kosit/validationtool/daemon/GuiHandler.java
new file mode 100644
index 0000000..972daf8
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/daemon/GuiHandler.java
@@ -0,0 +1,52 @@
+package de.kosit.validationtool.daemon;
+
+import com.sun.net.httpserver.HttpExchange;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+public class GuiHandler extends BaseHandler {
+
+ private static final URL INDEX_HTML = GuiHandler.class.getClassLoader().getResource("gui/index.html");
+
+ public GuiHandler() {
+ if (INDEX_HTML == null) {
+ throw new IllegalStateException("No html found");
+ }
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ assert INDEX_HTML != null;
+ final String path = exchange.getRequestURI().toASCIIString();
+ if (path.equals("/")) {
+ write(exchange, IOUtils.toString(INDEX_HTML, Charset.defaultCharset()).getBytes(), "text/html");
+ } else{
+ final URL resource = GuiHandler.class.getClassLoader().getResource("gui" + path);
+ if (resource != null) {
+ write(exchange,IOUtils.toString(resource, Charset.defaultCharset()).getBytes(), Mediatype.resolveBySuffix(resource.getPath()).getMimeType());;
+ }else {
+ error(exchange,404,"not found");
+ }
+ }
+ }
+
+
+ @RequiredArgsConstructor
+ @Getter
+ protected enum Mediatype {
+ JS("application/javascript"),
+ MD("text/markdown"),
+ CSS("text/css");
+ private final String mimeType;
+
+ static Mediatype resolveBySuffix(String path){
+ return Arrays.stream(values()).filter(e->path.toUpperCase().endsWith("."+e.name())).findFirst().orElse(Mediatype.MD);
+ }
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java b/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java
new file mode 100644
index 0000000..de7e40f
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java
@@ -0,0 +1,62 @@
+package de.kosit.validationtool.daemon;
+
+import java.io.IOException;
+
+import com.sun.net.httpserver.HttpExchange;
+
+import de.kosit.validationtool.impl.EngineInformation;
+import de.kosit.validationtool.model.daemon.ApplicationType;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import de.kosit.validationtool.api.Configuration;
+import de.kosit.validationtool.impl.ConversionService;
+import de.kosit.validationtool.model.daemon.HealthType;
+import de.kosit.validationtool.model.daemon.MemoryType;
+
+/**
+ * Handler that implements a simple health check. Useful for monitoring the service.
+ *
+ * @author Andreas Penski
+ */
+@Slf4j
+@RequiredArgsConstructor
+class HealthHandler extends BaseHandler {
+
+ private final Configuration scenarios;
+
+ private final ConversionService conversionService;
+
+ @Override
+ public void handle(final HttpExchange httpExchange) throws IOException {
+ final HealthType health = createHealth();
+ final String xml = this.conversionService.writeXml(health);
+ write(httpExchange, xml.getBytes(), APPLICATION_XML);
+
+ }
+
+ private HealthType createHealth() {
+ final HealthType h = new HealthType();
+ h.setMemory(createMemory());
+ h.setApplication(createApplication());
+ h.setStatus(scenarios.getScenarios().size() > 0 ? "UP" : "DOWN");
+ return h;
+ }
+
+ private static MemoryType createMemory() {
+ final MemoryType m = new MemoryType();
+ final Runtime runtime = Runtime.getRuntime();
+ m.setFreeMemory(runtime.freeMemory());
+ m.setMaxMemory(runtime.maxMemory());
+ m.setTotalMemory(runtime.totalMemory());
+ return m;
+ }
+
+ private static ApplicationType createApplication() {
+ ApplicationType a = new ApplicationType();
+ a.setBuild(EngineInformation.getBuild());
+ a.setName(EngineInformation.getName());
+ a.setVersion(EngineInformation.getVersion());
+ return a;
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/daemon/RoutingHandler.java b/src/main/java/de/kosit/validationtool/daemon/RoutingHandler.java
new file mode 100644
index 0000000..d15a39c
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/daemon/RoutingHandler.java
@@ -0,0 +1,30 @@
+package de.kosit.validationtool.daemon;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import lombok.RequiredArgsConstructor;
+
+import java.io.IOException;
+
+/**
+ * A simple handler which routes between the {@link CheckHandler} and the {@link GuiHandler} depending on the request.
+ */
+@RequiredArgsConstructor
+class RoutingHandler extends BaseHandler {
+
+ private final CheckHandler checkHandler;
+
+ private final GuiHandler guiHandler;
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ final String requestMethod = exchange.getRequestMethod();
+ if (requestMethod.equals("POST")) {
+ checkHandler.handle(exchange);
+ } else if (requestMethod.equals("GET")) {
+ guiHandler.handle(exchange);
+ } else {
+ error(exchange, 405, String.format("Method % not supported", requestMethod));
+ }
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java b/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java
index 50d96d6..46ff31f 100644
--- a/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java
+++ b/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java
@@ -34,6 +34,8 @@ import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
+import de.kosit.validationtool.impl.xml.RelativeUriResolver;
+
/**
* {@link LSResourceResolver} der objekte relativ zu einem Basis-Pfad aus dem Classpath der Anwendung laden kann.
*
diff --git a/src/main/java/de/kosit/validationtool/impl/ContentRepository.java b/src/main/java/de/kosit/validationtool/impl/ContentRepository.java
index 0da0736..84757d4 100644
--- a/src/main/java/de/kosit/validationtool/impl/ContentRepository.java
+++ b/src/main/java/de/kosit/validationtool/impl/ContentRepository.java
@@ -24,9 +24,14 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
@@ -39,6 +44,14 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
+import de.kosit.validationtool.impl.Scenario.Transformation;
+import de.kosit.validationtool.impl.xml.RelativeUriResolver;
+import de.kosit.validationtool.model.scenarios.NamespaceType;
+import de.kosit.validationtool.model.scenarios.ResourceType;
+import de.kosit.validationtool.model.scenarios.ScenarioType;
+import de.kosit.validationtool.model.scenarios.ValidateWithSchematron;
+
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathCompiler;
@@ -55,13 +68,36 @@ import net.sf.saxon.s9api.XsltExecutable;
@Slf4j
public class ContentRepository {
+ private Schema reportInputSchema;
+
@Getter
private final Processor processor;
private final URI repository;
- private Schema reportInputSchema;
+ private final URIResolver resolver;
+ private final SchemaFactory schemaFactory;
+
+ @Getter
+ private final ResolvingConfigurationStrategy resolvingConfigurationStrategy;
+
+ /**
+ * Creates a new {@link ContentRepository} based on configured security and resolving strategy and the specified
+ * repository location.
+ *
+ * @param strategy the security and resolving strategy
+ * @param repository the repository.
+ */
+ public ContentRepository(final ResolvingConfigurationStrategy strategy, final URI repository) {
+ this.repository = repository;
+ this.resolvingConfigurationStrategy = strategy;
+ this.processor = this.resolvingConfigurationStrategy.getProcessor();
+ this.resolver = this.resolvingConfigurationStrategy.createResolver(repository);
+ this.schemaFactory = this.resolvingConfigurationStrategy.createSchemaFactory();
+ }
+
+ @SuppressWarnings("java:S2095")
private static Source resolve(final URL resource) {
try {
return new StreamSource(resource.openStream(), resource.toURI().getRawPath());
@@ -70,9 +106,9 @@ public class ContentRepository {
}
}
- private static Schema createSchema(final Source[] schemaSources, final LSResourceResolver resourceResolver) {
+ private Schema createSchema(final Source[] schemaSources, final LSResourceResolver resourceResolver) {
try {
- final SchemaFactory sf = ObjectFactory.createSchemaFactory();
+ final SchemaFactory sf = this.schemaFactory;
sf.setResourceResolver(resourceResolver);
return sf.newSchema(schemaSources);
} catch (final SAXException e) {
@@ -80,7 +116,7 @@ public class ContentRepository {
}
}
- private static Schema createSchema(final Source[] schemaSources) {
+ private Schema createSchema(final Source[] schemaSources) {
return createSchema(schemaSources, null);
}
@@ -96,15 +132,18 @@ public class ContentRepository {
final CollectingErrorEventHandler listener = new CollectingErrorEventHandler();
try {
xsltCompiler.setErrorListener(listener);
- xsltCompiler.setURIResolver(createResolver());
+ if (getResolver() != null) {
+ // otherwise use default resolver
+ xsltCompiler.setURIResolver(getResolver());
+ }
- return xsltCompiler.compile(resolve(uri));
+ return xsltCompiler.compile(resolveInRepository(uri));
} catch (final 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);
+ log.warn("Received warnings or errors while loading a xslt script {}", uri);
listener.getErrors().forEach(e -> e.log(log));
}
}
@@ -116,11 +155,15 @@ public class ContentRepository {
* @param url die url
* @return das erzeugte Schema
*/
- public static Schema createSchema(final URL url) {
+ public Schema createSchema(final URL url) {
return createSchema(url, null);
}
- public static Schema createSchema(final URL url, final LSResourceResolver resourceResolver) {
+ public Schema createSchema(final URI uri) {
+ return createSchema(new Source[] { resolveInRepository(uri) });
+ }
+
+ public Schema createSchema(final URL url, final LSResourceResolver resourceResolver) {
log.info("Load schema from source {}", url.getPath());
return createSchema(new Source[] { resolve(url) }, resourceResolver);
}
@@ -130,7 +173,7 @@ public class ContentRepository {
*
* @return Scenario-Schema
*/
- public static Schema getScenarioSchema() {
+ public Schema getScenarioSchema() {
return createSchema(ContentRepository.class.getResource("/xsd/scenarios.xsd"));
}
@@ -154,12 +197,37 @@ public class ContentRepository {
* @return das Schema
*/
public Schema createSchema(final Collection uris) {
- return createSchema(uris.stream().map(s -> resolve(URI.create(s))).toArray(Source[]::new));
+ return createSchema(uris.stream().map(s -> resolveInRepository(URI.create(s))).toArray(Source[]::new));
}
- private Source resolve(final URI source) {
- final URI resolved = RelativeUriResolver.resolve(source, this.repository);
- return new StreamSource(resolved.toASCIIString());
+ /**
+ * Liefert das Schema zu diesem Szenario.
+ *
+ * @return das passende Schema
+ */
+ public Schema createSchema(final ScenarioType s) {
+ Schema schema = null;
+ if (s.getValidateWithXmlSchema() != null) {
+ final List schemaResources = s.getValidateWithXmlSchema().getResource().stream().map(ResourceType::getLocation)
+ .collect(Collectors.toList());
+ schema = createSchema(schemaResources);
+ }
+ return schema;
+ }
+
+ private Source resolveInRepository(final URI source) {
+ try {
+ if (this.resolver == null) {
+ // TODO wie wird ohne resolver das richtige Artefakt gefunden?
+ // assume local
+ final URI resolved = RelativeUriResolver.resolve(source, this.repository);
+ return new StreamSource(resolved.toASCIIString());
+ }
+ return this.resolver.resolve(source.toString(), this.repository.toString());
+ } catch (final TransformerException e) {
+ log.error("Error resolving source {}", source, e);
+ throw new IllegalStateException(String.format("Can not resolve %s in repository %s", source, this.repository), e);
+ }
}
/**
@@ -187,7 +255,43 @@ public class ContentRepository {
*
* @return ein neuer Resolver
*/
- public RelativeUriResolver createResolver() {
- return new RelativeUriResolver(this.repository);
+ public URIResolver getResolver() {
+ return this.resolver;
+ }
+
+ /**
+ * Gibt eine Transformation zurück.
+ *
+ * @return initialisierte Transformation
+ */
+ public Transformation createReportTransformation(final ScenarioType t) {
+ final ResourceType resource = t.getCreateReport().getResource();
+ return createTransformation(resource);
+ }
+
+ public Transformation createTransformation(final ResourceType resource) {
+ final XsltExecutable executable = loadXsltScript(URI.create(resource.getLocation()));
+ return new Transformation(executable, resource);
+ }
+
+ public XPathExecutable createMatchExecutable(final ScenarioType s) {
+ final Map namespaces = s.getNamespace().stream()
+ .collect(Collectors.toMap(NamespaceType::getPrefix, NamespaceType::getValue));
+ return createXPath(s.getMatch(), namespaces);
+ }
+
+ public XPathExecutable createAccepptExecutable(final ScenarioType s) {
+ final Map namespaces = s.getNamespace().stream()
+ .collect(Collectors.toMap(NamespaceType::getPrefix, NamespaceType::getValue));
+ return createXPath(s.getAcceptMatch(), namespaces);
+ }
+
+ public List createSchematronTransformations(final ScenarioType s) {
+ return s.getValidateWithSchematron().isEmpty() ? Collections.emptyList()
+ : s.getValidateWithSchematron().stream().map(this::createSchematronTransformation).collect(Collectors.toList());
+ }
+
+ public Transformation createSchematronTransformation(final ValidateWithSchematron validateWithSchematron) {
+ return createTransformation(validateWithSchematron.getResource());
}
}
diff --git a/src/main/java/de/kosit/validationtool/impl/ConversionService.java b/src/main/java/de/kosit/validationtool/impl/ConversionService.java
index 2bc4185..0fc6cd0 100644
--- a/src/main/java/de/kosit/validationtool/impl/ConversionService.java
+++ b/src/main/java/de/kosit/validationtool/impl/ConversionService.java
@@ -36,7 +36,6 @@ import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
-import javax.xml.parsers.DocumentBuilder;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
@@ -47,7 +46,6 @@ import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import org.apache.commons.lang3.StringUtils;
-import org.w3c.dom.Document;
import lombok.extern.slf4j.Slf4j;
@@ -86,6 +84,15 @@ public class ConversionService {
// context setup
private JAXBContext jaxbContext;
+ public JAXBContext
+
+ getJaxbContext() {
+ if (this.jaxbContext == null) {
+ initialize();
+ }
+ return this.jaxbContext;
+ }
+
private static QName createQName(final T model) {
return new QName(model.getClass().getSimpleName().toLowerCase());
}
@@ -141,13 +148,6 @@ public class ConversionService {
}
}
- private JAXBContext getJaxbContext() {
- if (this.jaxbContext == null) {
- initialize();
- }
- return this.jaxbContext;
- }
-
/**
* Unmarshalls a specifc xml model into a defined java object.
*
@@ -233,24 +233,8 @@ public class ConversionService {
}
}
- public Document writeDocument(final T input) {
- if (input == null) {
- throw new ConversionExeption("Can not serialize null");
- }
- final DocumentBuilder builder = ObjectFactory.createDocumentBuilder(false);
- final 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 (final JAXBException e) {
- throw new ConversionExeption(String.format("Error serializing Object %s to document", input.getClass().getName()), e);
- }
- }
+
public T readDocument(final Source source, final Class type) {
try {
diff --git a/src/main/java/de/kosit/validationtool/impl/DateFactory.java b/src/main/java/de/kosit/validationtool/impl/DateFactory.java
new file mode 100644
index 0000000..0c9fe9a
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/impl/DateFactory.java
@@ -0,0 +1,27 @@
+package de.kosit.validationtool.impl;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+
+import lombok.SneakyThrows;
+
+/**
+ * @author Andreas Penski
+ */
+public class DateFactory {
+
+ private DateFactory() {
+ // hide
+ }
+
+ @SneakyThrows
+ public static XMLGregorianCalendar createTimestamp() {
+ final GregorianCalendar cal = new GregorianCalendar();
+ cal.setTime(new Date());
+ return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
+
+ }
+}
diff --git a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java
index 02e2922..f87fb00 100644
--- a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java
+++ b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java
@@ -19,6 +19,8 @@
package de.kosit.validationtool.impl;
+import static de.kosit.validationtool.impl.DateFactory.createTimestamp;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -28,13 +30,14 @@ 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.Configuration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.Result;
import de.kosit.validationtool.api.XmlError;
import de.kosit.validationtool.impl.tasks.CheckAction;
import de.kosit.validationtool.impl.tasks.CheckAction.Bag;
import de.kosit.validationtool.impl.tasks.ComputeAcceptanceAction;
+import de.kosit.validationtool.impl.tasks.CreateDocumentIdentificationAction;
import de.kosit.validationtool.impl.tasks.CreateReportAction;
import de.kosit.validationtool.impl.tasks.DocumentParseAction;
import de.kosit.validationtool.impl.tasks.ScenarioSelectionAction;
@@ -56,15 +59,11 @@ import net.sf.saxon.s9api.Processor;
@Slf4j
public class DefaultCheck implements Check {
- @Getter
- private final ScenarioRepository repository;
-
- @Getter
- private final ContentRepository contentRepository;
-
@Getter
private final ConversionService conversionService;
+ private final Configuration configuration;
+
@Getter
private final List checkSteps;
@@ -73,20 +72,20 @@ public class DefaultCheck implements Check {
*
* @param configuration die Konfiguration
*/
- public DefaultCheck(final CheckConfiguration configuration) {
- final Processor processor = ObjectFactory.createProcessor();
+ public DefaultCheck(final Configuration configuration) {
+ this.configuration = configuration;
+ final ContentRepository content = configuration.getContentRepository();
+ final Processor processor = content.getProcessor();
this.conversionService = new ConversionService();
- this.contentRepository = new ContentRepository(processor, configuration.getScenarioRepository());
- this.repository = new ScenarioRepository(this.contentRepository);
- this.repository.initialize(configuration);
+
this.checkSteps = new ArrayList<>();
- this.checkSteps.add(new DocumentParseAction());
+ this.checkSteps.add(new DocumentParseAction(processor));
this.checkSteps.add(new CreateDocumentIdentificationAction());
- this.checkSteps.add(new ScenarioSelectionAction(this.repository));
- this.checkSteps.add(new SchemaValidationAction());
- this.checkSteps.add(new SchematronValidationAction(this.contentRepository, this.conversionService));
- this.checkSteps.add(new ValidateReportInputAction(this.conversionService, this.contentRepository.getReportInputSchema()));
- this.checkSteps.add(new CreateReportAction(processor, this.conversionService, this.repository, this.contentRepository));
+ this.checkSteps.add(new ScenarioSelectionAction(new ScenarioRepository(configuration)));
+ this.checkSteps.add(new SchemaValidationAction(content.getResolvingConfigurationStrategy(), processor));
+ this.checkSteps.add(new SchematronValidationAction(content.getResolver(), this.conversionService));
+ this.checkSteps.add(new ValidateReportInputAction(this.conversionService, content.getReportInputSchema()));
+ this.checkSteps.add(new CreateReportAction(processor, this.conversionService, content.getResolver()));
this.checkSteps.add(new ComputeAcceptanceAction());
}
@@ -95,11 +94,13 @@ public class DefaultCheck implements Check {
final EngineType e = new EngineType();
e.setName(EngineInformation.getName());
type.setEngine(e);
- type.setTimestamp(ObjectFactory.createTimestamp());
+ type.setTimestamp(createTimestamp());
type.setFrameworkVersion(EngineInformation.getFrameworkVersion());
return type;
}
+
+
@Override
public Result checkInput(final Input input) {
final CheckAction.Bag t = new CheckAction.Bag(input, createReport());
@@ -122,7 +123,8 @@ public class DefaultCheck implements Check {
}
private Result createResult(final Bag t) {
- final DefaultResult result = new DefaultResult(t.getReport(), t.getAcceptStatus(), new HtmlExtractor(this.contentRepository));
+ final DefaultResult result = new DefaultResult(t.getReport(), t.getAcceptStatus(),
+ new HtmlExtractor(this.configuration.getContentRepository().getProcessor()));
result.setWellformed(t.getParserResult().isValid());
result.setReportInput(t.getReportInput());
if (t.getSchemaValidationResult() != null) {
@@ -139,5 +141,4 @@ public class DefaultCheck implements Check {
return (List) (List>) errors;
}
-
}
diff --git a/src/main/java/de/kosit/validationtool/impl/EngineInformation.java b/src/main/java/de/kosit/validationtool/impl/EngineInformation.java
index 10692ec..d781f94 100644
--- a/src/main/java/de/kosit/validationtool/impl/EngineInformation.java
+++ b/src/main/java/de/kosit/validationtool/impl/EngineInformation.java
@@ -1,5 +1,7 @@
package de.kosit.validationtool.impl;
+import lombok.SneakyThrows;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
@@ -65,6 +67,10 @@ public class EngineInformation {
return getFrameworkVersion().substring(0, 1);
}
+ public static String getBuild() {
+ return PROPERTIES.getProperty("build_number");
+ }
+
/**
* Gibt den Namespace des eingesetzten Frameworks zurück.
*
diff --git a/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java b/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java
index 59bdc38..bde98a1 100644
--- a/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java
+++ b/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java
@@ -6,13 +6,16 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Element;
import lombok.RequiredArgsConstructor;
import net.sf.saxon.dom.NodeOverNodeInfo;
+import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
+import net.sf.saxon.s9api.XPathCompiler;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmItem;
@@ -26,38 +29,51 @@ import net.sf.saxon.s9api.XdmNode;
@RequiredArgsConstructor
public class HtmlExtractor {
- private final ContentRepository repository;
+ private final Processor processor;
private XPathExecutable executable;
- public List extract(XdmNode xdmSource) {
+ public List extract(final XdmNode xdmSource) {
try {
final XPathSelector selector = getSelector();
selector.setContextItem(xdmSource);
- return selector.stream().map(this::castToNode).collect(Collectors.toList());
+ return selector.stream().map(HtmlExtractor::castToNode).collect(Collectors.toList());
- } catch (SaxonApiException e) {
+ } catch (final SaxonApiException e) {
throw new IllegalStateException("Can not extract html content", e);
}
}
- private XdmNode castToNode(final XdmItem xdmItem) {
+ private static XdmNode castToNode(final XdmItem xdmItem) {
return (XdmNode) xdmItem;
}
private XPathSelector getSelector() {
- if (executable == null) {
- Map ns = new HashMap<>();
+ if (this.executable == null) {
+ final Map ns = new HashMap<>();
ns.put("html", "http://www.w3.org/1999/xhtml");
- executable = repository.createXPath("//html:html", ns);
+ this.executable = createXPath("//html:html", ns);
}
- return executable.load();
+ return this.executable.load();
}
- private static String convertToString(final XdmNode element) {
+ private XPathExecutable createXPath(final String expression, final Map namespaces) {
+ try {
+ final XPathCompiler compiler = this.processor.newXPathCompiler();
+ if (namespaces != null) {
+ namespaces.forEach(compiler::declareNamespace);
+ }
+ return compiler.compile(expression);
+ } catch (final SaxonApiException e) {
+ throw new IllegalStateException(String.format("Can not compile xpath match expression '%s'",
+ StringUtils.isNotBlank(expression) ? expression : "EMPTY EXPRESSION"), e);
+ }
+ }
+
+ private String convertToString(final XdmNode element) {
try {
final StringWriter writer = new StringWriter();
- final Serializer serializer = ObjectFactory.createProcessor().newSerializer(writer);
+ final Serializer serializer = this.processor.newSerializer(writer);
serializer.serializeNode(element);
return writer.toString();
} catch (final SaxonApiException e) {
@@ -72,7 +88,7 @@ public class HtmlExtractor {
* @return HTML-Fragment als String
*/
public List extractAsString(final XdmNode node) {
- return extract(node).stream().map(HtmlExtractor::convertToString).collect(Collectors.toList());
+ return extract(node).stream().map(this::convertToString).collect(Collectors.toList());
}
public List extractAsElement(final XdmNode node) {
diff --git a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java b/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java
deleted file mode 100644
index eace869..0000000
--- a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * 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.OutputKeys;
-import javax.xml.transform.Result;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-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.CollectionFinder;
-import net.sf.saxon.lib.FeatureKeys;
-import net.sf.saxon.lib.OutputURIResolver;
-import net.sf.saxon.lib.ResourceCollection;
-import net.sf.saxon.lib.UnparsedTextURIResolver;
-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 OWASP-Empfehlungen.
- *
- * 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 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(final String href, final String base) throws TransformerException {
- throw new IllegalStateException(MESSAGE);
- }
-
- @Override
- public void close(final Result result) throws TransformerException {
- throw new IllegalStateException(MESSAGE);
- }
-
- @Override
- public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException {
- throw new IllegalStateException(MESSAGE);
- }
-
- @Override
- public ResourceCollection findCollection(final XPathContext context, final String collectionURI) throws XPathException {
- throw new IllegalStateException(MESSAGE);
- }
- }
- 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 (final 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(final boolean validating) {
- final 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 (final 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(final 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 (final 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 {
- final GregorianCalendar cal = new GregorianCalendar();
- cal.setTime(new Date());
- return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
-
- } catch (final DatatypeConfigurationException e) {
- throw new IllegalStateException("Can not create timestamp", e);
- }
- }
-
- public static DocumentBuilder createDocumentBuilder(final boolean validating) {
- try {
- return createDocumentBuilderFactory(validating).newDocumentBuilder();
- } catch (final ParserConfigurationException e) {
- throw new IllegalStateException("Can not create DocumentFactory due to underlying configuration error", e);
- }
- }
-
- private static String encode(final String input) {
- try {
- return URLEncoder.encode(input, StandardCharsets.UTF_8.name());
- } catch (final 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
- final 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), true);
- 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(final Schema schema) {
- final Validator validator = schema.newValidator();
- try {
- validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
- } catch (final 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 (final 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() {
- final 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 (final SAXException e) {
- log.warn("Can not disable external DTD access, maybe an unsupported JAXP implementation is used", e);
- }
- return sf;
- }
-}
diff --git a/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java b/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java
new file mode 100644
index 0000000..1ca8517
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java
@@ -0,0 +1,35 @@
+package de.kosit.validationtool.impl;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
+import de.kosit.validationtool.impl.xml.RemoteResolvingStrategy;
+import de.kosit.validationtool.impl.xml.StrictLocalResolvingStrategy;
+import de.kosit.validationtool.impl.xml.StrictRelativeResolvingStrategy;
+
+/**
+ * Defines how artefacts are resolved internally.
+ *
+ * @author Andreas Penski
+ */
+@RequiredArgsConstructor
+public enum ResolvingMode {
+
+ /**
+ * Resolving using only the configured content repository. No furthing resolving allowed. This
+ */
+ STRICT_RELATIVE(new StrictRelativeResolvingStrategy()) {
+
+ },
+
+ STRICT_LOCAL(new StrictLocalResolvingStrategy()),
+
+ ALLOW_REMOTE(new RemoteResolvingStrategy()),
+
+ CUSTOM(null);
+
+ @Getter
+ private final ResolvingConfigurationStrategy strategy;
+
+}
diff --git a/src/main/java/de/kosit/validationtool/impl/Scenario.java b/src/main/java/de/kosit/validationtool/impl/Scenario.java
new file mode 100644
index 0000000..0e0ef5c
--- /dev/null
+++ b/src/main/java/de/kosit/validationtool/impl/Scenario.java
@@ -0,0 +1,82 @@
+package de.kosit.validationtool.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import javax.xml.validation.Schema;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+import de.kosit.validationtool.model.scenarios.ResourceType;
+import de.kosit.validationtool.model.scenarios.ScenarioType;
+
+import net.sf.saxon.s9api.XPathExecutable;
+import net.sf.saxon.s9api.XPathSelector;
+import net.sf.saxon.s9api.XsltExecutable;
+
+/**
+ * @author Andreas Penski
+ */
+@RequiredArgsConstructor
+@Setter
+@Getter
+public class Scenario {
+
+ /**
+ * Runtime objects for a transformation e.g. schematron or report.
+ */
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ public static class Transformation {
+
+ private XsltExecutable executable;
+
+ private ResourceType resourceType;
+ }
+
+ private final ScenarioType configuration;
+
+ private Schema schema;
+
+ private boolean fallback;
+
+ private XPathExecutable matchExecutable;
+
+ private XPathExecutable acceptExecutable;
+
+ @Setter
+ private List schematronValidations;
+
+ private Transformation reportTransformation;
+
+ public List getSchematronValidations() {
+ return this.schematronValidations == null ? Collections.emptyList() : this.schematronValidations;
+ }
+
+ public String getName() {
+ return this.configuration.getName();
+ }
+
+ public XPathSelector getMatchSelector() {
+ if (this.matchExecutable == null) {
+ throw new IllegalStateException("No match executable supplied");
+ }
+ return this.matchExecutable.load();
+ }
+
+ /**
+ * Liefert einen neuen XPath-Selector zur Evaluierung der {@link de.kosit.validationtool.api.AcceptRecommendation}.
+ *
+ * @return neuer Selector
+ */
+ public Optional getAcceptSelector() {
+ final XPathSelector selector = this.acceptExecutable != null ? this.acceptExecutable.load() : null;
+ return Optional.ofNullable(selector);
+ }
+
+}
diff --git a/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java b/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java
index 6992329..a8ddc26 100644
--- a/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java
+++ b/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java
@@ -19,34 +19,18 @@
package de.kosit.validationtool.impl;
-import static org.apache.commons.lang3.StringUtils.startsWith;
-
-import java.net.MalformedURLException;
-import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
-import de.kosit.validationtool.api.CheckConfiguration;
-import de.kosit.validationtool.api.InputFactory;
+import de.kosit.validationtool.api.Configuration;
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.CreateReportType;
-import de.kosit.validationtool.model.scenarios.ScenarioType;
-import de.kosit.validationtool.model.scenarios.Scenarios;
-import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmNode;
-import net.sf.saxon.s9api.XdmNodeKind;
/**
* Repository for die aktiven Szenario einer Prüfinstanz.
@@ -54,86 +38,28 @@ import net.sf.saxon.s9api.XdmNodeKind;
* @author Andreas Penski
*/
@Slf4j
-@RequiredArgsConstructor
+
public class ScenarioRepository {
- private static final String SUPPORTED_MAJOR_VERSION = "1";
+ private final Configuration configuration;
- private static final String SUPPORTED_MAJOR_VERSION_SCHEMA = "http://www.xoev.de/de/validator/framework/1/scenarios";
-
-
- @Getter(value = AccessLevel.PACKAGE)
- private final ContentRepository repository;
-
- @Getter
- private Scenarios scenarios;
-
- @Setter(AccessLevel.PACKAGE)
- @Getter
- private ScenarioType fallbackScenario;
-
- private static boolean isSupportedDocument(final XdmNode doc) {
- final XdmNode root = findRoot(doc);
- final String frameworkVersion = root.getAttributeValue(new QName("frameworkVersion"));
- return startsWith(frameworkVersion, SUPPORTED_MAJOR_VERSION)
- && root.getNodeName().getNamespaceURI().equals(SUPPORTED_MAJOR_VERSION_SCHEMA);
+ public ScenarioRepository(final Configuration configuration) {
+ this.configuration = configuration;
+ log.info("Loaded scenarios for {} by {} from {}. The following scenarios are available:\n\n{}", configuration.getName(),
+ configuration.getAuthor(), configuration.getDate(), summarizeScenarios());
}
- private static XdmNode findRoot(final XdmNode doc) {
- for (final XdmNode node : doc.children()) {
- if (node.getNodeKind() == XdmNodeKind.ELEMENT) {
- return node;
- }
- }
- throw new IllegalArgumentException("Kein root element gefunden");
+ public Scenario getFallbackScenario() {
+ return this.configuration.getFallbackScenario();
}
- private static void checkVersion(final URI scenarioDefinition) {
- final DocumentParseAction p = new DocumentParseAction();
- try {
- final Result result = DocumentParseAction.parseDocument(InputFactory.read(scenarioDefinition.toURL()));
- if (result.isValid() && !isSupportedDocument(result.getObject())) {
- throw new IllegalStateException(String.format(
- "Specified scenario configuration %s is not supported.%nThis version only supports definitions of '%s'",
- scenarioDefinition, SUPPORTED_MAJOR_VERSION_SCHEMA));
-
- }
- } catch (final MalformedURLException e) {
- throw new IllegalStateException("Error reading definition file");
- }
- }
-
-
-
- /**
- * Initialisiert das Repository mit der angegebenen Konfiguration.
- *
- * @param config die Konfiguration
- */
- public void initialize(final CheckConfiguration config) {
- final ConversionService conversionService = new ConversionService();
- checkVersion(config.getScenarioDefinition());
- log.info("Loading scenarios from {}", config.getScenarioDefinition());
- final CollectingErrorEventHandler handler = new CollectingErrorEventHandler();
- this.scenarios = conversionService.readXml(config.getScenarioDefinition(), Scenarios.class, ContentRepository.getScenarioSchema(),
- handler);
- if (!handler.hasErrors()) {
- log.info("Loaded scenarios for {} by {} from {}. The following scenarios are available:\n\n{}", this.scenarios.getName(),
- this.scenarios.getAuthor(), this.scenarios.getDate(), summarizeScenarios());
- log.info("Loading scenario content from {}", config.getScenarioRepository());
- getScenarios().getScenario().forEach(s -> s.initialize(this.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
- this.fallbackScenario = createFallback();
-
+ public List getScenarios() {
+ return this.configuration.getScenarios();
}
private String summarizeScenarios() {
final StringBuilder b = new StringBuilder();
- this.scenarios.getScenario().forEach(s -> {
+ getScenarios().forEach(s -> {
b.append(s.getName());
b.append("\n");
});
@@ -146,9 +72,9 @@ public class ScenarioRepository {
* @param document das Eingabedokument
* @return ein Ergebnis-Objekt zur weiteren Verarbeitung
*/
- public Result selectScenario(final XdmNode document) {
- final Result result;
- final List collect = this.scenarios.getScenario().stream().filter(s -> match(document, s))
+ public Result selectScenario(final XdmNode document) {
+ final Result result;
+ final List collect = getScenarios().stream().filter(s -> match(document, s))
.collect(Collectors.toList());
if (collect.size() == 1) {
result = new Result<>(collect.get(0));
@@ -162,23 +88,9 @@ public class ScenarioRepository {
}
- private ScenarioType createFallback() {
- final ScenarioType t = new ScenarioType();
- t.setFallback(true);
- t.setName("Fallback-Scenario");
- t.setMatch("count(/)<0");
- final CreateReportType reportType = new CreateReportType();
- reportType.setResource(this.scenarios.getNoScenarioReport().getResource());
- t.initialize(this.repository, true);
- // always reject
- t.setAcceptMatch("count(/)<0");
- t.setCreateReport(reportType);
- return t;
- }
-
- private static boolean match(final XdmNode document, final ScenarioType scenario) {
+ private static boolean match(final XdmNode document, final Scenario scenario) {
try {
- final XPathSelector selector = scenario.getSelector();
+ final XPathSelector selector = scenario.getMatchSelector();
selector.setContextItem(document);
return selector.effectiveBooleanValue();
} catch (final SaxonApiException e) {
@@ -187,7 +99,4 @@ public class ScenarioRepository {
return false;
}
- void initialize(final Scenarios def) {
- this.scenarios = def;
- }
}
diff --git a/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java b/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java
index 8cc4a3d..cb9f929 100644
--- a/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java
+++ b/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java
@@ -7,13 +7,25 @@ import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.io.input.ReaderInputStream;
-import org.apache.commons.lang3.NotImplementedException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/**
- * A validator {@link de.kosit.validationtool.api.Input} based an on a {@link Source}.
+ * A validator {@link de.kosit.validationtool.api.Input} based on a {@link Source}.
+ *