Merge branch 'configuration_api' into 'master'

New configuration api and xml  security configuration

See merge request kosit/validator!24
This commit is contained in:
Andreas Penski 2020-06-24 11:03:33 +00:00
commit a78a0861e8
118 changed files with 5782 additions and 1455 deletions

2
.idea/compiler.xml generated
View file

@ -2,11 +2,13 @@
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="validationtool" />
<module name="validator" />
</profile>
</annotationProcessing>
</component>

1
.idea/encodings.xml generated
View file

@ -2,6 +2,7 @@
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/generated/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/model" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />

5
.idea/misc.xml generated
View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true">
<PanelProvider>
@ -76,7 +79,7 @@
<component name="NodePackageJsonFileManager">
<packageJsonPaths />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" project-jdk-name="11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" project-jdk-name="openjdk-14" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="jetbrains.communicator.idea.IdProvider" IDEtalkID="4B2DA906C3A7DF4F7B6EA28093E19A3F" />

View file

@ -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

View file

@ -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-`<version`>-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-<version>-standalone.jar -s <scenario-config-file> [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-<version>-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-<version>-standalone.jar -s <scenario-config-file> -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-<version>-standalone.jar -s <scenario-config-file> -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.

View file

@ -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.
---

View file

@ -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:

108
docs/daemon.md Normal file
View file

@ -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-<version>-standalone.jar -s <scenario-config-file> -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-<version>-standalone.jar -s <scenario-config-file> -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 = "<file contents here>";
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-<version>-standalone.jar -s <scenario-config-file> -D --disable-gui
```

103
pom.xml
View file

@ -39,7 +39,7 @@
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.jacoco>0.8.4</version.jacoco>
<version.jacoco>0.8.5</version.jacoco>
<version.lombok>1.18.8</version.lombok>
<version.saxon-he>9.9.1-3</version.saxon-he>
<version.slf4j>1.7.25</version.slf4j>
@ -129,6 +129,12 @@
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
</dependency>
</dependencies>
@ -145,6 +151,26 @@
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>reserve-network-port</id>
<goals>
<goal>reserve-network-port</goal>
</goals>
<phase>process-resources</phase>
<configuration>
<portNames>
<portName>validator.server.port</portName>
<portName>jacoco.tcp.port</portName>
</portNames>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
@ -266,28 +292,6 @@
</executions>
</plugin>
<plugin>
<!-- Integrate the /src/main/generated folder -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/generated/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Generate model classes -->
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
@ -304,7 +308,6 @@
<extension>true</extension>
<schemaDirectory>src/main/model/xsd</schemaDirectory>
<bindingDirectory>src/main/model/binding</bindingDirectory>
<generateDirectory>src/generated/java</generateDirectory>
<packageLevelAnnotations>false</packageLevelAnnotations>
<args>
<arg>-Xinheritance</arg>
@ -332,14 +335,40 @@
</configuration>
<executions>
<execution>
<id>prepareJacocoJUnitArgLine</id>
<id>prepareJacocoSurefireArgLine</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<propertyName>jacocoArgumentsJUnit</propertyName>
<propertyName>jacocoSurefire</propertyName>
</configuration>
</execution>
<execution>
<id>prepareJacocoFailsafeArgLine</id>
<phase>pre-integration-test</phase>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<propertyName>jacocoFailsafe</propertyName>
<output>tcpserver</output>
<address>localhost</address>
<port>${jacoco.tcp.port}</port>
</configuration>
</execution>
<execution>
<id>dump</id>
<phase>post-integration-test</phase>
<goals>
<goal>dump</goal>
</goals>
<configuration>
<address>localhost</address>
<port>${jacoco.tcp.port}</port>
<append>true</append>
</configuration>
</execution>
<execution>
<id>generateJacocoReport</id>
<goals>
@ -355,7 +384,7 @@
<version>2.22.0</version>
<configuration>
<!--suppress MavenModelInspection -->
<argLine>-Dfile.encoding=UTF-8 ${jacocoArgumentsJUnit}</argLine>
<argLine>-Dfile.encoding=UTF-8 ${jacocoSurefire}</argLine>
</configuration>
</plugin>
@ -387,20 +416,21 @@
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<execution>
<id>run</id>
<phase>pre-integration-test</phase>
<goals>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
</goals>
</execution>
</executions>
<configuration>
<configuration>
<executable>java</executable>
<longClasspath>true</longClasspath>
<async>true</async>
<asyncDestroyOnShutdown>true</asyncDestroyOnShutdown>
<arguments>
<argument>${jacocoFailsafe}</argument>
<argument>-classpath</argument>
<classpath />
<argument>de.kosit.validationtool.cmd.CommandLineApplication</argument>
@ -408,6 +438,8 @@
<argument>${project.build.testOutputDirectory}/examples/simple/scenarios.xml</argument>
<argument>-r</argument>
<argument>${project.build.testOutputDirectory}/examples/simple/repository</argument>
<argument>--port</argument>
<argument>${validator.server.port}</argument>
<argument>-D</argument>
</arguments>
@ -421,7 +453,8 @@
<configuration>
<target>
<!-- schlafen um den Start des Daemon abzuwarten -->
<sleep seconds="5" />
<sleep seconds="10" />
<echo>${jacoco.tcp.port}</echo>
</target>
</configuration>
<executions>
@ -445,6 +478,10 @@
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<!--suppress MavenModelInspection -->
<argLine>-Dfile.encoding=UTF-8 -Ddaemon.port=${validator.server.port}</argLine>
</configuration>
</execution>
</executions>

View file

@ -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
}

View file

@ -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<Scenario> getScenarios() {
return getDelegate().getScenarios();
}
@Override
public Scenario getFallbackScenario() {
return getDelegate().getFallbackScenario();
}
@Override
public String getDate() {
return getDelegate().getDate();
}
@Override
public Map<String, Object> 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();
}
}

View file

@ -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:
*
* <ol>
* <li>{@link ConfigurationLoader} implements loading {@link Check} configurations from a scenario.xml file</li>
* <li>Using a builder style api {@link de.kosit.validationtool.config.ConfigurationBuilder}to configure the
* {@link Check}</li>
* </ol>
* <p>
* 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<Scenario> 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<String, Object> 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();
}
}

View file

@ -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;

View file

@ -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}. <br/>
* 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

View file

@ -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 <b>can</b> be
* used for resolving relative URIs against a base URI or restrict access to certain URIs.
* <p>
* This URIResolver is used to dereference the URIs appearing in <code>xsl:import</code>, <code>xsl:include</code>, and
* <code>xsl:import-schema</code> declarations.
* </p>
*
* @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);
}

View file

@ -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<FailedAssert> 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();

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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) {

View file

@ -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);

View file

@ -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<T> {
/**
* 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<T, String> build(ContentRepository repository);
}

View file

@ -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<ScenarioBuilder> 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<String, Object> 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<Scenario> 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<Scenario, String> result = this.fallbackBuilder.build(contentRepository);
if (result.isInvalid()) {
throw new IllegalStateException("Invalid fallback configuration: " + String.join(",", result.getErrors()));
}
return result.getObject();
}
private List<Scenario> initializeScenarios(final ContentRepository contentRepository) {
if (this.scenarios.isEmpty()) {
throw new IllegalStateException("No scenario specified");
}
return this.scenarios.stream().map(s -> {
final Result<Scenario, String> 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());
}
}

View file

@ -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<String, Object> 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<XdmNode, XMLSyntaxError> result = new DocumentParseAction(processor)
.parseDocument(InputFactory.read(scenarioDefinition.toURL()));
if (result.isValid() && !isSupportedDocument(result.getObject())) {
throw new IllegalStateException(String.format(
"Specified scenario configuration %s is not supported.%nThis version only supports definitions of '%s'",
scenarioDefinition, SUPPORTED_MAJOR_VERSION_SCHEMA));
}
} catch (final MalformedURLException e) {
throw new IllegalStateException("Error reading definition file");
}
}
private static XdmNode findRoot(final XdmNode doc) {
for (final XdmNode node : doc.children()) {
if (node.getNodeKind() == XdmNodeKind.ELEMENT) {
return node;
}
}
throw new IllegalArgumentException("Kein root element gefunden");
}
private static boolean isSupportedDocument(final XdmNode doc) {
final XdmNode root = findRoot(doc);
final String frameworkVersion = root.getAttributeValue(new QName("frameworkVersion"));
return startsWith(frameworkVersion, SUPPORTED_MAJOR_VERSION)
&& root.getNodeName().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<Scenario> scenarios = initializeScenarios(def, contentRepository);
final Scenario fallbackScenario = createFallback(def, contentRepository);
final DefaultConfiguration configuration = new DefaultConfiguration(scenarios, fallbackScenario);
configuration.setAdditionalParameters(this.parameters);
configuration.setAuthor(def.getAuthor());
configuration.setDate(def.getDate().toString());
configuration.setName(def.getName());
configuration.setContentRepository(contentRepository);
configuration.getAdditionalParameters().put(Keys.SCENARIOS_FILE, this.scenarioDefinition);
configuration.getAdditionalParameters().put(Keys.SCENARIO_DEFINITION, def);
return (configuration);
}
private static List<Scenario> 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;
}
}

View file

@ -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<Scenario> scenarios;
private final Scenario fallbackScenario;
private ContentRepository contentRepository;
private String name;
private String author;
private String date;
private Map<String, Object> additionalParameters;
}

View file

@ -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<Scenario> {
private final ReportBuilder internal = new ReportBuilder().name("fallback");
@Override
public Result<Scenario, String> build(final ContentRepository repository) {
final ScenarioType object = createObject();
final Result<Pair<CreateReportType, Transformation>, String> build = this.internal.build(repository);
final Result<Scenario, String> 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;
}
}

View file

@ -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
}
}

View file

@ -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<Pair<CreateReportType, Transformation>> {
private static final String DEFAULT_NAME = "manually created report";
private XsltExecutable executable;
private URI source;
private String name;
@Override
public Result<Pair<CreateReportType, Transformation>, 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<Pair<CreateReportType, Transformation>, 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<Pair<CreateReportType, Transformation>, 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;
}
}

View file

@ -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<Scenario> {
private static int nameCount = 0;
private static final String DEFAULT_DESCRIPTION = "Dieses Scenario wurde per API erstellt";
private final Map<String, String> 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<SchematronBuilder> schematronBuilders = new ArrayList<>();
private ReportBuilder reportBuilder;
private String description;
@Override
public Result<Scenario, String> build(final ContentRepository repository) {
final List<String> 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<NamespaceType> 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<String> errors, final Scenario scenario) {
this.matchConfig.setNamespaces(this.namespaces);
final Result<XPathExecutable, String> 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<String> errors, final Scenario scenario) {
this.acceptConfig.setNamespaces(this.namespaces);
if (this.acceptConfig.isAvailable()) {
final Result<XPathExecutable, String> 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<String> errors, final Scenario scenario) {
if (this.reportBuilder == null) {
errors.add("Must supply report configuration");
} else {
final Result<Pair<CreateReportType, Transformation>, 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<String> errors, final Scenario scenario) {
this.schematronBuilders.forEach(e -> {
final Result<Pair<ValidateWithSchematron, Transformation>, 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<String> errors, final Scenario scenario) {
if (this.schemaBuilder == null) {
errors.add("Must supply schema for validation");
} else {
final Result<Pair<ValidateWithXmlSchema, Schema>, 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;
}
}

View file

@ -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<Pair<ValidateWithXmlSchema, Schema>> {
private static final String DEFAULT_NAME = "manually configured";
private Schema schema;
private URI schemaLocation;
private String name;
@Override
public Result<Pair<ValidateWithXmlSchema, Schema>, 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<Pair<ValidateWithXmlSchema, Schema>, 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<Pair<ValidateWithXmlSchema, Schema>, 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;
}
}

View file

@ -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<Pair<ValidateWithSchematron, Transformation>> {
private static final String DEFAULT_NAME = "manually configured";
private XsltExecutable executable;
private URI source;
private String name;
@Override
public Result<Pair<ValidateWithSchematron, Transformation>, 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<Pair<ValidateWithSchematron, Transformation>, 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<Pair<ValidateWithSchematron, Transformation>, 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;
}
}

View file

@ -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<XPathExecutable> {
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<String, String> namespaces;
Map<String, String> 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<XPathExecutable, String> 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<String, String> ns = new HashMap<>();
final Iterator<String> iterator = this.executable.getUnderlyingExpression().getInternalExpression().getRetainedStaticContext()
.iteratePrefixes();
final Iterable<String> 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<XPathExecutable, String> createError(final String msg) {
return new Result<>(null, Collections.singletonList(msg));
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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<String> 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<String> getSource() {
final URI fileUri = (URI) this.configuration.getAdditionalParameters().get(Keys.SCENARIOS_FILE);
return fileUri != null ? loadFile(fileUri) : loadFromConfig();
}
private static Optional<String> 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<String> loadFromConfig() {
final Optional<String> 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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}
}

View file

@ -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.
*

View file

@ -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<String> 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<String> 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<String, String> namespaces = s.getNamespace().stream()
.collect(Collectors.toMap(NamespaceType::getPrefix, NamespaceType::getValue));
return createXPath(s.getMatch(), namespaces);
}
public XPathExecutable createAccepptExecutable(final ScenarioType s) {
final Map<String, String> namespaces = s.getNamespace().stream()
.collect(Collectors.toMap(NamespaceType::getPrefix, NamespaceType::getValue));
return createXPath(s.getAcceptMatch(), namespaces);
}
public List<Transformation> 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());
}
}

View file

@ -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 <T> 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 <T> 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> T readDocument(final Source source, final Class<T> type) {
try {

View file

@ -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);
}
}

View file

@ -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<CheckAction> 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<XmlError>) (List<?>) errors;
}
}

View file

@ -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.
*

View file

@ -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<XdmNode> extract(XdmNode xdmSource) {
public List<XdmNode> 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<String, String> ns = new HashMap<>();
if (this.executable == null) {
final Map<String, String> 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<String, String> 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<String> 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<Element> extractAsElement(final XdmNode node) {

View file

@ -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 <a href="https://www.owasp.org/index.php/XML_Security_Cheat_Sheet">OWASP-Empfehlungen</a>.
*
* Diese Klasse ist stark abhängig von der Verwendung eines Oracle JDK. Alternative JDKs haben u.U. eine andere SAX- /
* StAX- / XML-Implementierug und profitieren entsprechend NICHT von den hier getroffenen Einstellungen.
*
* @author Andreas Penski
*/
@Slf4j
public class ObjectFactory {
private static 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;
}
}

View file

@ -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;
}

View file

@ -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<Transformation> schematronValidations;
private Transformation reportTransformation;
public List<Transformation> 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<XPathSelector> getAcceptSelector() {
final XPathSelector selector = this.acceptExecutable != null ? this.acceptExecutable.load() : null;
return Optional.ofNullable(selector);
}
}

View file

@ -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<XdmNode, XMLSyntaxError> 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<Scenario> 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<ScenarioType, String> selectScenario(final XdmNode document) {
final Result<ScenarioType, String> result;
final List<ScenarioType> collect = this.scenarios.getScenario().stream().filter(s -> match(document, s))
public Result<Scenario, String> selectScenario(final XdmNode document) {
final Result<Scenario, String> result;
final List<Scenario> 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;
}
}

View file

@ -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}. <br/>
* <p>
* Note: The various implementations of {@link Source} varies wether the can be read twice or no. This implementation
* tries to handle this with respect document identification (hashcode).
*
* This class is know to work with:
* <ul>
* <li>{@link StreamSource} - both {@link java.io.InputStream} based and {@link java.io.Reader} based</li>
* <li>{@link javax.xml.transform.dom.DOMSource}</li>
* <li>{@link javax.xml.bind.util.JAXBSource}</li>
* </ul>
*
* Other {@link Source Sources} may work as well, please try and let us know.
* </p>
*
* @author Andreas Penski
*/
@ -40,26 +52,23 @@ public class SourceInput extends AbstractInput {
}
private void validate() {
if (!isSupported()) {
if (!isHashcodeComputed() && !isSupported()) {
throw new IllegalStateException("Unsupported source. Only StreamSource supported yet");
}
if (((StreamSource) this.source).getInputStream() == null && !isHashcodeComputed()) {
if (!isHashcodeComputed() && ((StreamSource) this.source).getInputStream() == null) {
log.warn("No hashcode supplied, will wrap the reader using system default charset");
}
}
@Override
public Source getSource() throws IOException {
if (!isSupported()) {
if (!isHashcodeComputed() && !isSupported()) {
throw new IllegalStateException("Unsupported source. Only InputStream-based StreamSource supported yet");
}
if (isWrappingRequired()) {
return wrap();
}
if (isConsumed()) {
throw new IllegalStateException("A SourceInput can only read once");
}
return this.source;
return isHashcodeComputed() ? this.source : wrappedSource();
}
private boolean isSupported() {
@ -67,23 +76,25 @@ public class SourceInput extends AbstractInput {
}
private boolean isConsumed() throws IOException {
if (!isStreamSource()) {
throw new NotImplementedException("Supports only StreamSource yet");
}
final StreamSource ss = (StreamSource) this.source;
try {
return (ss.getInputStream() != null && ss.getInputStream().available() == 0)
|| (ss.getReader() != null && !ss.getReader().ready());
} catch (final IOException e) {
return true;
if (isStreamSource()) {
final StreamSource ss = (StreamSource) this.source;
try {
return (ss.getInputStream() != null && ss.getInputStream().available() == 0)
|| (ss.getReader() != null && !ss.getReader().ready());
} catch (final IOException e) {
log.error("Error checking consumed state", e);
return true;
}
}
return false;
}
private boolean isStreamSource() {
return this.source instanceof StreamSource;
}
private Source wrap() {
private Source wrappedSource() {
Source result = this.source;
if (isStreamSource()) {
final StreamSource ss = (StreamSource) this.source;

View file

@ -1,224 +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.model;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.validation.Schema;
import org.apache.commons.lang3.NotImplementedException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.ScenarioRepository;
import de.kosit.validationtool.model.scenarios.CreateReportType;
import de.kosit.validationtool.model.scenarios.NamespaceType;
import de.kosit.validationtool.model.scenarios.ResourceType;
import de.kosit.validationtool.model.scenarios.ValidateWithSchematron;
import de.kosit.validationtool.model.scenarios.ValidateWithXmlSchema;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XsltExecutable;
/**
* Eine Basis-Klasse für Szenarien. Wiederverwendbare Objekte mit Bezug zum Szenario werden hier gecached. Die Klasse
* stellt im eigentlichen Sinne eine Erweiterung der durch JAXB generierten Strukturen dar.
*
* @author Andreas Penski
*/
@XmlAccessorType(XmlAccessType.NONE)
public abstract class BaseScenario {
/**
* Laufzeitinformationen über eine Transformation.
*/
@Getter
@Setter
@AllArgsConstructor
public static class Transformation {
private XsltExecutable executable;
private ResourceType resourceType;
}
@XmlTransient
@Getter
@Setter
private boolean fallback;
private XPathExecutable matchExecutable;
private XPathExecutable acceptExecutable;
@Setter
@XmlTransient
private Schema schema;
@Setter
private List<Transformation> schematronValidations;
private ContentRepository repository;
private Transformation reportTransformation;
/**
* Gibt eine Transformation zurück.
*
* @return initialisierte Transformation
*/
public Transformation getReportTransformation() {
if (this.reportTransformation == null) {
final ResourceType resource = getCreateReport().getResource();
final XsltExecutable executable = this.repository.loadXsltScript(URI.create(resource.getLocation()));
this.reportTransformation = new Transformation(executable, resource);
}
return this.reportTransformation;
}
/**
* Lieferrt das Schema zu diesem Szenario.
*
* @return das passende Schema
*/
public Schema getSchema() {
if (this.schema == null && getValidateWithXmlSchema() != null) {
final List<String> schemaResources = getValidateWithXmlSchema().getResource().stream().map(ResourceType::getLocation)
.collect(Collectors.toList());
this.schema = this.repository.createSchema(schemaResources);
}
return this.schema;
}
/**
* Initialisiert das Szenario auf Basis eines [@link ContentRepository}
*
* @param repository das Repository mit den Szenario-Artefakten
* @param lazy optionales lazy loading der XML-Artefakte
* @return true wenn erfolgreich
*/
public boolean initialize(final ContentRepository repository, final boolean lazy) {
this.repository = repository;
if (!lazy) {
getSchema();
getSelector();
getSchematronValidations();
getReportTransformation();
}
return true;
}
/**
* Liefer eine Liste mit Schematron Validierungs-Transformationen.
*
* @return liste mit initialisierten Transformationsinformationen
*/
public List<Transformation> getSchematronValidations() {
if (this.schematronValidations == null) {
this.schematronValidations = new ArrayList<>();
getValidateWithSchematron().forEach(v -> {
if (v.isPsvi()) {
throw new NotImplementedException("This implemenation does not support PSVI usage");
}
final XsltExecutable xsltExecutable = this.repository.loadXsltScript(URI.create(v.getResource().getLocation()));
this.schematronValidations.add(new Transformation(xsltExecutable, v.getResource()));
});
}
return this.schematronValidations;
}
/**
* Der XPath-Selector zur Identifikation des Scenarios.
*
* @return vorbereiteten selector
* @see ScenarioRepository#selectScenario(net.sf.saxon.s9api.XdmNode)
*/
public XPathSelector getSelector() {
if (this.matchExecutable == null) {
this.matchExecutable = this.repository.createXPath(getMatch(), prepareNamespaces());
}
return this.matchExecutable.load();
}
/**
* Liefert einen neuen XPath-Selector zur Evaluierung der {@link de.kosit.validationtool.api.AcceptRecommendation}.
*
* @return neuer Selector
*/
public XPathSelector getAcceptSelector() {
if (this.acceptExecutable == null) {
this.acceptExecutable = this.repository.createXPath(getAcceptMatch(), prepareNamespaces());
}
return this.acceptExecutable.load();
}
private Map<String, String> prepareNamespaces() {
return getNamespace().stream().collect(Collectors.toMap(NamespaceType::getPrefix, NamespaceType::getValue));
}
/**
* Getter aus dem schema.
*
* @return xpath match
*/
public abstract String getMatch();
public abstract String getAcceptMatch();
/**
* Getter aus dem schema.
*
* @return {@link List} of {@link NamespaceType}
*/
public abstract List<NamespaceType> getNamespace();
/**
* Getter aus dem schema.
*
* @return Validierungsanweisungen
*/
public abstract ValidateWithXmlSchema getValidateWithXmlSchema();
/**
* Getter aus dem schema.
*
* @return Validierungsanweisungne
*/
public abstract List<ValidateWithSchematron> getValidateWithSchematron();
/**
* Getter aus dem schema.
*
* @return report informationen
*/
public abstract CreateReportType getCreateReport();
}

View file

@ -30,11 +30,11 @@ import lombok.Setter;
import de.kosit.validationtool.api.AcceptRecommendation;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.impl.Scenario;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.ProcessingError;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.XdmNode;
@ -55,7 +55,7 @@ public interface CheckAction {
@Setter
class Bag {
private Result<ScenarioType, String> scenarioSelectionResult;
private Result<Scenario, String> scenarioSelectionResult;
@Setter(AccessLevel.NONE)
private CreateReportInput reportInput;

View file

@ -1,6 +1,6 @@
package de.kosit.validationtool.impl.tasks;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.Optional;
import org.oclc.purl.dsdl.svrl.FailedAssert;
@ -25,9 +25,9 @@ public class ComputeAcceptanceAction implements CheckAction {
@Override
public void check(final Bag results) {
if (preCondtionsMatch(results)) {
final String acceptMatch = results.getScenarioSelectionResult().getObject().getAcceptMatch();
if (results.getSchemaValidationResult().isValid() && isNotBlank(acceptMatch)) {
evaluateAcceptanceMatch(results);
final Optional<XPathSelector> acceptMatch = results.getScenarioSelectionResult().getObject().getAcceptSelector();
if (results.getSchemaValidationResult().isValid() && acceptMatch.isPresent()) {
evaluateAcceptanceMatch(results, acceptMatch.get());
} else {
evaluateSchemaAndSchematron(results);
}
@ -53,15 +53,14 @@ public class ComputeAcceptanceAction implements CheckAction {
.flatMap(e -> e.getActivePatternAndFiredRuleAndFailedAssert().stream()).anyMatch(FailedAssert.class::isInstance);
}
private static void evaluateAcceptanceMatch(final Bag results) {
private static void evaluateAcceptanceMatch(final Bag results, final XPathSelector selector) {
try {
final XPathSelector selector = results.getScenarioSelectionResult().getObject().getAcceptSelector();
selector.setContextItem(results.getReport());
results.setAcceptStatus(selector.effectiveBooleanValue() ? AcceptRecommendation.ACCEPTABLE : AcceptRecommendation.REJECT);
} catch (final SaxonApiException e) {
final String msg = "Error evaluating accept recommendation: %s";
log.error(msg);
results.addProcessingError(msg);
final String msg = String.format("Error evaluating accept recommendation: %s", selector.getUnderlyingXPathContext().toString());
log.error(msg, e);
results.stopProcessing(msg);
}
}

View file

@ -1,6 +1,5 @@
package de.kosit.validationtool.impl;
package de.kosit.validationtool.impl.tasks;
import de.kosit.validationtool.impl.tasks.CheckAction;
import de.kosit.validationtool.model.reportInput.DocumentIdentificationType;
/**
@ -8,7 +7,7 @@ import de.kosit.validationtool.model.reportInput.DocumentIdentificationType;
*
* @author Andreas Penski
*/
class CreateDocumentIdentificationAction implements CheckAction {
public class CreateDocumentIdentificationAction implements CheckAction {
@Override
public void check(final Bag transporter) {

View file

@ -19,26 +19,34 @@
package de.kosit.validationtool.impl.tasks;
import java.io.IOException;
import java.util.Collection;
import java.util.stream.Collectors;
import javax.xml.transform.dom.DOMSource;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.transform.URIResolver;
import org.w3c.dom.Document;
import lombok.extern.slf4j.Slf4j;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import lombok.RequiredArgsConstructor;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.ConversionService;
import de.kosit.validationtool.impl.EngineInformation;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.RelativeUriResolver;
import de.kosit.validationtool.impl.ScenarioRepository;
import de.kosit.validationtool.impl.Scenario;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.BuildingContentHandler;
import net.sf.saxon.s9api.DocumentBuilder;
@ -57,18 +65,117 @@ import net.sf.saxon.s9api.XsltTransformer;
* @author Andreas Penski
*/
@RequiredArgsConstructor
@Slf4j
public class CreateReportAction implements CheckAction {
/**
* Wrapper to fix some inconsistencies between sax and saxon. Saxon tries to set some properties which has no effect on
* {@link JAXBSource}'s XMLReader, but it throws exceptions on unknown properties. This just drops this exceptions.
*/
private static class ReaderWrapper implements XMLReader {
private static final String SAX_FEATURES_NAMESPACE_PREFIXES = "http://xml.org/sax/features/namespace-prefixes";
private static final String SAX_FEATURES_NAMESPACES = "http://xml.org/sax/features/namespaces";
private final XMLReader delegate;
public ReaderWrapper(final XMLReader xmlReader) {
this.delegate = xmlReader;
}
@Override
public boolean getFeature(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
if (SAX_FEATURES_NAMESPACES.equals(name)) {
return true;
} else if (SAX_FEATURES_NAMESPACE_PREFIXES.equals(name)) {
return false;
}
// just return false on unknown properties
return false;
}
@Override
public void setFeature(final String name, final boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
// this inverts the logic from JaxbSource pseude parser
if (name.equals(SAX_FEATURES_NAMESPACES) && !value) {
throw new SAXNotRecognizedException(name);
}
if (name.equals(SAX_FEATURES_NAMESPACE_PREFIXES) && value) {
throw new SAXNotRecognizedException(name);
}
}
@Override
public Object getProperty(final String name) throws SAXNotRecognizedException, SAXNotSupportedException {
return this.delegate.getProperty(name);
}
@Override
public void setProperty(final String name, final Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
this.delegate.setProperty(name, value);
}
@Override
public void setEntityResolver(final EntityResolver resolver) {
this.delegate.setEntityResolver(resolver);
}
@Override
public EntityResolver getEntityResolver() {
return this.delegate.getEntityResolver();
}
@Override
public void setDTDHandler(final DTDHandler handler) {
this.delegate.setDTDHandler(handler);
}
@Override
public DTDHandler getDTDHandler() {
return this.delegate.getDTDHandler();
}
@Override
public void setContentHandler(final ContentHandler handler) {
this.delegate.setContentHandler(handler);
}
@Override
public ContentHandler getContentHandler() {
return this.delegate.getContentHandler();
}
@Override
public void setErrorHandler(final ErrorHandler handler) {
this.delegate.setErrorHandler(handler);
}
@Override
public ErrorHandler getErrorHandler() {
return this.delegate.getErrorHandler();
}
@Override
public void parse(final InputSource input) throws IOException, SAXException {
this.delegate.parse(input);
}
@Override
public void parse(final String systemId) throws IOException, SAXException {
this.delegate.parse(systemId);
}
}
private static final String ERROR_MESSAGE_ELEMENT = "error-message";
private final Processor processor;
private final ConversionService conversionService;
private final ScenarioRepository scenarioRepository;
private final URIResolver resolver;
private final ContentRepository contentRepository;
private static XsltExecutable loadFromScenario(final ScenarioType object) {
private static XsltExecutable loadFromScenario(final Scenario object) {
return object.getReportTransformation().getExecutable();
}
@ -80,15 +187,18 @@ public class CreateReportAction implements CheckAction {
final XdmNode parsedDocument = results.getParserResult().isValid() ? results.getParserResult().getObject()
: createErrorInformation(results.getParserResult().getErrors());
final Document reportInput = this.conversionService.writeDocument(results.getReportInput());
final XdmNode root = documentBuilder.build(new DOMSource(reportInput));
final Marshaller marshaller = this.conversionService.getJaxbContext().createMarshaller();
final JAXBSource source = new JAXBSource(marshaller, results.getReportInput());
// wrap to circumvent inconsistency between sax and saxon
source.setXMLReader(new ReaderWrapper(source.getXMLReader()));
final XdmNode root = documentBuilder.build(source);
final XsltTransformer transformer = getTransformation(results).load();
transformer.setInitialContextNode(root);
final CollectingErrorEventHandler e = new CollectingErrorEventHandler();
final RelativeUriResolver resolver = this.contentRepository.createResolver();
transformer.setMessageListener(e);
transformer.setURIResolver(resolver);
transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver);
transformer.setURIResolver(this.resolver);
// transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver);
if (parsedDocument != null) {
transformer.setParameter(new QName("input-document"), parsedDocument);
}
@ -97,13 +207,14 @@ public class CreateReportAction implements CheckAction {
transformer.transform();
results.setReport(destination.getXdmNode());
} catch (final SaxonApiException | SAXException e) {
throw new IllegalStateException("Can not create final report", e);
} catch (final SaxonApiException | SAXException | JAXBException e) {
log.error("Error creating final report", e);
results.stopProcessing("Can not create final report: " + e.getMessage());
}
}
private static XdmNode createErrorInformation(final Collection<XMLSyntaxError> errors) throws SaxonApiException, SAXException {
final BuildingContentHandler contentHandler = ObjectFactory.createProcessor().newDocumentBuilder().newBuildingContentHandler();
private XdmNode createErrorInformation(final Collection<XMLSyntaxError> errors) throws SaxonApiException, SAXException {
final BuildingContentHandler contentHandler = this.processor.newDocumentBuilder().newBuildingContentHandler();
contentHandler.startDocument();
contentHandler.startElement(EngineInformation.getFrameworkNamespace(), ERROR_MESSAGE_ELEMENT, ERROR_MESSAGE_ELEMENT,
new AttributesImpl());

View file

@ -27,13 +27,13 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.ValidationResultsWellformedness;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.reportInput.XMLSyntaxErrorSeverity;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmNode;
@ -46,6 +46,7 @@ import net.sf.saxon.s9api.XdmNode;
@RequiredArgsConstructor
public class DocumentParseAction implements CheckAction {
private final Processor processor;
/**
* Parsed und überprüft ein übergebenes Dokument darauf ob es well-formed ist. Dies stellt den ersten
* Verarbeitungsschritt des Prüf-Tools dar. Diese Funktion verzichtet explizit auf die Validierung gegenüber einem
@ -54,13 +55,13 @@ public class DocumentParseAction implements CheckAction {
* @param content ein Dokument
* @return Ergebnis des Parsings inklusive etwaiger Fehler
*/
public static Result<XdmNode, XMLSyntaxError> parseDocument(final Input content) {
public Result<XdmNode, XMLSyntaxError> parseDocument(final Input content) {
if (content == null) {
throw new IllegalArgumentException("Input may not be null");
}
Result<XdmNode, XMLSyntaxError> result;
try {
final DocumentBuilder builder = ObjectFactory.createProcessor().newDocumentBuilder();
final DocumentBuilder builder = this.processor.newDocumentBuilder();
builder.setLineNumbering(true);
final XdmNode doc = builder.build(content.getSource());
result = new Result<>(doc, Collections.emptyList());

View file

@ -22,10 +22,10 @@ package de.kosit.validationtool.impl.tasks;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.Scenario;
import de.kosit.validationtool.impl.ScenarioRepository;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.XdmNode;
@ -44,7 +44,7 @@ public class ScenarioSelectionAction implements CheckAction {
@Override
public void check(final Bag results) {
final CreateReportInput report = results.getReportInput();
final Result<ScenarioType, String> scenarioTypeResult;
final Result<Scenario, String> scenarioTypeResult;
if (results.getParserResult().isValid()) {
scenarioTypeResult = determineScenario(results.getParserResult().getObject());
@ -53,15 +53,15 @@ public class ScenarioSelectionAction implements CheckAction {
}
results.setScenarioSelectionResult(scenarioTypeResult);
if (!scenarioTypeResult.getObject().isFallback()) {
report.setScenario(scenarioTypeResult.getObject());
report.setScenario(scenarioTypeResult.getObject().getConfiguration());
log.info("Schenario {} identified for {}", scenarioTypeResult.getObject().getName(), results.getInput().getName());
} else {
log.error("No valid schenario configuration found for {}", results.getInput().getName());
}
}
private Result<ScenarioType, String> determineScenario(final XdmNode document) {
final Result<ScenarioType, String> result = this.repository.selectScenario(document);
private Result<Scenario, String> determineScenario(final XdmNode document) {
final Result<Scenario, String> result = this.repository.selectScenario(document);
if (result.isInvalid()) {
return new Result<>(this.repository.getFallbackScenario());
}

View file

@ -36,19 +36,21 @@ import org.xml.sax.SAXException;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.Scenario;
import de.kosit.validationtool.impl.input.AbstractInput;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.ValidationResultsXmlSchema;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XdmNode;
@ -67,16 +69,20 @@ import net.sf.saxon.s9api.XdmNode;
* @author Andreas Penski
*/
@Slf4j
@RequiredArgsConstructor
public class SchemaValidationAction implements CheckAction {
@RequiredArgsConstructor
private static class ByteArraySerializedDocument implements SerializedDocument {
private byte[] bytes;
private final Processor processor;
@Override
public void serialize(final XdmNode node) throws SaxonApiException, IOException {
try ( final ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
final Serializer serializer = ObjectFactory.createProcessor().newSerializer();
final Serializer serializer = this.processor.newSerializer();
serializer.setOutputStream(out);
serializer.serializeNode(node);
serializer.close();
@ -97,16 +103,20 @@ public class SchemaValidationAction implements CheckAction {
private static class FileSerializedDocument implements SerializedDocument {
private final Path file;
FileSerializedDocument() throws IOException {
private final Processor processor;
FileSerializedDocument(final Processor processor) throws IOException {
this.file = Files.createTempFile("validator", ".xml");
this.processor = processor;
}
@Override
public void serialize(final XdmNode node) throws SaxonApiException, IOException {
try ( final OutputStream out = Files.newOutputStream(this.file) ) {
final Serializer serializer = ObjectFactory.createProcessor().newSerializer();
final Serializer serializer = this.processor.newSerializer();
serializer.setOutputStream(out);
serializer.serializeNode(node);
serializer.close();
@ -128,21 +138,25 @@ public class SchemaValidationAction implements CheckAction {
private static final String LIMIT_PARAMETER = "schema.validation.inmem.limit";
private final ResolvingConfigurationStrategy factory;
private final Processor processor;
@Setter(AccessLevel.PACKAGE)
@Getter
private long inMemoryLimit = Long.parseLong(System.getProperty(LIMIT_PARAMETER, BA_LIMIT.toString())) * FileUtils.ONE_MB;
private Result<Boolean, XMLSyntaxError> validate(final Bag results, final ScenarioType scenarioType) {
log.debug("Validating document using scenario {}", scenarioType.getName());
private Result<Boolean, XMLSyntaxError> validate(final Bag results, final Scenario scenario) {
log.debug("Validating document using scenario {}", scenario.getConfiguration().getName());
final CollectingErrorEventHandler errorHandler = new CollectingErrorEventHandler();
try ( final SourceProvider validateInput = resolveSource(results) ) {
final Validator validator = ObjectFactory.createValidator(scenarioType.getSchema());
final Validator validator = this.factory.createValidator(scenario.getSchema());
validator.setErrorHandler(errorHandler);
validator.validate(validateInput.getSource());
return new Result<>(!errorHandler.hasErrors(), errorHandler.getErrors());
} catch (final SAXException | SaxonApiException | IOException e) {
final String msg = String.format("Error processing schema validation for scenario %s", scenarioType.getName());
final String msg = String.format("Error processing schema validation for scenario %s", scenario.getConfiguration().getName());
log.error(msg, e);
results.addProcessingError(msg);
return new Result<>(Boolean.FALSE);
@ -152,14 +166,14 @@ public class SchemaValidationAction implements CheckAction {
@Override
public void check(final Bag results) {
final CreateReportInput report = results.getReportInput();
final ScenarioType scenario = results.getScenarioSelectionResult().getObject();
final Scenario scenario = results.getScenarioSelectionResult().getObject();
final Result<Boolean, XMLSyntaxError> validateResult = validate(results, scenario);
results.setSchemaValidationResult(validateResult);
final ValidationResultsXmlSchema result = new ValidationResultsXmlSchema();
report.setValidationResultsXmlSchema(result);
result.getResource().addAll(scenario.getValidateWithXmlSchema().getResource());
result.getResource().addAll(scenario.getConfiguration().getValidateWithXmlSchema().getResource());
if (!validateResult.isValid()) {
result.getXmlSyntaxError().addAll(validateResult.getErrors());
}
@ -180,9 +194,9 @@ public class SchemaValidationAction implements CheckAction {
private SerializedDocument serialize(final Input input, final XdmNode object) throws IOException, SaxonApiException {
final SerializedDocument doc;
if (input instanceof AbstractInput && ((AbstractInput) input).getLength() < getInMemoryLimit()) {
doc = new ByteArraySerializedDocument();
doc = new ByteArraySerializedDocument(this.processor);
} else {
doc = new FileSerializedDocument();
doc = new FileSerializedDocument(this.processor);
}
doc.serialize(object);
return doc;

View file

@ -22,26 +22,23 @@ package de.kosit.validationtool.impl.tasks;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import org.oclc.purl.dsdl.svrl.SchematronOutput;
import org.w3c.dom.Document;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.CollectingErrorEventHandler;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.ConversionService;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.RelativeUriResolver;
import de.kosit.validationtool.impl.model.BaseScenario;
import de.kosit.validationtool.impl.Scenario;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.ValidationResultsSchematron;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.DOMDestination;
import net.sf.saxon.dom.NodeOverNodeInfo;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmDestination;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XsltTransformer;
@ -54,32 +51,33 @@ import net.sf.saxon.s9api.XsltTransformer;
@Slf4j
public class SchematronValidationAction implements CheckAction {
private final ContentRepository repository;
private final URIResolver resolver;
private final ConversionService conversionService;
private List<ValidationResultsSchematron> validate(final Bag results, final XdmNode document, final ScenarioType scenario) {
private List<ValidationResultsSchematron> validate(final Bag results, final XdmNode document, final Scenario scenario) {
return scenario.getSchematronValidations().stream().map(v -> validate(results, document, v)).collect(Collectors.toList());
}
private ValidationResultsSchematron validate(final Bag results, final XdmNode document, final BaseScenario.Transformation validation) {
private ValidationResultsSchematron validate(final Bag results, final XdmNode document, final Scenario.Transformation validation) {
final ValidationResultsSchematron s = new ValidationResultsSchematron();
s.setResource(validation.getResourceType());
try {
final XsltTransformer transformer = validation.getExecutable().load();
// resolving nur relative zum Repository
final RelativeUriResolver resolver = this.repository.createResolver();
transformer.setURIResolver(resolver);
transformer.setURIResolver(this.resolver);
final CollectingErrorEventHandler e = new CollectingErrorEventHandler();
transformer.setMessageListener(e);
final Document result = ObjectFactory.createDocumentBuilder(false).newDocument();
transformer.setDestination(new DOMDestination(result));
final XdmDestination result = new XdmDestination();
transformer.setDestination(result);
transformer.setInitialContextNode(document);
transformer.transform();
final ValidationResultsSchematron.Results r = new ValidationResultsSchematron.Results();
r.setSchematronOutput(this.conversionService.readDocument(new DOMSource(result), SchematronOutput.class));
r.setSchematronOutput(this.conversionService.readDocument(
new DOMSource(NodeOverNodeInfo.wrap(result.getXdmNode().getUnderlyingNode()).getOwnerDocument()),
SchematronOutput.class));
s.setResults(r);
} catch (final SaxonApiException e) {
@ -107,7 +105,7 @@ public class SchematronValidationAction implements CheckAction {
return results.getSchemaValidationResult() == null || results.getSchemaValidationResult().isInvalid();
}
private static boolean hasNoSchematrons(final ScenarioType object) {
return object.getValidateWithSchematron() == null || object.getValidateWithSchematron().size() == 0;
private static boolean hasNoSchematrons(final Scenario object) {
return object.getSchematronValidations().isEmpty();
}
}

View file

@ -0,0 +1,121 @@
package de.kosit.validationtool.impl.xml;
import static java.lang.String.format;
import javax.xml.XMLConstants;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXException;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
import net.sf.saxon.s9api.Processor;
/**
* @author Andreas Penski
*/
@Slf4j
public abstract class BaseResolvingStrategy implements ResolvingConfigurationStrategy {
protected static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
protected static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
protected static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing";
private static final String ORACLE_XERCES_CLASS = "com.sun.org.apache.xerces.internal.impl.Constants";
private Processor processor;
@Override
public Processor getProcessor() {
if (this.processor == null) {
this.processor = createProcessor();
}
return this.processor;
}
protected abstract Processor createProcessor();
public static void forceOpenJdkXmlImplementation() {
if (!isOpenJdkXmlImplementationAvailable()) {
throw new IllegalStateException("No OpenJDK version of XERCES found");
}
}
public static boolean isOpenJdkXmlImplementationAvailable() {
try {
Class.forName(ORACLE_XERCES_CLASS);
return true;
} 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");
return false;
}
}
private void setProperty(final PropertySetter setter, final boolean lenient, final String errorMessage) {
try {
setter.apply();
} catch (final SAXException e) {
if (lenient) {
log.warn(errorMessage);
log.debug(e.getMessage(), e);
} else {
throw new IllegalStateException(errorMessage);
}
}
}
protected void allowExternalSchema(final Validator validator, final String... scheme) {
allowExternalSchema(validator, false, scheme);
}
protected void allowExternalSchema(final SchemaFactory schemaFactory, final String... scheme) {
allowExternalSchema(schemaFactory, false, scheme);
}
protected void allowExternalSchema(final Validator validator, final boolean lenient, final String... schemes) {
final String schemeString = String.join(",", schemes);
setProperty(() -> validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, schemeString), lenient, format(
"Can set external schema access to schemes (%s). Maybe an unsupported JAXP implementation is used.", schemeString));
}
protected void allowExternalSchema(final SchemaFactory schemaFactory, final boolean lenient, final String... schemes) {
final String schemeString = String.join(",", schemes);
setProperty(() -> schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, schemeString), lenient, format(
"Can set external schema access to schemes (%s). Maybe an unsupported JAXP implementation is used.", schemeString));
}
protected void disableExternalEntities(final Validator validator) {
disableExternalEntities(validator, false);
}
protected void disableExternalEntities(final SchemaFactory schemaFactory) {
disableExternalEntities(schemaFactory, false);
}
protected void disableExternalEntities(final Validator validator, final boolean lenient) {
log.debug("Try to disable extern DTD access");
setProperty(() -> validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""), lenient,
"Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
}
protected void disableExternalEntities(final SchemaFactory schemaFactory, final boolean lenient) {
log.debug("Try to disable extern DTD access");
setProperty(() -> schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""), lenient,
"Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
}
@FunctionalInterface
private interface PropertySetter {
void apply() throws SAXException;
}
}

View file

@ -17,7 +17,7 @@
* under the License.
*/
package de.kosit.validationtool.impl;
package de.kosit.validationtool.impl.xml;
import java.io.IOException;
import java.io.InputStreamReader;
@ -25,14 +25,13 @@ import java.io.Reader;
import java.net.URI;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.UnparsedTextURIResolver;
import net.sf.saxon.trans.XPathException;
/**
@ -41,24 +40,24 @@ import net.sf.saxon.trans.XPathException;
*
* @author Andreas Penski
*/
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class RelativeUriResolver implements URIResolver, UnparsedTextURIResolver {
@RequiredArgsConstructor()
public class RelativeUriResolver implements URIResolver {
/** the base uri */
private final URI baseUri;
@Override
public Source resolve(final String href, final String base) {
public Source resolve(final String href, final String base) throws TransformerException {
final URI resolved = resolve(URI.create(href), URI.create(base));
if (isUnderBaseUri(resolved)) {
try {
return new StreamSource(resolved.toURL().openStream(), resolved.toASCIIString());
} catch (final IOException e) {
throw new IllegalStateException(String.format("Can not resolve required %s", href), e);
throw new TransformerException(String.format("Can not resolve required %s", href), e);
}
} else {
throw new IllegalStateException(String
throw new TransformerException(String
.format("The resolved transformation artifact %s is not within the configured repository %s", resolved, this.baseUri));
}
}
@ -82,22 +81,18 @@ public class RelativeUriResolver implements URIResolver, UnparsedTextURIResolver
}
private boolean isUnderBaseUri(final URI resolved) {
final String base = this.baseUri.toASCIIString().replaceAll("file:/+", "");
return isUnderBaseUri(resolved, this.baseUri);
}
private static boolean isUnderBaseUri(final URI resolved, final URI baseUri) {
if (resolved == null || baseUri == null) {
return false;
}
final String base = baseUri.toASCIIString().replaceAll("file:/+", "");
final String r = resolved.toASCIIString().replaceAll("file:/+", "");
return r.startsWith(base);
}
@Override
public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException {
if (isUnderBaseUri(absoluteURI)) {
try {
return new InputStreamReader(absoluteURI.toURL().openStream(), encoding);
} catch (final IOException e) {
throw new IllegalStateException(String.format("Can not resolve required %s", absoluteURI), e);
}
} else {
throw new IllegalStateException(String.format(
"The resolved transformation artifact %s is not within the configured repository %s", absoluteURI, this.baseUri));
}
}
}

View file

@ -0,0 +1,13 @@
package de.kosit.validationtool.impl.xml;
import javax.xml.validation.SchemaFactory;
public class RemoteResolvingStrategy extends StrictLocalResolvingStrategy {
@Override
public SchemaFactory createSchemaFactory() {
final SchemaFactory schemaFactory = super.createSchemaFactory();
allowExternalSchema(schemaFactory, "https,http,file");
return schemaFactory;
}
}

View file

@ -0,0 +1,53 @@
package de.kosit.validationtool.impl.xml;
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 lombok.extern.slf4j.Slf4j;
/**
* This is a slightly more open implementation that allows resolving artifacts from local filesystems. Your are not
* bound to a specific 'repository'. But your validation artifacts (schema, xsl, etc.) must be available locally. This
* implementation does not allow loading from http sources
*
* @author Andreas Penski
*/
@Slf4j
public class StrictLocalResolvingStrategy extends StrictRelativeResolvingStrategy {
/**
* Allow loading schema files from any local location.
*
* @return a configured {@link SchemaFactory}
*/
@Override
public SchemaFactory createSchemaFactory() {
final SchemaFactory schemaFactory = super.createSchemaFactory();
allowExternalSchema(schemaFactory, "file");
return schemaFactory;
}
/**
* The default resolver is able to resolve locally and relative.
*
* @param repository the repository is not used by this strategy
* @return null!
*/
@Override
public URIResolver createResolver(final URI repository) {
// intentionally return 'null', since all resolving is configured with the other objects
return null;
}
@Override
public Validator createValidator(final Schema schema) {
final Validator validator = super.createValidator(schema);
allowExternalSchema(validator, "file");
return validator;
}
}

View file

@ -0,0 +1,125 @@
package de.kosit.validationtool.impl.xml;
import java.io.Reader;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.xml.XMLConstants;
import javax.xml.transform.Result;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import net.sf.saxon.Configuration;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.CollectionFinder;
import net.sf.saxon.lib.Feature;
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;
/**
* @author Andreas Penski
*/
@RequiredArgsConstructor
public class StrictRelativeResolvingStrategy extends BaseResolvingStrategy {
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);
}
}
/**
* e.g. don't allow any scheme
*/
private static final String EMPTY_SCHEME = "";
@Override
public SchemaFactory createSchemaFactory() {
final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
disableExternalEntities(sf);
allowExternalSchema(sf, "file");
return sf;
}
@Override
protected Processor createProcessor() {
final Processor processor = new Processor(false);
// verhindere global im Prinzip alle resolving strategien
final SecureUriResolver resolver = new SecureUriResolver();
processor.getUnderlyingConfiguration().setCollectionFinder(resolver);
processor.getUnderlyingConfiguration().setOutputURIResolver(resolver);
processor.getUnderlyingConfiguration().setUnparsedTextURIResolver(resolver);
// grundsätzlich Feature-konfiguration:
processor.setConfigurationProperty(Feature.DTD_VALIDATION, false);
processor.setConfigurationProperty(Feature.ENTITY_RESOLVER_CLASS, "");
processor.setConfigurationProperty(Feature.XINCLUDE, false);
processor.setConfigurationProperty(Feature.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);
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(XMLConstants.ACCESS_EXTERNAL_DTD), false);
return processor;
}
@SneakyThrows
private static String encode(final String input) {
return URLEncoder.encode(input, StandardCharsets.UTF_8.name());
}
@Override
public URIResolver createResolver(final URI repositoryURI) {
return new RelativeUriResolver(repositoryURI);
}
@Override
public Validator createValidator(final Schema schema) {
if (schema == null) {
throw new IllegalArgumentException("No schema supplied. Can not create validator");
}
forceOpenJdkXmlImplementation();
final Validator validator = schema.newValidator();
disableExternalEntities(validator);
allowExternalSchema(validator, "file" /* allow nothing external */);
return validator;
}
}

View file

@ -42,21 +42,21 @@
<jaxb:schemaBindings>
<jaxb:package name="de.kosit.validationtool.model.scenarios"/>
</jaxb:schemaBindings>
<jaxb:bindings node="//xs:complexType[@name='ScenarioType']">
<inheritance:extends>de.kosit.validationtool.impl.model.BaseScenario</inheritance:extends>
</jaxb:bindings>
</jaxb:bindings>
<jaxb:bindings schemaLocation="../xsd/assertions.xsd">
<jaxb:schemaBindings>
<jaxb:package name="de.kosit.validationtool.cmd.assertions"/>
<jaxb:package name="de.kosit.validationtool.cmd.assertions" />
</jaxb:schemaBindings>
</jaxb:bindings>
<jaxb:bindings schemaLocation="../xsd/svrl-kosit.xsd">
<jaxb:bindings node="//xs:element[@name='schematron-output']/xs:complexType">
<inheritance:extends>de.kosit.validationtool.impl.model.BaseOutput</inheritance:extends>
</jaxb:bindings>
</jaxb:bindings>
<jaxb:bindings schemaLocation="../xsd/daemon.xsd">
<jaxb:schemaBindings>
<jaxb:package name="de.kosit.validationtool.model.daemon" />
</jaxb:schemaBindings>
</jaxb:bindings>
</jaxb:bindings>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns:d="http://www.xoev.de/de/validator/framework/1/daemon" targetNamespace="http://www.xoev.de/de/validator/framework/1/daemon"
elementFormDefault="qualified"
xmlns="http://www.w3.org/2001/XMLSchema" version="1.0.0">
<element name="health" type="d:HealthType"></element>
<complexType name="ApplicationType">
<sequence>
<element name="name" type="string"></element>
<element name="version" type="string"></element>
<element name="build" type="string"></element>
</sequence>
</complexType>
<complexType name="HealthType">
<sequence>
<element name="status">
<simpleType>
<restriction base="string">
<enumeration value="UP"></enumeration>
<enumeration value="DOWN"></enumeration>
<enumeration value="WARN"></enumeration>
</restriction>
</simpleType>
</element>
<element name="application" type="d:ApplicationType"></element>
<element name="memory" type="d:MemoryType"></element>
</sequence>
</complexType>
<complexType name="MemoryType">
<sequence>
<element name="freeMemory" type="long"></element>
<element name="maxMemory" type="long"></element>
<element name="totalMemory" type="long"></element>
</sequence>
</complexType>
</schema>

View file

@ -0,0 +1,22 @@
# KoSIT Validator - Daemon
[API usage](docs/api)
[configurations](docs/configurations)
# Server information
View [validator configuration](/server/config) or <a href="/server/health" target="_blank">health information</a>
# Try it
<div>
<form>
<div>
<label for="file">Choose a file</label>
<input type="file" id="file" name="myFile">
<input type="submit" id="submit" value="Validate" onclick="return validate();">
</div>
<div id="result"></div>
</form>
</div>

View file

@ -0,0 +1,42 @@
# API Usage
The validation service listens to `POST`-requests to any server uri. You need to supply the xml/object to validate in the post body.
The service expects a single plain input in the post 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 = "<file contents here>";
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));
```

View file

@ -0,0 +1,15 @@
# Configurations
The validator needs a scenario configuration for working properly.
Currently, there are two public third party validation configurations available.
* 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](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
For creating custom configurations see [configaration documentation](https://github.com/itplr-kosit/validator/blob/master/docs/configurations.md)
for details

View file

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Validator</title>
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1" name="viewport">
<link href="lib/vue.css" rel="stylesheet">
<style>
#result {
border: 1pt solid black;
margin-top: 20px
}
#result:empty {
display: none;
}
</style>
</head>
<body>
<script type="text/javascript">
var validate = function () {
const input = document.getElementById('file');
const output = document.getElementById('result');
var headers = new Headers();
headers.append('Content-Type', 'application/xml');
var requestOptions = {
method: 'POST',
headers: headers,
body: input.files[0],
redirect: 'follow',
};
fetch('/', requestOptions)
.then(response => response.text())
.then(result => output.innerText = result)
.catch(error => output.innerText = result);
return false;
};
</script>
<div data-app id="app">Loading validator... </div>
<script>
window.$docsify = {
repo: "itplr-kosit/validator",
loadSidebar: false,
hideSidebar: true,
autoHeader: true,
}
</script>
<script src="lib/docsify.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,23 @@
Sources in this diretory are based on https://github.com/docsifyjs/docsify/
MIT License
Copyright (c) 2016 - present cinwell.li
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,828 @@
/*@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*/
* {
-webkit-font-smoothing: antialiased;
-webkit-overflow-scrolling: touch;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-text-size-adjust: none;
-webkit-touch-callout: none;
box-sizing: border-box;
}
body:not(.ready) {
overflow: hidden;
}
body:not(.ready) [data-cloak],
body:not(.ready) .app-nav,
body:not(.ready) > nav {
display: none;
}
div#app {
font-size: 30px;
font-weight: lighter;
margin: 40vh auto;
text-align: center;
}
div#app:empty::before {
content: 'Loading...';
}
.emoji {
height: 1.2rem;
vertical-align: middle;
}
.progress {
background-color: var(--theme-color, #42b983);
height: 2px;
left: 0px;
position: fixed;
right: 0px;
top: 0px;
transition: width 0.2s, opacity 0.4s;
width: 0%;
z-index: 999999;
}
.search a:hover {
color: var(--theme-color, #42b983);
}
.search .search-keyword {
color: var(--theme-color, #42b983);
font-style: normal;
font-weight: bold;
}
html,
body {
height: 100%;
}
body {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
color: #34495e;
font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
font-size: 15px;
letter-spacing: 0;
margin: 0;
overflow-x: hidden;
}
img {
max-width: 100%;
}
a[disabled] {
cursor: not-allowed;
opacity: 0.6;
}
kbd {
border: solid 1px #ccc;
border-radius: 3px;
display: inline-block;
font-size: 12px !important;
line-height: 12px;
margin-bottom: 3px;
padding: 3px 5px;
vertical-align: middle;
}
li input[type='checkbox'] {
margin: 0 0.2em 0.25em 0;
vertical-align: middle;
}
.app-nav {
margin: 25px 60px 0 0;
position: absolute;
right: 0;
text-align: right;
z-index: 10;
/* navbar dropdown */
}
.app-nav.no-badge {
margin-right: 25px;
}
.app-nav p {
margin: 0;
}
.app-nav > a {
margin: 0 1rem;
padding: 5px 0;
}
.app-nav ul,
.app-nav li {
display: inline-block;
list-style: none;
margin: 0;
}
.app-nav a {
color: inherit;
font-size: 16px;
text-decoration: none;
transition: color 0.3s;
}
.app-nav a:hover {
color: var(--theme-color, #42b983);
}
.app-nav a.active {
border-bottom: 2px solid var(--theme-color, #42b983);
color: var(--theme-color, #42b983);
}
.app-nav li {
display: inline-block;
margin: 0 1rem;
padding: 5px 0;
position: relative;
cursor: pointer;
}
.app-nav li ul {
background-color: #fff;
border: 1px solid #ddd;
border-bottom-color: #ccc;
border-radius: 4px;
box-sizing: border-box;
display: none;
max-height: calc(100vh - 61px);
overflow-y: auto;
padding: 10px 0;
position: absolute;
right: -15px;
text-align: left;
top: 100%;
white-space: nowrap;
}
.app-nav li ul li {
display: block;
font-size: 14px;
line-height: 1rem;
margin: 0;
margin: 8px 14px;
white-space: nowrap;
}
.app-nav li ul a {
display: block;
font-size: inherit;
margin: 0;
padding: 0;
}
.app-nav li ul a.active {
border-bottom: 0;
}
.app-nav li:hover ul {
display: block;
}
.github-corner {
border-bottom: 0;
position: fixed;
right: 0;
text-decoration: none;
top: 0;
z-index: 1;
}
.github-corner:hover .octo-arm {
-webkit-animation: octocat-wave 560ms ease-in-out;
animation: octocat-wave 560ms ease-in-out;
}
.github-corner svg {
color: #fff;
fill: var(--theme-color, #42b983);
height: 80px;
width: 80px;
}
main {
display: block;
position: relative;
width: 100vw;
height: 100%;
z-index: 0;
}
main.hidden {
display: none;
}
.anchor {
display: inline-block;
text-decoration: none;
transition: all 0.3s;
}
.anchor span {
color: #34495e;
}
.anchor:hover {
text-decoration: underline;
}
.sidebar {
border-right: 1px solid rgba(0,0,0,0.07);
overflow-y: auto;
padding: 40px 0 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
transition: transform 250ms ease-out;
width: 300px;
z-index: 20;
}
.sidebar > h1 {
margin: 0 auto 1rem;
font-size: 1.5rem;
font-weight: 300;
text-align: center;
}
.sidebar > h1 a {
color: inherit;
text-decoration: none;
}
.sidebar > h1 .app-nav {
display: block;
position: static;
}
.sidebar .sidebar-nav {
line-height: 2em;
padding-bottom: 40px;
}
.sidebar li.collapse .app-sub-sidebar {
display: none;
}
.sidebar ul {
margin: 0 0 0 15px;
padding: 0;
}
.sidebar li > p {
font-weight: 700;
margin: 0;
}
.sidebar ul,
.sidebar ul li {
list-style: none;
}
.sidebar ul li a {
border-bottom: none;
display: block;
}
.sidebar ul li ul {
padding-left: 20px;
}
.sidebar::-webkit-scrollbar {
width: 4px;
}
.sidebar::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 4px;
}
.sidebar:hover::-webkit-scrollbar-thumb {
background: rgba(136,136,136,0.4);
}
.sidebar:hover::-webkit-scrollbar-track {
background: rgba(136,136,136,0.1);
}
.sidebar-toggle {
background-color: transparent;
background-color: rgba(255,255,255,0.8);
border: 0;
outline: none;
padding: 10px;
position: absolute;
bottom: 0;
left: 0;
text-align: center;
transition: opacity 0.3s;
width: 284px;
z-index: 30;
cursor: pointer;
}
.sidebar-toggle:hover .sidebar-toggle-button {
opacity: 0.4;
}
.sidebar-toggle span {
background-color: var(--theme-color, #42b983);
display: block;
margin-bottom: 4px;
width: 16px;
height: 2px;
}
body.sticky .sidebar,
body.sticky .sidebar-toggle {
position: fixed;
}
.content {
padding-top: 60px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 300px;
transition: left 250ms ease;
}
.markdown-section {
margin: 0 auto;
max-width: 80%;
padding: 30px 15px 40px 15px;
position: relative;
}
.markdown-section > * {
box-sizing: border-box;
font-size: inherit;
}
.markdown-section > :first-child {
margin-top: 0 !important;
}
.markdown-section hr {
border: none;
border-bottom: 1px solid #eee;
margin: 2em 0;
}
.markdown-section iframe {
border: 1px solid #eee;
/* fix horizontal overflow on iOS Safari */
width: 1px;
min-width: 100%;
}
.markdown-section table {
border-collapse: collapse;
border-spacing: 0;
display: block;
margin-bottom: 1rem;
overflow: auto;
width: 100%;
}
.markdown-section th {
border: 1px solid #ddd;
font-weight: bold;
padding: 6px 13px;
}
.markdown-section td {
border: 1px solid #ddd;
padding: 6px 13px;
}
.markdown-section tr {
border-top: 1px solid #ccc;
}
.markdown-section tr:nth-child(2n) {
background-color: #f8f8f8;
}
.markdown-section p.tip {
background-color: #f8f8f8;
border-bottom-right-radius: 2px;
border-left: 4px solid #f66;
border-top-right-radius: 2px;
margin: 2em 0;
padding: 12px 24px 12px 30px;
position: relative;
}
.markdown-section p.tip:before {
background-color: #f66;
border-radius: 100%;
color: #fff;
content: '!';
font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
left: -12px;
line-height: 20px;
position: absolute;
height: 20px;
width: 20px;
text-align: center;
top: 14px;
}
.markdown-section p.tip code {
background-color: #efefef;
}
.markdown-section p.tip em {
color: #34495e;
}
.markdown-section p.warn {
background: rgba(66,185,131,0.1);
border-radius: 2px;
padding: 1rem;
}
.markdown-section ul.task-list > li {
list-style-type: none;
}
body.close .sidebar {
transform: translateX(-300px);
}
body.close .sidebar-toggle {
width: auto;
}
body.close .content {
left: 0;
}
@media print {
.github-corner,
.sidebar-toggle,
.sidebar,
.app-nav {
display: none;
}
}
@media screen and (max-width: 768px) {
.github-corner,
.sidebar-toggle,
.sidebar {
position: fixed;
}
.app-nav {
margin-top: 16px;
}
.app-nav li ul {
top: 30px;
}
main {
height: auto;
overflow-x: hidden;
}
.sidebar {
left: -300px;
transition: transform 250ms ease-out;
}
.content {
left: 0;
max-width: 100vw;
position: static;
padding-top: 20px;
transition: transform 250ms ease;
}
.app-nav,
.github-corner {
transition: transform 250ms ease-out;
}
.sidebar-toggle {
background-color: transparent;
width: auto;
padding: 30px 30px 10px 10px;
}
body.close .sidebar {
transform: translateX(300px);
}
body.close .sidebar-toggle {
background-color: rgba(255,255,255,0.8);
transition: 1s background-color;
width: 284px;
padding: 10px;
}
body.close .content {
transform: translateX(300px);
}
body.close .app-nav,
body.close .github-corner {
display: none;
}
.github-corner:hover .octo-arm {
-webkit-animation: none;
animation: none;
}
.github-corner .octo-arm {
-webkit-animation: octocat-wave 560ms ease-in-out;
animation: octocat-wave 560ms ease-in-out;
}
}
@-webkit-keyframes octocat-wave {
0%, 100% {
transform: rotate(0);
}
20%, 60% {
transform: rotate(-25deg);
}
40%, 80% {
transform: rotate(10deg);
}
}
@keyframes octocat-wave {
0%, 100% {
transform: rotate(0);
}
20%, 60% {
transform: rotate(-25deg);
}
40%, 80% {
transform: rotate(10deg);
}
}
section.cover {
align-items: center;
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
height: 100vh;
display: none;
}
section.cover.show {
display: flex;
}
section.cover.has-mask .mask {
background-color: #fff;
opacity: 0.8;
position: absolute;
top: 0;
height: 100%;
width: 100%;
}
section.cover .cover-main {
flex: 1;
margin: -20px 16px 0;
text-align: center;
z-index: 1;
}
section.cover a {
color: inherit;
text-decoration: none;
}
section.cover a:hover {
text-decoration: none;
}
section.cover p {
line-height: 1.5rem;
margin: 1em 0;
}
section.cover h1 {
color: inherit;
font-size: 2.5rem;
font-weight: 300;
margin: 0.625rem 0 2.5rem;
position: relative;
text-align: center;
}
section.cover h1 a {
display: block;
}
section.cover h1 small {
bottom: -0.4375rem;
font-size: 1rem;
position: absolute;
}
section.cover blockquote {
font-size: 1.5rem;
text-align: center;
}
section.cover ul {
line-height: 1.8;
list-style-type: none;
margin: 1em auto;
max-width: 500px;
padding: 0;
}
section.cover .cover-main > p:last-child a {
border-color: var(--theme-color, #42b983);
border-radius: 2rem;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
color: var(--theme-color, #42b983);
display: inline-block;
font-size: 1.05rem;
letter-spacing: 0.1rem;
margin: 0.5rem 1rem;
padding: 0.75em 2rem;
text-decoration: none;
transition: all 0.15s ease;
}
section.cover .cover-main > p:last-child a:last-child {
background-color: var(--theme-color, #42b983);
color: #fff;
}
section.cover .cover-main > p:last-child a:last-child:hover {
color: inherit;
opacity: 0.8;
}
section.cover .cover-main > p:last-child a:hover {
color: inherit;
}
section.cover blockquote > p > a {
border-bottom: 2px solid var(--theme-color, #42b983);
transition: color 0.3s;
}
section.cover blockquote > p > a:hover {
color: var(--theme-color, #42b983);
}
body {
background-color: #fff;
}
/* sidebar */
.sidebar {
background-color: #fff;
color: #364149;
}
.sidebar li {
margin: 6px 0 6px 0;
}
.sidebar ul li a {
color: #505d6b;
font-size: 14px;
font-weight: normal;
overflow: hidden;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
}
.sidebar ul li a:hover {
text-decoration: underline;
}
.sidebar ul li ul {
padding: 0;
}
.sidebar ul li.active > a {
border-right: 2px solid;
color: var(--theme-color, #42b983);
font-weight: 600;
}
.app-sub-sidebar li::before {
content: '-';
padding-right: 4px;
float: left;
}
/* markdown content found on pages */
.markdown-section h1,
.markdown-section h2,
.markdown-section h3,
.markdown-section h4,
.markdown-section strong {
color: #2c3e50;
font-weight: 600;
}
.markdown-section a {
color: var(--theme-color, #42b983);
font-weight: 600;
}
.markdown-section h1 {
font-size: 2rem;
margin: 0 0 1rem;
}
.markdown-section h2 {
font-size: 1.75rem;
margin: 45px 0 0.8rem;
}
.markdown-section h3 {
font-size: 1.5rem;
margin: 40px 0 0.6rem;
}
.markdown-section h4 {
font-size: 1.25rem;
}
.markdown-section h5 {
font-size: 1rem;
}
.markdown-section h6 {
color: #777;
font-size: 1rem;
}
.markdown-section figure,
.markdown-section p {
margin: 1.2em 0;
}
.markdown-section p,
.markdown-section ul,
.markdown-section ol {
line-height: 1.6rem;
word-spacing: 0.05rem;
}
.markdown-section ul,
.markdown-section ol {
padding-left: 1.5rem;
}
.markdown-section blockquote {
border-left: 4px solid var(--theme-color, #42b983);
color: #858585;
margin: 2em 0;
padding-left: 20px;
}
.markdown-section blockquote p {
font-weight: 600;
margin-left: 0;
}
.markdown-section iframe {
margin: 1em 0;
}
.markdown-section em {
color: #7f8c8d;
}
.markdown-section code {
background-color: #f8f8f8;
border-radius: 2px;
color: #e96900;
font-family: 'Roboto Mono', Monaco, courier, monospace;
font-size: 0.8rem;
margin: 0 2px;
padding: 3px 5px;
white-space: pre-wrap;
}
.markdown-section pre {
-moz-osx-font-smoothing: initial;
-webkit-font-smoothing: initial;
background-color: #f8f8f8;
font-family: 'Roboto Mono', Monaco, courier, monospace;
line-height: 1.5rem;
margin: 1.2em 0;
overflow: auto;
padding: 0 1.4rem;
position: relative;
word-wrap: normal;
}
/* code highlight */
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #8e908c;
}
.token.namespace {
opacity: 0.7;
}
.token.boolean,
.token.number {
color: #c76b29;
}
.token.punctuation {
color: #525252;
}
.token.property {
color: #c08b30;
}
.token.tag {
color: #2973b7;
}
.token.string {
color: var(--theme-color, #42b983);
}
.token.selector {
color: #6679cc;
}
.token.attr-name {
color: #2973b7;
}
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #22a2c9;
}
.token.attr-value,
.token.control,
.token.directive,
.token.unit {
color: var(--theme-color, #42b983);
}
.token.keyword,
.token.function {
color: #e96900;
}
.token.statement,
.token.regex,
.token.atrule {
color: #22a2c9;
}
.token.placeholder,
.token.variable {
color: #3d8fd1;
}
.token.deleted {
text-decoration: line-through;
}
.token.inserted {
border-bottom: 1px dotted #202746;
text-decoration: none;
}
.token.italic {
font-style: italic;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.important {
color: #c94922;
}
.token.entity {
cursor: help;
}
.markdown-section pre > code {
-moz-osx-font-smoothing: initial;
-webkit-font-smoothing: initial;
background-color: #f8f8f8;
border-radius: 2px;
color: #525252;
display: block;
font-family: 'Roboto Mono', Monaco, courier, monospace;
font-size: 0.8rem;
line-height: inherit;
margin: 0 2px;
max-width: inherit;
overflow: inherit;
padding: 2.2em 5px;
white-space: inherit;
}
.markdown-section code::after,
.markdown-section code::before {
letter-spacing: 0.05rem;
}
code .token {
-moz-osx-font-smoothing: initial;
-webkit-font-smoothing: initial;
min-height: 1.5rem;
position: relative;
left: auto;
}
pre::after {
color: #ccc;
content: attr(data-lang);
font-size: 0.6rem;
font-weight: 600;
height: 15px;
line-height: 15px;
padding: 5px 10px 0;
position: absolute;
right: 0;
text-align: right;
top: 0;
}

View file

@ -31,14 +31,29 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import de.kosit.validationtool.impl.Helper;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.TestObjectFactory;
import de.kosit.validationtool.impl.input.SourceInput;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import net.sf.saxon.dom.NodeOverNodeInfo;
import net.sf.saxon.s9api.BuildingContentHandler;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmNode;
/**
* Testet den Hashcode-Service.
@ -163,4 +178,23 @@ public class InputFactoryTest {
InputFactory.read(Simple.NOT_EXISTING);
}
@Test
public void testDomSource() throws SaxonApiException, SAXException, IOException {
final DocumentBuilder builder = TestObjectFactory.createProcessor().newDocumentBuilder();
final BuildingContentHandler handler = builder.newBuildingContentHandler();
handler.startDocument();
handler.startElement("http://some.ns", "mynode", "mynode", new AttributesImpl());
final Document dom = NodeOverNodeInfo.wrap(handler.getDocumentNode().getUnderlyingNode()).getOwnerDocument();
final Input domInput = InputFactory.read(new DOMSource(dom), "MD5", "id".getBytes());
assertThat(domInput).isNotNull();
final Source source = domInput.getSource();
assertThat(source).isNotNull();
final Result<XdmNode, XMLSyntaxError> parsed = Helper.parseDocument(domInput);
assertThat(parsed.isValid()).isTrue();
// read twice
assertThat(Helper.parseDocument(domInput).getObject()).isNotNull();
}
}

View file

@ -31,7 +31,7 @@ import org.junit.Test;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.cmd.assertions.Assertions;
import de.kosit.validationtool.impl.Helper;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.TestObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
@ -52,15 +52,15 @@ public class CheckAssertionActionTest {
@Before
public void setup() throws IOException {
commandLine = new CommandLine();
commandLine.activate();
this.commandLine = new CommandLine();
this.commandLine.activate();
}
@Test
public void testEmptyInput() {
CheckAssertionAction a = new CheckAssertionAction(new Assertions(), ObjectFactory.createProcessor());
final CheckAssertionAction a = new CheckAssertionAction(new Assertions(), TestObjectFactory.createProcessor());
a.check(new CheckAction.Bag(InputFactory.read(SAMPLE), new CreateReportInput()));
assertThat(commandLine.getErrorOutput()).contains("Can not find assertions for");
assertThat(this.commandLine.getErrorOutput()).contains("Can not find assertions for");
}
@Test
@ -69,9 +69,9 @@ public class CheckAssertionActionTest {
bag.setReport(Helper.load(SAMPLE_REPORT));
final Assertions assertions = Helper.load(SAMPLE_ASSERTIONS, Assertions.class);
CheckAssertionAction a = new CheckAssertionAction(assertions, ObjectFactory.createProcessor());
final CheckAssertionAction a = new CheckAssertionAction(assertions, TestObjectFactory.createProcessor());
a.check(bag);
assertThat(commandLine.getErrorOutput()).contains("Assertion mismatch");
assertThat(this.commandLine.getErrorOutput()).contains("Assertion mismatch");
}
}

View file

@ -51,6 +51,7 @@ public class CommandlineApplicationTest {
private final Path output = Paths.get("target/test-output");
@Before
public void setup() throws IOException {
this.commandLine = new CommandLine();
@ -105,12 +106,13 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), Paths.get(Simple.NOT_EXISTING).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).isNotEmpty();
assertThat(this.commandLine.getErrorOutput()).contains("Can not load schema from sources");
assertThat(this.commandLine.getErrorOutput()).contains("Can not resolve");
}
@Test
public void testNotExistingTestTarget() {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r", Paths.get(Simple.REPOSITORY).toString(),
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(),
Paths.get(Simple.NOT_EXISTING).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).isNotEmpty();
@ -119,7 +121,8 @@ public class CommandlineApplicationTest {
@Test
public void testValidMinimalConfiguration() {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r", Paths.get(Simple.REPOSITORY).toString(),
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(),
Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
@ -128,7 +131,7 @@ public class CommandlineApplicationTest {
@Test
public void testValidMultipleInput() {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-o", this.output.toString(), "-r",
Paths.get(Simple.REPOSITORY).toString(), Paths.get(Simple.SIMPLE_VALID).toString(), Paths.get(Simple.FOO).toString() };
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString(), Paths.get(Simple.FOO).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains("Processing 2 object(s) completed");
}
@ -136,7 +139,7 @@ public class CommandlineApplicationTest {
@Test
public void testValidDirectoryInput() {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-o", this.output.toString(), "-r",
Paths.get(Simple.REPOSITORY).toString(), Paths.get(Simple.EXAMPLES).toString() };
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.EXAMPLES).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains("Processing 6 object(s) completed");
}
@ -145,7 +148,7 @@ public class CommandlineApplicationTest {
public void testValidOutputConfiguration() throws IOException {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-o", this.output.toString(), "-r",
Paths.get(Simple.REPOSITORY).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.output).exists();
@ -155,7 +158,8 @@ public class CommandlineApplicationTest {
@Test
public void testNoInput() {
// assertThat(output).doesNotExist();
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r", Paths.get(Simple.REPOSITORY).toString(), };
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), };
CommandLineApplication.mainProgram(args);
checkForHelp(this.commandLine.getOutputLines());
}
@ -164,7 +168,7 @@ public class CommandlineApplicationTest {
public void testPrint() {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-p", "-r",
Paths.get(Simple.REPOSITORY).toString(), "-o", this.output.toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
Paths.get(Simple.REPOSITORY_URI).toString(), "-o", this.output.toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.commandLine.getOutputLines().get(0)).contains("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
@ -174,7 +178,7 @@ public class CommandlineApplicationTest {
public void testHtmlExtraktion() throws IOException {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-h", "-o",
this.output.toAbsolutePath().toString(),
"-r", Paths.get(Simple.REPOSITORY).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
"-r", Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(Files.list(this.output).filter(f -> f.toString().endsWith(".html")).count()).isGreaterThan(0);
@ -183,8 +187,8 @@ public class CommandlineApplicationTest {
@Test
public void testAssertionsExtraktion() {
final String[] args = new String[] { "-d", "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY).toString(), "-o", this.output.toString(), "-c", Paths.get(ASSERTIONS).toString(),
Paths.get(Simple.REPOSITORY).toString(),
Paths.get(Simple.REPOSITORY_URI).toString(), "-o", this.output.toString(), "-c", Paths.get(ASSERTIONS).toString(),
Paths.get(Simple.REPOSITORY_URI).toString(),
Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
@ -200,4 +204,12 @@ public class CommandlineApplicationTest {
assertThat(this.commandLine.getErrorOutput()).contains("at de.kosit.validationtool");
}
@Test
public void testPrintMemoryStats() {
final String[] args = new String[] { "-m", "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.commandLine.getErrorOutput()).contains("total");
}
}

View file

@ -34,6 +34,7 @@ import org.junit.Test;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.impl.Helper;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.TestObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
/**
@ -51,7 +52,7 @@ public class ExtractHtmlActionTest {
@Before
public void setup() throws IOException {
this.tmpDirectory = Files.createTempDirectory("checktool");
this.action = new ExtractHtmlContentAction(Helper.loadTestRepository(), this.tmpDirectory);
this.action = new ExtractHtmlContentAction(TestObjectFactory.createProcessor(), this.tmpDirectory);
}
@After

View file

@ -30,6 +30,7 @@ import org.junit.Test;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.impl.Helper;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.TestObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
/**
@ -46,7 +47,7 @@ public class PrintReportActionTest {
public void setup() {
this.commandLine = new CommandLine();
this.commandLine.activate();
this.action = new PrintReportAction();
this.action = new PrintReportAction(TestObjectFactory.createProcessor());
}
@After

View file

@ -34,6 +34,7 @@ import org.junit.Test;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.impl.Helper;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.TestObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
/**
@ -49,7 +50,7 @@ public class SerializeReportActionTest {
@Before
public void setup() throws IOException {
this.tmpDirectory = Files.createTempDirectory("checktool");
this.action = new SerializeReportAction(this.tmpDirectory);
this.action = new SerializeReportAction(this.tmpDirectory, TestObjectFactory.createProcessor());
}
@After

View file

@ -0,0 +1,88 @@
package de.kosit.validationtool.config;
import static de.kosit.validationtool.config.ConfigurationBuilder.report;
import static de.kosit.validationtool.config.ConfigurationBuilder.schematron;
import static de.kosit.validationtool.config.SimpleConfigTest.createSimpleConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import java.net.URI;
import java.time.LocalDate;
import java.util.Date;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
* Test {@link ConfigurationBuilder}.
*
* @author Andreas Penski
*/
public class ConfigurationBuilderTest {
public static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
@Rule
public ExpectedException exceptions = ExpectedException.none();
@Test
public void testNoConfiguration() {
this.exceptions.expect(IllegalStateException.class);
new ConfigurationBuilder().build();
}
@Test
public void testNoFallback() {
this.exceptions.expect(IllegalStateException.class);
this.exceptions.expectMessage(Matchers.containsString("fallback"));
final ConfigurationBuilder builder = createSimpleConfiguration();
builder.with((FallbackBuilder) null);
builder.build();
}
@Test
public void testNoSchema() {
this.exceptions.expect(IllegalStateException.class);
this.exceptions.expectMessage(Matchers.containsString("schema"));
final ConfigurationBuilder builder = createSimpleConfiguration();
builder.getScenarios().get(0).validate((SchemaBuilder) null);
builder.build();
}
@Test
public void testInvalidSchematron() {
this.exceptions.expect(IllegalStateException.class);
this.exceptions.expectMessage(Matchers.containsString("schematron"));
final ConfigurationBuilder builder = createSimpleConfiguration();
builder.getScenarios().get(0).validate(schematron("invalid").source(URI.create("DoesNotExist")));
builder.build();
}
@Test
public void testInsufficientSchematron() {
this.exceptions.expect(IllegalStateException.class);
this.exceptions.expectMessage(Matchers.containsString("schematron"));
final ConfigurationBuilder builder = createSimpleConfiguration();
builder.getScenarios().get(0).validate(schematron("invalid"));
builder.build();
}
@Test
public void testNoReport() {
this.exceptions.expect(IllegalStateException.class);
this.exceptions.expectMessage(Matchers.containsString("report"));
final ConfigurationBuilder builder = createSimpleConfiguration();
builder.getScenarios().get(0).with(report("invalid"));
builder.build();
}
@Test
public void testDate() {
assertThat(createSimpleConfiguration().date(EPOCH).build().getDate()).isEqualTo("1970-01-01");
assertThat(createSimpleConfiguration().date(new Date(EPOCH.toEpochDay())).build().getDate()).isEqualTo("1970-01-01");
assertThat(createSimpleConfiguration().date((Date) null).build().getDate()).isEqualTo(LocalDate.now().toString());
assertThat(createSimpleConfiguration().date((LocalDate) null).build().getDate()).isEqualTo(LocalDate.now().toString());
}
}

View file

@ -0,0 +1,153 @@
package de.kosit.validationtool.config;
import static de.kosit.validationtool.config.TestScenarioFactory.createScenario;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.Scenario;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.scenarios.NamespaceType;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.XPathExecutable;
/**
* Test {@link ScenarioBuilder}.
*
* @author Andreas Penski
*/
public class ScenarioBuilderTest {
@Rule
public ExpectedException exceptions = ExpectedException.none();
@Test
public void simpleValid() {
final Result<Scenario, String> result = createScenario().build(Simple.createContentRepository());
assertThat(result.isValid()).isTrue();
assertThat(result.getObject().getConfiguration()).isNotNull();
}
@Test
public void testNoSchema() {
final ScenarioBuilder builder = createScenario();
builder.validate((SchemaBuilder) null);
final Result<Scenario, String> result = builder.build(Simple.createContentRepository());
assertThat(result.isValid()).isFalse();
assertThat(result.getErrors()).anyMatch(e -> e.contains("schema"));
}
@Test
public void testNoMatch() {
final ScenarioBuilder builder = createScenario();
builder.match((String) null);
final Result<Scenario, String> result = builder.build(Simple.createContentRepository());
assertThat(result.isValid()).isFalse();
assertThat(result.getErrors()).anyMatch(e -> e.contains("match"));
}
@Test
public void testInvalidMatch() {
final ScenarioBuilder builder = createScenario();
builder.match("/////");
final Result<Scenario, String> result = builder.build(Simple.createContentRepository());
assertThat(result.isValid()).isFalse();
assertThat(result.getErrors()).anyMatch(e -> e.contains("match"));
}
@Test
public void testNoAccept() {
final ScenarioBuilder builder = createScenario();
builder.acceptWith((String) null);
final Result<Scenario, String> result = builder.build(Simple.createContentRepository());
assertThat(result.isValid()).isTrue();
}
@Test
public void testInvalidAccept() {
final ScenarioBuilder builder = createScenario();
builder.acceptWith("/////");
final Result<Scenario, String> result = builder.build(Simple.createContentRepository());
assertThat(result.isValid()).isFalse();
assertThat(result.getErrors()).anyMatch(e -> e.contains("accept"));
}
@Test
public void testCombinedNamespaces() {
final ContentRepository repository = Simple.createContentRepository();
final Map<String, String> ns1 = new HashMap<>();
ns1.put("n1", "http://n1.org");
final XPathExecutable match = repository.createXPath("//n1:*", ns1);
final Map<String, String> ns2 = new HashMap<>();
ns2.put("n2", "http://n2.org");
final XPathExecutable accept = repository.createXPath("//n2:*", ns2);
final ScenarioBuilder builder = createScenario();
builder.getNamespaces().clear();
builder.match(match).acceptWith(accept).declareNamespace("n3", "http://n3.org");
final Result<Scenario, String> result = builder.build(repository);
assertThat(result.isValid()).isTrue();
final Scenario scenario = result.getObject();
final List<NamespaceType> namespaces = scenario.getConfiguration().getNamespace();
assertThat(namespaces.stream().map(NamespaceType::getPrefix)).containsExactly("n1", "n2", "n3");
assertThat(namespaces).hasSize(3);
}
@Test
public void testConfigureWithExecutable() {
final ContentRepository repository = Simple.createContentRepository();
final XPathExecutable match = repository.createXPath("//*", null);
final XPathExecutable accept = repository.createXPath("//*", null);
final ScenarioBuilder builder = createScenario();
builder.getNamespaces().clear();
builder.match(match);
builder.acceptWith(accept);
final Result<Scenario, String> result = builder.build(repository);
assertThat(result.isValid()).isTrue();
final ScenarioType configuration = result.getObject().getConfiguration();
assertThat(configuration.getMatch()).isNotEmpty();
assertThat(configuration.getAcceptMatch()).isNotEmpty();
assertThat(configuration.getNamespace()).isEmpty();
}
@Test
public void testBasicAttributes() {
final ContentRepository repository = Simple.createContentRepository();
final String random = RandomStringUtils.random(5);
final ScenarioBuilder builder = createScenario();
builder.name(random).description(random);
final Result<Scenario, String> result = builder.build(repository);
assertThat(result.isValid()).isTrue();
final ScenarioType config = result.getObject().getConfiguration();
assertThat(config.getName()).isEqualTo(random);
assertThat(config.getDescription()).isNotNull();
assertThat(config.getDescription().getPOrOlOrUl()).isNotEmpty();
}
@Test
public void testNoBasicAttributes() {
final ContentRepository repository = Simple.createContentRepository();
final ScenarioBuilder builder = createScenario();
builder.name(null);
final Result<Scenario, String> result = builder.build(repository);
assertThat(result.isValid()).isTrue();
final ScenarioType config = result.getObject().getConfiguration();
assertThat(config.getName()).contains("manually");
assertThat(config.getDescription()).isNotNull();
assertThat(config.getDescription().getPOrOlOrUl()).isNotEmpty();
}
}

View file

@ -0,0 +1,93 @@
package de.kosit.validationtool.config;
import static de.kosit.validationtool.config.ConfigurationBuilder.schema;
import static org.assertj.core.api.Assertions.assertThat;
import java.nio.file.Paths;
import javax.xml.validation.Schema;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Test;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.model.scenarios.ResourceType;
import de.kosit.validationtool.model.scenarios.ValidateWithXmlSchema;
/**
* Tests {@link SchemaBuilder}.
*
* @author Andreas Penski
*/
public class SchemaBuilderTest {
@Test
public void testBuildSchema() {
final SchemaBuilder builder = schema(Simple.SCHEMA);
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
}
@Test
public void testNoConfiguration() {
final SchemaBuilder builder = schema("no-config");
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isFalse();
}
@Test
public void testBuildNamedSchema() {
final SchemaBuilder builder = schema("myname").schemaLocation(Simple.SCHEMA);
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
assertThat(result.getObject().getKey().getResource().stream().map(ResourceType::getName).findFirst().get()).isEqualTo("myname");
}
@Test
public void testInvalidSchema() {
final SchemaBuilder builder = schema("myname").schemaLocation(Simple.INVALID);
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isFalse();
}
@Test
public void testNonExisting() {
final SchemaBuilder builder = schema("myname").schemaLocation(Simple.REPOSITORY_URI.resolve("doesNotExist.xsd"));
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isFalse();
}
@Test
public void testPath() {
final SchemaBuilder builder = schema("myname").schemaLocation(Paths.get(Simple.SCHEMA));
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
}
@Test
public void testStringLocation() {
final SchemaBuilder builder = schema("myname").schemaLocation("simple.xsd");
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
}
@Test
public void testPrecompiled() {
final ContentRepository repository = Simple.createContentRepository();
final Schema schema = repository.createSchema(Simple.SCHEMA);
final SchemaBuilder builder = schema("myname").schema(schema);
final Result<Pair<ValidateWithXmlSchema, Schema>, String> result = builder.build(repository);
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
}
}

View file

@ -0,0 +1,40 @@
package de.kosit.validationtool.config;
import static de.kosit.validationtool.config.ConfigurationBuilder.fallback;
import static de.kosit.validationtool.config.TestScenarioFactory.createScenario;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import de.kosit.validationtool.api.Configuration;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.api.Result;
import de.kosit.validationtool.impl.DefaultCheck;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.ResolvingMode;
/**
* @author Andreas Penski
*/
public class SimpleConfigTest {
@Test
public void testSimpleWithApi() {
//@formatter:off
final Configuration config = createSimpleConfiguration().build();
//@formatter:on
final DefaultCheck check = new DefaultCheck(config);
final Result result = check.checkInput(InputFactory.read(Simple.SIMPLE_VALID));
assertThat(result).isNotNull();
}
static ConfigurationBuilder createSimpleConfiguration() {
return Configuration.create().name("Simple-API").with(createScenario()
// .description("awesome api")
).with(fallback().name("default").source("report.xsl"))
.resolvingMode(ResolvingMode.STRICT_RELATIVE).useRepository(Simple.REPOSITORY_URI);
}
}

View file

@ -0,0 +1,31 @@
package de.kosit.validationtool.config;
import java.util.List;
import java.util.Map;
import lombok.Data;
import de.kosit.validationtool.api.Configuration;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.Scenario;
/**
* @author Andreas Penski
*/
@Data
public class TestConfiguration implements Configuration {
private List<Scenario> scenarios;
private Scenario fallbackScenario;
private String author;
private String name;
private String date;
private ContentRepository contentRepository;
private Map<String, Object> additionalParameters;
}

View file

@ -0,0 +1,21 @@
package de.kosit.validationtool.config;
import static de.kosit.validationtool.config.ConfigurationBuilder.report;
import static de.kosit.validationtool.config.ConfigurationBuilder.scenario;
import static de.kosit.validationtool.config.ConfigurationBuilder.schema;
import java.net.URI;
/**
* @author Andreas Penski
*/
public class TestScenarioFactory {
public static ScenarioBuilder createScenario() {
return scenario("simple").validate(schema("Sample Schema").schemaLocation(URI.create("simple.xsd")))
.with(report("Report für eRechnung").source("report.xsl")).acceptWith("count(//test:rejected) = 0")
.declareNamespace("cri", "http://www.xoev.de/de/validator/framework/1/createreportinput")
.declareNamespace("rpt", "http://validator.kosit.de/test-report")
.declareNamespace("test", "http://validator.kosit.de/test-sample").match("/test:simple");
}
}

View file

@ -0,0 +1,116 @@
package de.kosit.validationtool.config;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Test;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.model.Result;
import net.sf.saxon.s9api.XPathExecutable;
/**
* Tests {@link XPathBuilder}.
*
* @author Andreas Penski
*/
public class XPathBuilderTest {
@Test
public void testSimpleString() {
final String name = RandomStringUtils.randomAlphanumeric(5);
final XPathBuilder b = new XPathBuilder(name);
b.setXpath("//*");
final Result<XPathExecutable, String> result = b.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
assertThat(b.getNamespaces()).isNotNull();
assertThat(b.getNamespaces()).isEmpty();
assertThat(b.getXPath()).isNotEmpty();
assertThat(b.getName()).isNotEmpty();
}
@Test
public void testStringWithNamespace() {
final String name = RandomStringUtils.randomAlphanumeric(5);
final XPathBuilder b = new XPathBuilder(name);
final Map<String, String> ns = new HashMap<>();
ns.put("p", "http://somens");
b.setNamespaces(ns);
b.setXpath("//p:*");
final Result<XPathExecutable, String> result = b.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
assertThat(b.getNamespaces()).isNotEmpty();
assertThat(b.getXPath()).isNotEmpty();
}
@Test
public void testStringWithUnknownNamespace() {
final String name = RandomStringUtils.randomAlphanumeric(5);
final XPathBuilder b = new XPathBuilder(name);
final Map<String, String> ns = new HashMap<>();
ns.put("p", "http://somens");
b.setNamespaces(ns);
b.setXpath("//u:*");
final Result<XPathExecutable, String> result = b.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isFalse();
}
@Test
public void testExecutable() {
final String name = RandomStringUtils.randomAlphanumeric(5);
final ContentRepository repository = Simple.createContentRepository();
final XPathExecutable xpath = repository.createXPath("//*", Collections.emptyMap());
final XPathBuilder b = new XPathBuilder(name);
b.setExecutable(xpath);
final Result<XPathExecutable, String> result = b.build(repository);
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
assertThat(b.getNamespaces()).isEmpty();
assertThat(b.getXPath()).isNotEmpty();
}
@Test
public void testExecutableWithNamespace() {
final String name = RandomStringUtils.randomAlphanumeric(5);
final ContentRepository repository = Simple.createContentRepository();
final Map<String, String> ns = new HashMap<>();
ns.put("p", "http://somens");
final XPathExecutable xpath = repository.createXPath("//p:*", ns);
final XPathBuilder b = new XPathBuilder(name);
b.setExecutable(xpath);
final Result<XPathExecutable, String> result = b.build(repository);
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
assertThat(b.getNamespaces()).isNotEmpty();
assertThat(b.getNamespaces()).containsKey("p");
assertThat(b.getXPath()).isNotEmpty();
}
@Test
public void testNoName() {
final XPathBuilder b = new XPathBuilder(null);
b.setXpath("//*");
final Result<XPathExecutable, String> result = b.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isTrue();
assertThat(b.getName()).isNull();
}
@Test
public void testNoConfig() {
final String name = RandomStringUtils.randomAlphanumeric(5);
final XPathBuilder b = new XPathBuilder(name);
final Result<XPathExecutable, String> result = b.build(Simple.createContentRepository());
assertThat(result).isNotNull();
assertThat(result.isValid()).isFalse();
}
}

View file

@ -0,0 +1,26 @@
package de.kosit.validationtool.daemon;
import org.junit.Before;
import io.restassured.RestAssured;
/**
* Base for integration tests.
*
* @author Andreas Penski
*/
public abstract class BaseIT {
@Before
public void setup() {
final String port = System.getProperty("daemon.port");
if (port != null) {
RestAssured.port = Integer.valueOf(port);
}
final String baseHost = System.getProperty("daemon.host");
if (baseHost != null) {
RestAssured.baseURI = baseHost;
}
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
}

View file

@ -1,4 +1,4 @@
package de.kosit.validationtool.cmd;
package de.kosit.validationtool.daemon;
import static io.restassured.RestAssured.given;
@ -6,39 +6,23 @@ import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import de.kosit.validationtool.impl.Helper.Simple;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
/**
* Testet the Daemon-Mode input , Methoden , Output Content-Type and the success case
*
* @author Roula Antoun
* @author Andreas Penski
*/
public class DaemonIT {
public class CheckHandlerIT extends BaseIT {
private static final String APPLICATION_XML = "application/xml";
@Before
public void setup() {
final String port = System.getProperty("daemon.port");
if (port != null) {
RestAssured.port = Integer.valueOf(port);
}
final String baseHost = System.getProperty("daemon.host");
if (baseHost != null) {
RestAssured.baseURI = baseHost;
}
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
@Test
public void makeSureThatSuccessTest() throws IOException {
try ( final InputStream io = Simple.SIMPLE_VALID.toURL().openStream() ) {
@ -63,15 +47,7 @@ public class DaemonIT {
return IOUtils.toByteArray(io);
}
@Test
public void methodNotAllowedTest() {
given().when().get("/").then().statusCode(405);
given().when().put("/").then().statusCode(405);
given().when().patch("/").then().statusCode(405);
given().when().delete("/").then().statusCode(405);
given().when().head("/").then().statusCode(405);
given().when().options("/").then().statusCode(405);
}
@Test
public void xmlResultTest() throws IOException {

View file

@ -0,0 +1,20 @@
package de.kosit.validationtool.daemon;
import static io.restassured.RestAssured.given;
import org.junit.Test;
import io.restassured.http.ContentType;
/**
* Integration test for the {@link ConfigHandler}.
*
* @author Andreas Penski
*/
public class ConfigHandlerIT extends BaseIT {
@Test
public void checkHealth() {
given().when().get("/server/config").then().statusCode(200).and().contentType(ContentType.XML);
}
}

View file

@ -0,0 +1,16 @@
package de.kosit.validationtool.daemon;
import io.restassured.http.ContentType;
import org.junit.Test;
import static io.restassured.RestAssured.given;
public class GuiHandlerIT extends BaseIT {
@Test
public void checkGui() {
given().when().get("/").then().statusCode(200).and().contentType(ContentType.HTML);
given().when().get("/README.md").then().statusCode(200).and().contentType("text/markdown");
given().when().get("/unknown.md").then().statusCode(404).and().contentType(ContentType.TEXT);
}
}

View file

@ -0,0 +1,20 @@
package de.kosit.validationtool.daemon;
import static io.restassured.RestAssured.given;
import org.junit.Test;
import io.restassured.http.ContentType;
/**
* Checks the health endpoint.
*
* @author Andreas Penski
*/
public class HealthHandlerIT extends BaseIT {
@Test
public void checkHealth() {
given().when().get("/server/health").then().statusCode(200).and().contentType(ContentType.XML);
}
}

View file

@ -42,7 +42,7 @@ import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XsltExecutable;
/**
* Testet das ContentRepository.
* Testet das repository.
*
* @author Andreas Penski
*/
@ -55,12 +55,12 @@ public class ContentRepositoryTest {
@Before
public void setup() {
this.repository = new ContentRepository(ObjectFactory.createProcessor(), Simple.REPOSITORY);
this.repository = Simple.createContentRepository();
}
@Test
public void testCreateSchema() throws MalformedURLException {
final Schema schema = ContentRepository.createSchema(Helper.ASSERTION_SCHEMA.toURL());
final Schema schema = this.repository.createSchema(Helper.ASSERTION_SCHEMA.toURL());
assertThat(schema).isNotNull();
}
@ -73,7 +73,7 @@ public class ContentRepositoryTest {
@Test
public void testCreateSchemaNotExisting() throws Exception {
this.exception.expect(IllegalStateException.class);
ContentRepository.createSchema(Simple.NOT_EXISTING.toURL());
this.repository.createSchema(Simple.NOT_EXISTING.toURL());
}
@Test
@ -114,7 +114,8 @@ public class ContentRepositoryTest {
@Test
public void loadFromJar() throws URISyntaxException {
this.repository = new ContentRepository(ObjectFactory.createProcessor(), Helper.JAR_REPOSITORY.toURI());
assert Helper.JAR_REPOSITORY != null;
this.repository = new ContentRepository(ResolvingMode.STRICT_RELATIVE.getStrategy(), Helper.JAR_REPOSITORY.toURI());
final XsltExecutable xsltExecutable = this.repository.loadXsltScript(URI.create("resources/eRechnung/report.xsl"));
assertThat(xsltExecutable).isNotNull();
}
@ -122,16 +123,28 @@ public class ContentRepositoryTest {
@Test
public void testLoadSchema() {
final URL main = RelativeUriResolverTest.class.getClassLoader().getResource("loading/main.xsd");
final Schema schema = ContentRepository.createSchema(main, new ClassPathResourceResolver("/loading"));
assert main != null;
final Schema schema = this.repository.createSchema(main, new ClassPathResourceResolver("/loading"));
assertThat(schema).isNotNull();
}
@Test
public void testLoadSchemaPackaged() throws URISyntaxException {
final URL main = RelativeUriResolverTest.class.getClassLoader().getResource("packaged/main.xsd");
final Schema schema = ContentRepository.createSchema(main,
assert main != null;
final Schema schema = this.repository.createSchema(main,
new ClassPathResourceResolver(RelativeUriResolverTest.class.getClassLoader().getResource("packaged/").toURI()));
assertThat(schema).isNotNull();
}
// @Test
// public void loadFromJar() throws URISyntaxException {
// this.content = new ContentRepository(TestObjectFactory.createProcessor(), Helper.JAR_REPOSITORY.toURI());
// this.repository = new ScenarioRepository(this.content);
// final CheckConfiguration conf = new CheckConfiguration(
// ScenarioRepository.class.getClassLoader().getResource("xrechnung/scenarios.xml").toURI());
// ScenarioRepository.initialize(conf);
// assertThat(this.repository.getScenarios()).isNotNull();
// }
}

View file

@ -21,7 +21,6 @@ package de.kosit.validationtool.impl;
import static org.assertj.core.api.Java6Assertions.assertThat;
import java.io.File;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.net.URL;
@ -54,8 +53,7 @@ public class ConversionServiceTest {
@Before
public void setup() {
this.service = new ConversionService();
this.repository = new ContentRepository(ObjectFactory.createProcessor(),
new File("src/test/resources/examples/repository").toURI());
this.repository = Simple.createContentRepository();
}
@Test
@ -72,7 +70,7 @@ public class ConversionServiceTest {
}
@Test
public void testUnmarshal() throws URISyntaxException {
public void testUnmarshal() {
final Scenarios s = this.service.readXml(Simple.SCENARIOS, Scenarios.class);
assertThat(s).isNotNull();
assertThat(s.getName()).isEqualToIgnoringCase("HTML-TestSuite");
@ -80,7 +78,7 @@ public class ConversionServiceTest {
@Test
public void testUnmarshalWithSchema() {
final Scenarios s = this.service.readXml(Simple.SCENARIOS, Scenarios.class, ContentRepository.createSchema(SCHEMA));
final Scenarios s = this.service.readXml(Simple.SCENARIOS, Scenarios.class, this.repository.createSchema(SCHEMA));
assertThat(s).isNotNull();
assertThat(s.getName()).isEqualToIgnoringCase("HTML-TestSuite");
}
@ -88,13 +86,13 @@ public class ConversionServiceTest {
@Test
public void testUnmarshalInvalidXml() {
this.exception.expect(ConversionService.ConversionExeption.class);
this.service.readXml(Invalid.SCENARIOS, Scenarios.class, ContentRepository.createSchema(SCHEMA));
this.service.readXml(Invalid.SCENARIOS, Scenarios.class, this.repository.createSchema(SCHEMA));
}
@Test
public void testUnmarshalIllFormed() {
this.exception.expect(ConversionService.ConversionExeption.class);
this.service.readXml(Invalid.SCENARIOS_ILLFORMED, Scenarios.class, ContentRepository.createSchema(SCHEMA));
this.service.readXml(Invalid.SCENARIOS_ILLFORMED, Scenarios.class, this.repository.createSchema(SCHEMA));
}
@Test

View file

@ -57,7 +57,7 @@ public class DefaultCheckTest {
@Before
public void setup() {
final CheckConfiguration d = new CheckConfiguration(Simple.SCENARIOS);
d.setScenarioRepository(new File(Simple.REPOSITORY).toURI());
d.setScenarioRepository(new File(Simple.REPOSITORY_URI).toURI());
this.implementation = new DefaultCheck(d);
}

View file

@ -19,7 +19,6 @@
package de.kosit.validationtool.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
@ -28,16 +27,17 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.DocumentParseAction;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
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.XdmNode;
/**
@ -47,6 +47,7 @@ import net.sf.saxon.s9api.XdmNode;
*/
public class Helper {
public static class Simple {
public static final URI ROOT = EXAMPLES_DIR.resolve("simple/");
@ -61,7 +62,7 @@ public class Helper {
public static final URI SCENARIOS = ROOT.resolve("scenarios.xml");
public static final URI REPOSITORY = ROOT.resolve("repository/");
public static final URI REPOSITORY_URI = ROOT.resolve("repository/");
public static final URI INVALID = ROOT.resolve("input/simple-invalid.xml");
@ -73,10 +74,17 @@ public class Helper {
public static final URI NOT_EXISTING = EXAMPLES_DIR.resolve("doesnotexist");
public static final URI REPORT_XSL = REPOSITORY.resolve("report.xsl");
public static final URI REPORT_XSL = REPOSITORY_URI.resolve("report.xsl");
public static final URI SCHEMA = REPOSITORY_URI.resolve("simple.xsd");
public static final ContentRepository createContentRepository() {
final ResolvingConfigurationStrategy strategy = ResolvingMode.STRICT_RELATIVE.getStrategy();
return new ContentRepository(strategy, Simple.REPOSITORY_URI);
}
public static URI getSchemaLocation() {
return ROOT.resolve("repository/simple.xsd");
return SCHEMA;
}
}
@ -89,27 +97,27 @@ public class Helper {
public static final URI SCENARIOS_ILLFORMED = ROOT.resolve("scenarios-illformed.xml");
}
public static class Resolving {
public static final URI ROOT = EXAMPLES_DIR.resolve("resolving/");
public static final URI SCHEMA_WITH_REMOTE_REFERENCE = ROOT.resolve("withRemote.xsd");
public static final URI SCHEMA_WITH_REFERENCE = ROOT.resolve("main.xsd");
}
public static final URI MODEL_ROOT = Paths.get("src/main/model").toUri();
public static final URI ASSERTION_SCHEMA = MODEL_ROOT.resolve("xsd/assertions.xsd");
public static final URI TEST_ROOT = Paths.get("src/test/resources").toUri();
public static final URI EXAMPLES_DIR = TEST_ROOT.resolve("examples/");
public static final URI ASSERTIONS = EXAMPLES_DIR.resolve("assertions/tests-xrechnung.xml");
public static final URL JAR_REPOSITORY = Helper.class.getClassLoader().getResource("xrechnung/repository/");
/**
* Lädt ein XML-Dokument von der gegebenen URL
*
@ -118,7 +126,7 @@ public class Helper {
*/
public static XdmNode load(final URL url) {
try ( final InputStream input = url.openStream() ) {
return ObjectFactory.createProcessor().newDocumentBuilder().build(new StreamSource(input));
return TestObjectFactory.createProcessor().newDocumentBuilder().build(new StreamSource(input));
} catch (final SaxonApiException | IOException e) {
throw new IllegalStateException("Fehler beim Laden der XML-Datei", e);
@ -134,27 +142,31 @@ public class Helper {
return c.readXml(url.toURI(), type);
}
/**
* Lädt das default test repository mit Artefacten für Unit-Tests
*
* @return ein {@link ContentRepository}
*/
public static ContentRepository loadTestRepository() {
return new ContentRepository(ObjectFactory.createProcessor(), new File("src/test/resources/examples/repository").toURI());
}
public static String serialize(final Document doc) {
public static String serialize(final XdmNode node) {
try ( final StringWriter writer = new StringWriter() ) {
final Transformer transformer = ObjectFactory.createTransformer(true);
transformer.transform(new DOMSource(doc), new StreamResult(writer));
final Processor processor = Helper.getTestProcessor();
final Serializer serializer = processor.newSerializer(writer);
serializer.serializeNode(node);
return writer.toString();
} catch (final IOException | TransformerException e) {
} catch (final SaxonApiException | IOException e) {
throw new IllegalStateException("Can not serialize document", e);
}
}
public static String serialize(final XdmNode node) {
return serialize((Document) NodeOverNodeInfo.wrap(node.getUnderlyingNode()));
public static Result<XdmNode, XMLSyntaxError> parseDocument(final Processor processor, final Input input) {
return new DocumentParseAction(processor).parseDocument(input);
}
public static Result<XdmNode, XMLSyntaxError> parseDocument(final Input input) {
return new DocumentParseAction(getTestProcessor()).parseDocument(input);
}
public static Processor getTestProcessor() {
// is always the same at the moment
return createProcessor();
}
public static Processor createProcessor() {
return ResolvingMode.STRICT_RELATIVE.getStrategy().getProcessor();
}
}

View file

@ -33,6 +33,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import de.kosit.validationtool.impl.xml.RelativeUriResolver;
/**
* Testet den Uri-Resolver der relative auflösen soll
*
@ -56,20 +58,20 @@ public class RelativeUriResolverTest {
private URIResolver resolver = new RelativeUriResolver(BASE);
@Test
public void testSucces() throws TransformerException {
public void testSuccess() throws TransformerException {
final Source resource = this.resolver.resolve("ubl-0001.xml", BASE.toASCIIString());
assertThat(resource).isNotNull();
}
@Test
public void testNotExisting() throws TransformerException {
this.exception.expect(IllegalStateException.class);
this.exception.expect(TransformerException.class);
this.resolver.resolve("ubl-0001", BASE.toASCIIString());
}
@Test
public void testOutOfPath() throws TransformerException {
this.exception.expect(IllegalStateException.class);
this.exception.expect(TransformerException.class);
this.resolver.resolve("../results/report.xml", BASE.toASCIIString());
}

View file

@ -24,20 +24,20 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.config.TestConfiguration;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.DocumentParseAction;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import de.kosit.validationtool.model.scenarios.Scenarios;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XdmNode;
/**
@ -51,72 +51,69 @@ public class ScenarioRepositoryTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
ContentRepository content;
private ScenarioRepository repository;
private TestConfiguration configInstance;
@Before
public void setup() {
this.content = new ContentRepository(ObjectFactory.createProcessor(), Simple.REPOSITORY);
final Scenarios def = new Scenarios();
final ScenarioType t = new ScenarioType();
t.setMatch("//*:name");
t.setName("Test");
t.initialize(this.content, true);
def.getScenario().add(t);
this.repository = new ScenarioRepository(this.content);
this.repository.initialize(def);
this.configInstance = new TestConfiguration();
this.configInstance.setContentRepository(new ContentRepository(ResolvingMode.STRICT_RELATIVE.getStrategy(), null));
final Scenario s = createScenario();
this.configInstance.setScenarios(new ArrayList<>());
this.configInstance.getScenarios().add(s);
this.repository = new ScenarioRepository(this.configInstance);
}
private Scenario createScenario() {
final Scenario s = new Scenario(new ScenarioType());
s.setMatchExecutable(createXpath("//*:name"));
return s;
}
@Test
public void testHappyCase() throws Exception {
final Result<ScenarioType, String> scenario = this.repository.selectScenario(load(Simple.SCENARIOS));
final Result<Scenario, String> scenario = this.repository.selectScenario(load(Simple.SCENARIOS));
assertThat(scenario).isNotNull();
assertThat(scenario.isValid()).isTrue();
}
@Test
public void testNonMatch() throws Exception {
this.repository.getScenarios().getScenario().clear();
final ScenarioType fallback = new ScenarioType();
fallback.setName("fallback");
this.repository.setFallbackScenario(fallback);
final Result<ScenarioType, String> scenario = this.repository.selectScenario(load(Simple.SCENARIOS));
this.configInstance.setScenarios(new ArrayList<>());
final Scenario fallback = createFallback();
this.configInstance.setFallbackScenario(fallback);
final Result<Scenario, String> scenario = this.repository.selectScenario(load(Simple.SCENARIOS));
assertThat(scenario).isNotNull();
assertThat(scenario.isValid()).isFalse();
assertThat(scenario.getObject().getName()).isEqualTo("fallback");
}
private static Scenario createFallback() {
final ScenarioType t = new ScenarioType();
t.setName("fallback");
final Scenario fallback = new Scenario(t);
fallback.setFallback(true);
return fallback;
}
@Test
public void testMultiMatch() throws Exception {
final ScenarioType t = new ScenarioType();
t.setMatch("//*:name");
t.setName("Test");
t.initialize(this.content, true);
this.repository.getScenarios().getScenario().add(t);
final ScenarioType fallback = new ScenarioType();
fallback.setName("fallback");
this.repository.setFallbackScenario(fallback);
final Result<ScenarioType, String> scenario = this.repository.selectScenario(load(Simple.SCENARIOS));
this.configInstance.getScenarios().add(createScenario());
this.configInstance.setFallbackScenario(createFallback());
final Result<Scenario, String> scenario = this.repository.selectScenario(load(Simple.SCENARIOS));
assertThat(scenario).isNotNull();
assertThat(scenario.isValid()).isFalse();
assertThat(scenario.getObject().getName()).isEqualTo("fallback");
}
private static XdmNode load(final URI uri) throws IOException {
final DocumentParseAction p = new DocumentParseAction();
return DocumentParseAction.parseDocument(read(uri.toURL())).getObject();
private XdmNode load(final URI uri) throws IOException {
return Helper.parseDocument(this.configInstance.getContentRepository().getProcessor(), read(uri.toURL())).getObject();
}
@Test
public void loadFromJar() throws URISyntaxException {
this.content = new ContentRepository(ObjectFactory.createProcessor(), Helper.JAR_REPOSITORY.toURI());
this.repository = new ScenarioRepository(this.content);
final CheckConfiguration conf = new CheckConfiguration(
ScenarioRepository.class.getClassLoader().getResource("xrechnung/scenarios.xml").toURI());
this.repository.initialize(conf);
assertThat(this.repository.getScenarios()).isNotNull();
private XPathExecutable createXpath(final String expression) {
return this.configInstance.getContentRepository().createXPath(expression, new HashMap<>());
}
}

Some files were not shown because too many files have changed in this diff Show more