Merge remote-tracking branch 'origin/branch-1.2.x'

# Conflicts:
#	CHANGELOG.md
#	src/main/java/de/kosit/validationtool/impl/model/BaseScenario.java
This commit is contained in:
Andreas Penski (init) 2020-03-23 11:14:29 +01:00
commit 6aab106075
11 changed files with 307 additions and 75 deletions

View file

@ -7,14 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## next version (unreleased)
# Added
### Added
- Support java.xml.transform.Source/java.xml.transform.StreamSource as Input
# Changed
### Changed
- Inputs are NOT read into memory (e.g. Byte-Array) prior processing within the validator. This reduces memory consumption.
## 1.2.0 (unreleased)
### Added
- Provide access to schematron result through Result.java
- *Result#getFailedAsserts()* returns a list of failed asserts found by schematron
- *Result#isSchematronValid()* convinience access to evaluate whether schematron was processed without any *FailedAsserts*
### Changed
- *getAcceptRecommendation()* does not _only_ work when _acceptMatch_ is configured in the scenario
- schema correct is a precondion, of the checked instance is not valid, this evaluates to _REJECTED_
- if _acceptMatch_ is configured, the result is based on the boolean result of the xpath expression evaluated against the generated report
- if *no* _acceptMatch_ is configured, the result is based on evaluation of schema and schematron correctness
- _UNDEFINED_ is only returned, when processing is stopped somehow
- *isAcceptable()* can no evaluate to true, when no _acceptMatch_ is configured (see above)
## 1.1.3
### Fixed

View file

@ -89,3 +89,21 @@ Initializing all XML artifacts and XSLT-executables is expensive. The `Check` in
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`.
## Accept Recommendation and Accept Match
A tri-state Object `AcceptRecommendation` can be retrieved from the `Result` using `getAcceptRecommendation()`.
The three defined states are:
1. `UNDEFINED` i.e. the evaluation of the overall validation could not be computed.
2. `ACCEPTABLE` i.e. the recommendation is to accept input based on the evaluation of the overall validation.
3. `REJECT` i.e. the recommendation is to reject input based on the evaluation of the overall validation.
By default it is `UNDEFINED`.
### 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` amd otherwise to a `REJECT` recommendation.
This allows to have own control over what validation result is to be considered acceptable for your own application context.

View file

@ -13,9 +13,9 @@ the validation and generates a report in XML format. This report is then the inp
The validator reports valid/invalid, a configuration reports acceptance/rejection!
## General process
## General default process
The general process is like this:
The general process is like this (the default is defined in `DefaultCheck`):
```mermaid
@ -30,6 +30,7 @@ sequenceDiagram
e->>e: validate Schematron
e->>e: create Validator Report
e->>+c: execute configuration report generator
e->>e: Compute Recommendation
```
@ -50,3 +51,8 @@ sequenceDiagram
6. *execute configuration report generator*
The Validator will search for the XSLT as configured in scenario.xml and execute it with the Validator Report as input
7. compute Recommendation
In case a scenario contains an `acceptMatch` element with an XPATH expression, this expression will be executed.
In case the XPATH returns `true`, the recommendation will be set to `ACCEPT` else to `REJECT`. In case no such XPATH is defined it is `UNDEFINED`.

View file

@ -1,21 +1,21 @@
package de.kosit.validationtool.api;
/**
* Status der Empfehlung.
* Tri-state describtion of a Recommendation.
*/
public enum AcceptRecommendation {
/**
* Nicht definiert, weil eine Evaluierung nicht durchgeführt wurde, oder nicht durchgeführt werden konnte.
* The evaluation of the overall validation could not be computed.
*/
UNDEFINED,
/**
* Das Dokument ist gemäß Konfiguration valide und kann akzeptiert werden.
* Recommendation is to accept input based on the evaluation of the overall validation.
*/
ACCEPTABLE,
/**
* Das Dokuemnt ist gemäß Konfiguration invalide und sollte NICHT akzeptiert werden.
* Recommendation is to reject input based on the evaluation of the overall validation.
*/
REJECT
}

View file

@ -2,6 +2,7 @@ package de.kosit.validationtool.api;
import java.util.List;
import org.oclc.purl.dsdl.svrl.FailedAssert;
import org.oclc.purl.dsdl.svrl.SchematronOutput;
import org.w3c.dom.Document;
@ -36,7 +37,9 @@ public interface Result {
XdmNode getReport();
/**
* Das evaluierte Ergebnis.
* The Recommendation based on the evaluation of this Result.
*
* @return AcceptRecommendation
*/
AcceptRecommendation getAcceptRecommendation();
@ -67,6 +70,13 @@ public interface Result {
*/
List<SchematronOutput> getSchematronResult();
/**
* 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();
/**
* Liefert ein true, wenn keine Schema-Violations vorhanden sind.
*
@ -80,4 +90,11 @@ public interface Result {
* @return true wenn well-formed
*/
boolean isWellformed();
/**
* 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

@ -127,12 +127,19 @@ public class DefaultResult implements Result {
*
* @return die {@link FailedAssert}
*/
@Override
public List<FailedAssert> getFailedAsserts() {
return filterSchematronResult(FailedAssert.class);
}
private <T> List<T> filterSchematronResult(final Class<T> type) {
return getSchematronResult().stream().filter(type::isInstance).map(type::cast).collect(Collectors.toList());
return getSchematronResult() != null
? getSchematronResult().stream().filter(type::isInstance).map(type::cast).collect(Collectors.toList())
: Collections.emptyList();
}
@Override
public boolean isSchematronValid() {
return getSchematronResult() != null && getFailedAsserts().isEmpty();
}
}

View file

@ -2,15 +2,19 @@ package de.kosit.validationtool.impl.tasks;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import org.oclc.purl.dsdl.svrl.FailedAssert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.AcceptRecommendation;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathSelector;
/**
* Berechnet die Akzeptanz-Empfehlung gemäß konfigurierten 'acceptMatch' des aktuellen Szenarios.
* Computes a {@link AcceptRecommendation} for this instance. This is either based on an 'acceptMatch'-configuration of
* the active scenario or based on overall evaluation about schema and semantic (schematron) correctness of the
*
* @author Andreas Penski
*/
@ -20,23 +24,49 @@ public class ComputeAcceptanceAction implements CheckAction {
@Override
public void check(final Bag results) {
if (preCondtionsMatch(results)) {
final String acceptMatch = results.getScenarioSelectionResult().getObject().getAcceptMatch();
if (isNotBlank(acceptMatch)) {
if (results.getSchemaValidationResult().isValid() && isNotBlank(acceptMatch)) {
evaluateAcceptanceMatch(results);
} else {
evaluateSchemaAndSchematron(results);
}
} else {
results.setAcceptStatus(AcceptRecommendation.REJECT);
}
}
private void evaluateSchemaAndSchematron(final Bag results) {
if (results.getSchemaValidationResult().isValid() && isSchematronValid(results)) {
results.setAcceptStatus(AcceptRecommendation.ACCEPTABLE);
} else {
results.setAcceptStatus(AcceptRecommendation.REJECT);
}
}
private boolean isSchematronValid(final Bag results) {
return !hasSchematronErrors(results);
}
private boolean hasSchematronErrors(final Bag results) {
return results.getReportInput().getValidationResultsSchematron().stream().map(e -> e.getResults().getSchematronOutput())
.flatMap(e -> e.getActivePatternAndFiredRuleAndFailedAssert().stream()).anyMatch(FailedAssert.class::isInstance);
}
private static void evaluateAcceptanceMatch(final Bag results) {
try {
final XPathSelector selector = results.getScenarioSelectionResult().getObject().getAcceptSelector();
selector.setContextItem(results.getReport());
results.setAcceptStatus(selector.effectiveBooleanValue() ? AcceptRecommendation.ACCEPTABLE : AcceptRecommendation.REJECT);
} catch (final Exception e) {
log.error("Fehler bei Evaluierung des Accept-Status: {}", e.getMessage(), e);
}
} catch (final SaxonApiException e) {
final String msg = "Error evaluating accept recommendation: %s";
log.error(msg);
results.addProcessingError(msg);
}
}
@Override
public boolean isSkipped(final Bag results) {
return results.getReport() == null;
private static boolean preCondtionsMatch(final Bag results) {
return results.getReport() != null && results.getSchemaValidationResult() != null && results.getScenarioSelectionResult() != null;
}
}

View file

@ -19,9 +19,7 @@
-->
<!-- $Id$ -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.xoev.de/de/validator/framework/1/scenarios"
targetNamespace="http://www.xoev.de/de/validator/framework/1/scenarios" version="1.1.0" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.xoev.de/de/validator/framework/1/scenarios" targetNamespace="http://www.xoev.de/de/validator/framework/1/scenarios" version="1.1.0" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="scenarios">
<xs:complexType>
@ -88,6 +86,7 @@
<xs:element name="validateWithSchematron" maxOccurs="unbounded" minOccurs="0" type="s:ValidateWithSchematron" />
<xs:element name="createReport" type="s:CreateReportType" />
<!-- Optional da nachträglich eingeführt -->
<!-- An XPATH expression to be applied to a generated validation report as defined in the createReport element -->
<xs:element name="acceptMatch" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>

View file

@ -74,8 +74,9 @@ public class DefaultCheckTest {
final Result doc = this.implementation.checkInput(read(Simple.FOO));
assertThat(doc).isNotNull();
assertThat(doc.getReport()).isNotNull();
// happy case has schematron errors !??
assertThat(doc.isAcceptable()).isFalse();
assertThat(doc.getAcceptRecommendation()).isEqualTo(AcceptRecommendation.UNDEFINED);
assertThat(doc.getAcceptRecommendation()).isEqualTo(AcceptRecommendation.REJECT);
}
@Test

View file

@ -19,7 +19,7 @@ import de.kosit.validationtool.impl.Helper.Simple;
*
* @author Andreas Penski
*/
public class SimpleScenarioCheck {
public class SimpleScenarioCheckTest {
private DefaultCheck implementation;
@ -56,7 +56,7 @@ public class SimpleScenarioCheck {
public void testWithoutAcceptMatch() throws MalformedURLException {
final Result result = this.implementation.checkInput(InputFactory.read(Simple.FOO.toURL()));
assertThat(result).isNotNull();
assertThat(result.getAcceptRecommendation()).isEqualTo(AcceptRecommendation.UNDEFINED);
assertThat(result.getAcceptRecommendation()).isEqualTo(AcceptRecommendation.ACCEPTABLE);
}
}

View file

@ -0,0 +1,138 @@
package de.kosit.validationtool.impl.tasks;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.oclc.purl.dsdl.svrl.FailedAssert;
import org.oclc.purl.dsdl.svrl.SchematronOutput;
import de.kosit.validationtool.api.AcceptRecommendation;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.Helper.Simple;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.model.Result;
import de.kosit.validationtool.impl.tasks.CheckAction.Bag;
import de.kosit.validationtool.model.reportInput.CreateReportInput;
import de.kosit.validationtool.model.reportInput.ValidationResultsSchematron;
import de.kosit.validationtool.model.reportInput.ValidationResultsSchematron.Results;
import de.kosit.validationtool.model.reportInput.XMLSyntaxError;
import de.kosit.validationtool.model.scenarios.ScenarioType;
import net.sf.saxon.s9api.XdmNode;
/**
* Tests the 'acceptMatch' functionality.
*
* @author Andreas Penski
*/
public class ComputeAcceptanceActionTest {
private final ComputeAcceptanceAction action = new ComputeAcceptanceAction();
@Test
public void simpleTest() {
final Bag bag = createBag(true, true);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.UNDEFINED);
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.ACCEPTABLE);
}
@Test
public void testSchemaFailed() {
final Bag bag = createBag(false, true);
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.REJECT);
}
@Test
public void testSchematronFailed() {
final Bag bag = createBag(true, false);
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.REJECT);
}
@Test
public void testValidAcceptMatch() {
final Bag bag = createBag(true, true);
bag.getScenarioSelectionResult().getObject().setAcceptMatch("count(//doesnotExist) = 0");
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.ACCEPTABLE);
}
@Test
public void testAcceptMatchNotSatisfied() {
final Bag bag = createBag(true, true);
bag.getScenarioSelectionResult().getObject().setAcceptMatch("count(//doesnotExist) = 1");
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.REJECT);
}
@Test
public void testValidAcceptMatchOnSchematronFailed() {
final Bag bag = createBag(true, false);
bag.getScenarioSelectionResult().getObject().setAcceptMatch("count(//doesnotExist) = 0");
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.ACCEPTABLE);
}
@Test
public void testValidAcceptMatchOnSchemaFailed() {
final Bag bag = createBag(false, true);
bag.getScenarioSelectionResult().getObject().setAcceptMatch("count(//doesnotExist) = 0");
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.REJECT);
}
@Test
public void testMissingSchemaCheck() {
final Bag bag = createBag(null, Collections.emptyList());
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.REJECT);
}
@Test
public void testMissingReport() {
final Bag bag = createBag(false, true);
bag.setReport(null);
this.action.check(bag);
assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.REJECT);
}
private static Bag createBag(final boolean schemaValid, final boolean schematronValid) {
final Result<Boolean, XMLSyntaxError> schemaResult = schemaValid ? new Result<>(true)
: new Result<>(Collections.singletonList(new XMLSyntaxError()));
final List<ValidationResultsSchematron> schematronResult = schematronValid ? Collections.emptyList() : createSchematronError();
return createBag(schemaResult, schematronResult);
}
private static List<ValidationResultsSchematron> createSchematronError() {
final ValidationResultsSchematron v = new ValidationResultsSchematron();
final SchematronOutput out = new SchematronOutput();
final FailedAssert f = new FailedAssert();
out.getActivePatternAndFiredRuleAndFailedAssert().add(f);
final Results r = new Results();
r.setSchematronOutput(out);
v.setResults(r);
return Collections.singletonList(v);
}
private static Bag createBag(final Result<Boolean, XMLSyntaxError> schemaResult,
final Collection<ValidationResultsSchematron> schematronResult) {
final ScenarioType t = new ScenarioType();
t.initialize(new ContentRepository(ObjectFactory.createProcessor(), Simple.REPOSITORY), true);
final CreateReportInput reportInput = new CreateReportInput();
reportInput.getValidationResultsSchematron().addAll(schematronResult);
final Bag b = new Bag(InputFactory.read("<someXml></someXml>".getBytes(), "someCheck"), reportInput);
final Result<XdmNode, XMLSyntaxError> parseREsult = DocumentParseAction.parseDocument(b.getInput());
b.setReport(parseREsult.getObject());
b.setParserResult(parseREsult);
b.setSchemaValidationResult(schemaResult);
b.setScenarioSelectionResult(new Result<>(t));
return b;
}
}