diff --git a/.idea/misc.xml b/.idea/misc.xml index aebea3c..f207c86 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ + + diff --git a/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java b/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java index 31f6aff..10b3d56 100644 --- a/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java +++ b/src/main/java/de/kosit/validationtool/api/CheckConfiguration.java @@ -21,18 +21,17 @@ 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.config.LoadConfiguration; +import de.kosit.validationtool.config.ConfigurationLoader; import de.kosit.validationtool.impl.ContentRepository; import de.kosit.validationtool.impl.Scenario; -import net.sf.saxon.s9api.Processor; - /** * Zentrale Konfigration einer Prüf-Instanz. * @@ -56,11 +55,13 @@ public class CheckConfiguration implements Configuration { */ private URI scenarioRepository; - private LoadConfiguration delegate; + private ConfigurationLoader loader; - private LoadConfiguration getDelegate() { + private Configuration delegate; + + private Configuration getDelegate() { if (this.delegate == null) { - this.delegate = Configuration.load(this.scenarioDefinition, this.scenarioRepository); + this.delegate = Configuration.load(this.scenarioDefinition, this.scenarioRepository).build(); } return this.delegate; } @@ -76,13 +77,13 @@ public class CheckConfiguration implements Configuration { } @Override - public void build() { - getDelegate().build(); + public String getDate() { + return getDelegate().getDate(); } @Override - public String getDate() { - return getDelegate().getDate(); + public Map getAdditionalParameters() { + return this.delegate.getAdditionalParameters(); } @Override @@ -95,10 +96,7 @@ public class CheckConfiguration implements Configuration { return getDelegate().getAuthor(); } - @Override - public Processor getProcessor() { - return getDelegate().getProcessor(); - } + @Override public ContentRepository getContentRepository() { diff --git a/src/main/java/de/kosit/validationtool/api/Configuration.java b/src/main/java/de/kosit/validationtool/api/Configuration.java index ac99275..dbbb53d 100644 --- a/src/main/java/de/kosit/validationtool/api/Configuration.java +++ b/src/main/java/de/kosit/validationtool/api/Configuration.java @@ -2,20 +2,19 @@ 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.LoadConfiguration; +import de.kosit.validationtool.config.ConfigurationLoader; import de.kosit.validationtool.impl.ContentRepository; import de.kosit.validationtool.impl.Scenario; -import net.sf.saxon.s9api.Processor; - /** - * Configuration of the actual {@link Check} instance. This is a contruct and can be used implemented by custom + * 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: * *
    - *
  1. {@link LoadConfiguration} implements loading {@link Check} configurations from a scenario.xml file
  2. + *
  3. {@link ConfigurationLoader} implements loading {@link Check} configurations from a scenario.xml file
  4. *
  5. Using a builder style api {@link de.kosit.validationtool.config.ConfigurationBuilder}to configure the * {@link Check}
  6. *
@@ -27,33 +26,77 @@ import net.sf.saxon.s9api.Processor; public interface Configuration { + /** + * Returns a list of configured scenarios. + * + * @return the list of scenarios + */ List getScenarios(); - static LoadConfiguration load(final URI scenarioDefinition) { + /** + * 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(); + + Map getAdditionalParameters(); + + /** + * The content repository including resolving strategies. + * + * @return the configured {@link ContentRepository} + */ + ContentRepository getContentRepository(); + + /** + * Loads an XML based scenario definition from the file specified via URI. + * + * @param scenarioDefinition the XML file with scenario definition + * @return the loaded configuration + */ + static ConfigurationLoader load(final URI scenarioDefinition) { return load(scenarioDefinition, null); } - static LoadConfiguration load(final URI scenarioDefinition, final URI repository) { - final LoadConfiguration config = new LoadConfiguration(scenarioDefinition, repository); - config.build(); - return config; + /** + * 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(); } - - Scenario getFallbackScenario(); - - void build(); - - String getAuthor(); - - String getName(); - - String getDate(); - - Processor getProcessor(); - - ContentRepository getContentRepository(); } diff --git a/src/main/java/de/kosit/validationtool/api/Input.java b/src/main/java/de/kosit/validationtool/api/Input.java index ca27ebf..83794b6 100644 --- a/src/main/java/de/kosit/validationtool/api/Input.java +++ b/src/main/java/de/kosit/validationtool/api/Input.java @@ -20,7 +20,6 @@ package de.kosit.validationtool.api; import java.io.IOException; -import java.io.InputStream; import javax.xml.transform.Source; @@ -54,10 +53,10 @@ public interface Input { String getDigestAlgorithm(); /** - * Opens a new {@link InputStream } for this input which carries the actual data + * Creates a new {@link Source } for this input which carries the actual data * - * @return an open {@link InputStream} - * @throws IOException on I/O while opening the stream + * @return an open {@link Source} + * @throws IOException on I/O while opening the source */ Source getSource() throws IOException; diff --git a/src/main/java/de/kosit/validationtool/api/InputFactory.java b/src/main/java/de/kosit/validationtool/api/InputFactory.java index 77d99d0..8cf380f 100644 --- a/src/main/java/de/kosit/validationtool/api/InputFactory.java +++ b/src/main/java/de/kosit/validationtool/api/InputFactory.java @@ -54,10 +54,6 @@ public class InputFactory { static final String DEFAULT_ALGORITH = "SHA-256"; - private static final int EOF = -1; - - private static final int DEFAULT_BUFFER_SIZE = 4096; - private static final String MESSAGE_OPEN_STREAM_ERROR = "Can not open stream from"; @Getter @@ -108,7 +104,6 @@ public class InputFactory { return read(file, DEFAULT_ALGORITH); } - /** * Liest einen Prüfling von der übergebenen URI. Es wird der Default-Prüfsummenalgorithmus zur Ermittlung der Prüfsumme * genutzt. diff --git a/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java b/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java new file mode 100644 index 0000000..b8ec0b6 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java @@ -0,0 +1,64 @@ +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 infrastructore components. The KoSIT Validator provides out + * of the box implementaions with various security levels. + * + * 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. + * + * @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(); + + /** + * Creates 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. + * + * @return a preconfigured {@link Processor} + */ + Processor createProcessor(); + + /** + * Creates a specific implementation for resolving referenced objects in XML files. The URIResolver, it is used for + * dereferencing an absolute URI (after resolution) to return a {@link javax.xml.transform.Source}. It can be + * used for resolving relative URIs against a base URI or restrict access to certain URIs. + *

+ * This URIResolver is used to dereference the URIs appearing in xsl:import, xsl:include, and + * xsl:import-schema declarations. + *

+ * + * @return a preconfigured {@link URIResolver} + */ + URIResolver createResolver(URI scenarioRepository); + + /** + * Creates a preconfigured {@link Validator } instance for a given schema for xml file validation. The implementation + * takes care about security and reference resolving strategies. + * + * @param schema the scheme to create a {@link Validator} for + * @return a preconfigured {@link Validator} + */ + Validator createValidator(Schema schema); + +} diff --git a/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java b/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java index 21527f9..917fe57 100644 --- a/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java +++ b/src/main/java/de/kosit/validationtool/cmd/CommandLineApplication.java @@ -42,12 +42,15 @@ import org.apache.commons.lang3.StringUtils; import lombok.extern.slf4j.Slf4j; -import de.kosit.validationtool.api.CheckConfiguration; +import de.kosit.validationtool.api.Configuration; import de.kosit.validationtool.api.Input; import de.kosit.validationtool.api.InputFactory; import de.kosit.validationtool.cmd.assertions.Assertions; +import de.kosit.validationtool.config.ConfigurationLoader; +import de.kosit.validationtool.daemon.Daemon; import de.kosit.validationtool.impl.ConversionService; -import de.kosit.validationtool.impl.ObjectFactory; + +import net.sf.saxon.s9api.Processor; /** * Commandline Version des Prüftools. Parsed die Kommandozeile und führt die konfigurierten Aktionen aus. @@ -169,9 +172,9 @@ 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)); + validDaemon.startServer(config.build()); return DAEMON_SIGNAL; } @@ -203,25 +206,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()); diff --git a/src/main/java/de/kosit/validationtool/cmd/Daemon.java b/src/main/java/de/kosit/validationtool/cmd/Daemon.java deleted file mode 100644 index 91aceda..0000000 --- a/src/main/java/de/kosit/validationtool/cmd/Daemon.java +++ /dev/null @@ -1,199 +0,0 @@ -package de.kosit.validationtool.cmd; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.URI; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicLong; - -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -import org.w3c.dom.Document; - -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -import de.kosit.validationtool.api.Check; -import de.kosit.validationtool.api.CheckConfiguration; -import de.kosit.validationtool.api.Configuration; -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; - -/** - * 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 Configuration scenarios; - - HealthHandler(final Configuration config) { - this.scenarios = config; - } - - @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(config)); - server.setExecutor(Executors.newFixedThreadPool(this.threadCount)); - server.start(); - log.info("Server unter Port {} ist erfolgreich gestartet", this.port); - } catch (final IOException e) { - log.error("Fehler beim HttpServer erstellen: {}", e.getMessage(), e); - } - } -} diff --git a/src/main/java/de/kosit/validationtool/cmd/DemoBuilder.java b/src/main/java/de/kosit/validationtool/cmd/DemoBuilder.java new file mode 100644 index 0000000..074d08d --- /dev/null +++ b/src/main/java/de/kosit/validationtool/cmd/DemoBuilder.java @@ -0,0 +1,69 @@ +package de.kosit.validationtool.cmd; + +import static de.kosit.validationtool.config.ConfigurationBuilder.defaultFallback; +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 static de.kosit.validationtool.config.ConfigurationBuilder.schematron; + +import java.net.URI; + +import javax.xml.validation.Schema; + +import de.kosit.validationtool.api.Configuration; +import de.kosit.validationtool.config.FallbackBuilder; +import de.kosit.validationtool.config.ScenarioBuilder; +import de.kosit.validationtool.impl.ResolvingMode; + +import net.sf.saxon.s9api.XPathExecutable; + +/** + * @author Andreas Penski + */ +public class DemoBuilder { + + public static void main(final String[] args) { + final XPathExecutable xpath = null; + // @formatter:off + Configuration + .create() + .name("some config") + .resolvingMode(ResolvingMode.JDK_SUPPORTED) + .with(scenario("s1").match("//name").validate(schema("http://some.schema.url")).description("some desc")) + .with(scenario("s2") + .match(xpath) + .acceptWith(xpath) + .validate(schema(URI.create("http://some.other.schema.url"))) + .validate(schematron("some checks").source("some-schematron.xsl")) + .with(report("myReport").source(URI.create("some.xsl"))) + .description("some desc")) + .with(defaultFallback()) + + .build(); + + Configuration + .create() + .name("xrechnung") + .resolvingMode(ResolvingMode.STRICT_LOCAL) + .with( ubl() ) + .with(cii()) + .with( myFallback()) + .build(); + // @formatter:on + } + + private static ScenarioBuilder cii() { + return null; + } + + private static FallbackBuilder myFallback() { + return new FallbackBuilder(); + } + + private static ScenarioBuilder ubl() { + final Schema schema = null; // load somehow + final ScenarioBuilder ubl = scenario("ubl"); + ubl.validate(schema("someSchema", schema)); + return ubl; + } +} diff --git a/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java b/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java index 09e84b6..4c472d6 100644 --- a/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java +++ b/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java @@ -24,10 +24,10 @@ import java.nio.file.Path; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import de.kosit.validationtool.impl.ContentRepository; import de.kosit.validationtool.impl.HtmlExtractor; import de.kosit.validationtool.impl.tasks.CheckAction; +import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.QName; import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.Serializer; @@ -49,12 +49,12 @@ class ExtractHtmlContentAction implements CheckAction { private HtmlExtractor htmlExtraction; - private ContentRepository repository; + private Processor processor; - public ExtractHtmlContentAction(final ContentRepository repository, final Path outputDirectory) { + public ExtractHtmlContentAction(final Processor p, final Path outputDirectory) { this.outputDirectory = outputDirectory; - this.htmlExtraction = new HtmlExtractor(repository); - this.repository = repository; + this.htmlExtraction = new HtmlExtractor(p); + this.processor = p; } @Override @@ -66,7 +66,7 @@ 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); diff --git a/src/main/java/de/kosit/validationtool/cmd/Health.java b/src/main/java/de/kosit/validationtool/cmd/Health.java deleted file mode 100644 index 2694142..0000000 --- a/src/main/java/de/kosit/validationtool/cmd/Health.java +++ /dev/null @@ -1,109 +0,0 @@ -package de.kosit.validationtool.cmd; - -import javax.xml.parsers.DocumentBuilder; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import lombok.extern.slf4j.Slf4j; - -import de.kosit.validationtool.api.Configuration; -import de.kosit.validationtool.impl.ObjectFactory; - -/** - * 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 Configuration config; - - Health(final Configuration config) { - - final Runtime runtime = Runtime.getRuntime(); - this.freeMemory = runtime.freeMemory(); - this.maxMemory = runtime.maxMemory(); - this.totalMemory = runtime.totalMemory(); - this.config = config; - } - - /** - * Methode, die schreibt das Health Xml für optimale Status - * - */ - Document writeHealthXml() { - final DocumentBuilder dBuilder = ObjectFactory.createDocumentBuilder(false); - final Document doc = dBuilder.newDocument(); - final Element rootElement = doc.createElementNS("https://localhost:8080/Health", "Health"); - doc.appendChild(rootElement); - rootElement.appendChild(getMemory(doc, this.freeMemory, this.maxMemory, this.totalMemory)); - rootElement.appendChild(getState(doc)); - rootElement.appendChild(getScenario(doc, this.config)); - return doc; - } - - /** - * Methode, die schreibt das System Status Node im Xml File - * - * @param doc Vom Typ Dokument. - * - */ - private static Node getState(final Document doc) { - final Element state = doc.createElement("state"); - state.setAttribute("indicator", "OK"); - final 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 config Vom Typ {@link Configuration} das verwendete scenario. - * - */ - private static Node getScenario(final Document doc, final Configuration config) { - final Element scenario = doc.createElement("scenario"); - final Element scenarioNameNode = doc.createElement("name"); - scenarioNameNode.appendChild(doc.createTextNode(config.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(final Document doc, final long freeMemory, final long maxMemory, final long totalMemory) { - final Element memory = doc.createElement("memoryState"); - final String freeM = Long.toString(freeMemory); - final Element freeMNode = doc.createElement("freeMemory"); - freeMNode.appendChild(doc.createTextNode(freeM)); - memory.appendChild(freeMNode); - final String maxM = Long.toString(maxMemory); - final Element maxMNode = doc.createElement("maxMemory"); - maxMNode.appendChild(doc.createTextNode(maxM)); - memory.appendChild(maxMNode); - final String totalM = Long.toString(totalMemory); - final Element totalMNode = doc.createElement("totalMemory"); - totalMNode.appendChild(doc.createTextNode(totalM)); - memory.appendChild(totalMNode); - return memory; - } -} diff --git a/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java b/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java index db9a7b1..2a2c007 100644 --- a/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java +++ b/src/main/java/de/kosit/validationtool/cmd/InternalCheck.java @@ -21,7 +21,7 @@ package de.kosit.validationtool.cmd; import lombok.extern.slf4j.Slf4j; -import de.kosit.validationtool.api.CheckConfiguration; +import de.kosit.validationtool.api.Configuration; import de.kosit.validationtool.api.Input; import de.kosit.validationtool.api.Result; import de.kosit.validationtool.impl.DefaultCheck; @@ -45,7 +45,7 @@ class InternalCheck extends DefaultCheck { * * @param configuration die Konfiguration */ - InternalCheck(final CheckConfiguration configuration) { + InternalCheck(final Configuration configuration) { super(configuration); } diff --git a/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java b/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java index 2c3faa2..0659397 100644 --- a/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java +++ b/src/main/java/de/kosit/validationtool/cmd/PrintReportAction.java @@ -21,11 +21,12 @@ package de.kosit.validationtool.cmd; import java.io.StringWriter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import de.kosit.validationtool.impl.ObjectFactory; import de.kosit.validationtool.impl.tasks.CheckAction; +import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.Serializer; @@ -35,13 +36,16 @@ import net.sf.saxon.s9api.Serializer; * @author Andreas Penski */ @Slf4j +@RequiredArgsConstructor class PrintReportAction implements CheckAction { + private final Processor processor; + @Override public void check(Bag results) { try { final StringWriter writer = new StringWriter(); - final Serializer serializer = ObjectFactory.createProcessor().newSerializer(writer); + final Serializer serializer = processor.newSerializer(writer); serializer.serializeNode(results.getReport()); System.out.print(writer.toString()); } catch (SaxonApiException e) { diff --git a/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java b/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java index 1c26479..cc2ef04 100644 --- a/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java +++ b/src/main/java/de/kosit/validationtool/cmd/SerializeReportAction.java @@ -24,9 +24,9 @@ import java.nio.file.Path; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import de.kosit.validationtool.impl.ObjectFactory; import de.kosit.validationtool.impl.tasks.CheckAction; +import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.Serializer; @@ -41,12 +41,14 @@ class SerializeReportAction implements CheckAction { private final Path outputDirectory; + private final Processor processor; + @Override public void check(Bag results) { final Path file = outputDirectory.resolve(results.getName() + "-report.xml"); try { log.info("Serializing result to {}", file.toAbsolutePath()); - final Serializer serializer = ObjectFactory.createProcessor().newSerializer(file.toFile()); + final Serializer serializer = processor.newSerializer(file.toFile()); serializer.serializeNode(results.getReport()); } catch (SaxonApiException e) { log.error("Can not serialize result report to {}", file.toAbsolutePath(), e); diff --git a/src/main/java/de/kosit/validationtool/config/BaseConfiguration.java b/src/main/java/de/kosit/validationtool/config/BaseConfiguration.java deleted file mode 100644 index 167099a..0000000 --- a/src/main/java/de/kosit/validationtool/config/BaseConfiguration.java +++ /dev/null @@ -1,92 +0,0 @@ -package de.kosit.validationtool.config; - -import java.util.List; - -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.ObjectFactory; -import de.kosit.validationtool.impl.Scenario; - -import net.sf.saxon.s9api.Processor; - -/** - * Base configuration class. - * - * @author Andreas Penski - */ -@Slf4j -public abstract class BaseConfiguration implements Configuration { - - @RequiredArgsConstructor - @Getter - @Setter - protected static class RuntimeArtefacts { - - private final List scenarios; - - private final Scenario fallbackScenario; - - private String name; - - private String author; - - private String date; - } - - private RuntimeArtefacts artefacts; - - protected abstract RuntimeArtefacts buildArtefacts(); - - @Override - public void build() { - if (this.artefacts != null) { - log.warn("Configuration already complete. Will drop previous artefacts and build again"); - } - this.artefacts = buildArtefacts(); - } - - @Override - public List getScenarios() { - assertBuild(); - return this.artefacts.getScenarios(); - } - - @Override - public Scenario getFallbackScenario() { - assertBuild(); - return this.artefacts.getFallbackScenario(); - } - - private void assertBuild() { - if (this.artefacts == null) { - throw new IllegalStateException("Configuration"); - } - } - - @Override - public Processor getProcessor() { - return ObjectFactory.createProcessor(); - } - - @Override - public String getAuthor() { - assertBuild(); - return this.artefacts.getAuthor(); - } - - @Override - public String getName() { - assertBuild(); - return this.artefacts.getName(); - } - - @Override - public String getDate() { - assertBuild(); - return this.artefacts.getDate(); - } -} diff --git a/src/main/java/de/kosit/validationtool/config/Builder.java b/src/main/java/de/kosit/validationtool/config/Builder.java new file mode 100644 index 0000000..bdee7fd --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/Builder.java @@ -0,0 +1,12 @@ +package de.kosit.validationtool.config; + +import de.kosit.validationtool.impl.ContentRepository; +import de.kosit.validationtool.impl.model.Result; + +/** + * @author Andreas Penski + */ +public interface Builder { + + Result build(ContentRepository repository); +} diff --git a/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java index 27dc6b5..e1ec359 100644 --- a/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java +++ b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java @@ -1,10 +1,245 @@ package de.kosit.validationtool.config; +import java.net.URI; +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 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 net.sf.saxon.s9api.Processor; + /** + * Implements a builder style creation of a {@link Configuration}. + * * @author Andreas Penski */ +@Slf4j public class ConfigurationBuilder { - private ScenarioBuilder scenarioBuilder; + private final List scenarios = new ArrayList<>(); + private FallbackBuilder fallbackBuilder; + + private ResolvingConfigurationStrategy resolvingConfigurationStrategy; + + private ResolvingMode resolvingMode = ResolvingMode.STRICT_RELATIVE; + + private Processor processor; + + private String author = "API"; + + private String date = LocalDate.now().toString(); + + private String name = "Custom"; + + private final Map parameters = new HashMap<>(); + + private URI repository; + + public ConfigurationBuilder author(final String authorName) { + this.author = authorName; + return this; + } + + public ConfigurationBuilder date(final LocalDate localDate) { + this.date = localDate.toString(); + return this; + } + + public ConfigurationBuilder name(final String name) { + this.name = name; + return this; + } + + public ConfigurationBuilder date(final Date date) { + return date(LocalDate.ofEpochDay(date.getTime())); + } + + public ConfigurationBuilder with(final ScenarioBuilder scenarioBuilder) { + this.scenarios.add(scenarioBuilder); + return this; + } + + public ConfigurationBuilder with(final FallbackBuilder builder) { + if (this.fallbackBuilder != null) { + log.warn("Overriding previously created fallback scenario"); + } + this.fallbackBuilder = builder; + 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"); + } + + 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); + } + + /** + * 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); + } + + public Configuration build() { + final ResolvingConfigurationStrategy resolving = getResolvingConfigurationStrategy(); + if (this.processor == null) { + this.processor = resolving.createProcessor(); + } + final ContentRepository contentRepository = new ContentRepository(this.processor, this.repository, + resolving.createResolver(this.repository)); + contentRepository.setSchemaFactory(resolving.createSchemaFactory()); + contentRepository.setResolvingConfigurationStrategy(resolving); + + final List list = initializeScenarios(contentRepository); + final Scenario fallbackScenario = initializeFallback(contentRepository); + final DefaultConfiguration configuration = new DefaultConfiguration(list, fallbackScenario); + configuration.setAdditionalParameters(this.parameters); + configuration.setAuthor(this.author); + configuration.setDate(this.date); + configuration.setName(this.name); + configuration.setContentRepository(contentRepository); + return (configuration); + } + + private Scenario initializeFallback(final ContentRepository contentRepository) { + if (this.fallbackBuilder == null) { + throw new IllegalStateException("No fallback configuration specified"); + } + final Result result = this.fallbackBuilder.build(contentRepository); + if (result.isInvalid()) { + throw new IllegalStateException("Invalid fallback configuration: " + String.join(",", result.getErrors())); + } + return result.getObject(); + } + + private List initializeScenarios(final ContentRepository contentRepository) { + if (this.scenarios.size() == 0) { + throw new IllegalStateException("No scenario specified"); + } + return this.scenarios.stream().map(s -> { + final Result result = s.build(contentRepository); + if (result.isInvalid()) { + final String msg = String.join(",", result.getErrors()); + throw new IllegalStateException(String.format("Invalid configuration for scenario %s found: %s", s.getName(), msg)); + } + return result.getObject(); + }).collect(Collectors.toList()); + } + + private ResolvingConfigurationStrategy getResolvingConfigurationStrategy() { + if (this.resolvingConfigurationStrategy != null) { + log.info("Custom resolving strategy supplied. Please take care of xml security!"); + return this.resolvingConfigurationStrategy; + } + log.info("Using resolving strategy {}", this.resolvingMode); + return this.resolvingMode.getStrategy(); + } + + 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; + } + + public ConfigurationBuilder useRepository(final URI repository) { + this.repository = repository; + return this; + } } diff --git a/src/main/java/de/kosit/validationtool/config/LoadConfiguration.java b/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java similarity index 59% rename from src/main/java/de/kosit/validationtool/config/LoadConfiguration.java rename to src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java index e9f52e0..589979a 100644 --- a/src/main/java/de/kosit/validationtool/config/LoadConfiguration.java +++ b/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java @@ -4,28 +4,36 @@ 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.AllArgsConstructor; 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.RelativeUriResolver; +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.CreateReportType; +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; @@ -36,9 +44,9 @@ import net.sf.saxon.s9api.XdmNodeKind; * * @author Andreas Penski */ -@AllArgsConstructor +@RequiredArgsConstructor @Slf4j -public class LoadConfiguration extends BaseConfiguration { +public class ConfigurationLoader { private static final String SUPPORTED_MAJOR_VERSION = "1"; @@ -55,6 +63,12 @@ public class LoadConfiguration extends BaseConfiguration { */ private final URI scenarioRepository; + protected ResolvingMode resolvingMode = ResolvingMode.STRICT_RELATIVE; + + protected ResolvingConfigurationStrategy resolvingConfigurationStrategy; + + protected final Map parameters = new HashMap<>(); + URI getScenarioRepository() { if (this.scenarioRepository == null) { log.info("Creating default scenario repository (alongside scenario definition)"); @@ -63,9 +77,10 @@ public class LoadConfiguration extends BaseConfiguration { return this.scenarioRepository; } - private static void checkVersion(final URI scenarioDefinition) { + private static void checkVersion(final URI scenarioDefinition, final Processor processor) { try { - final Result result = DocumentParseAction.parseDocument(InputFactory.read(scenarioDefinition.toURL())); + final Result result = new DocumentParseAction(processor) + .parseDocument(InputFactory.read(scenarioDefinition.toURL())); if (result.isValid() && !isSupportedDocument(result.getObject())) { throw new IllegalStateException(String.format( "Specified scenario configuration %s is not supported.%nThis version only supports definitions of '%s'", @@ -94,54 +109,53 @@ public class LoadConfiguration extends BaseConfiguration { } private static Scenario createFallback(final Scenarios scenarios, final ContentRepository repository) { - final ScenarioType t = new ScenarioType(); - t.setName("Fallback-Scenario"); - t.setMatch("count(/)<0"); - final CreateReportType reportType = new CreateReportType(); - reportType.setResource(scenarios.getNoScenarioReport().getResource()); - // always reject - t.setAcceptMatch("count(/)<0"); - t.setCreateReport(reportType); - final Scenario sceanrio = initialize(t, repository); - sceanrio.setFallback(true); - return sceanrio; + final ResourceType noscenarioResource = scenarios.getNoScenarioReport().getResource(); + return new FallbackBuilder().source(noscenarioResource.getLocation()).name(noscenarioResource.getName()).build(repository) + .getObject(); + } - @Override - protected RuntimeArtefacts buildArtefacts() { - final ContentRepository contentRepository = buildContentRepository(); - final Scenarios def = loadScenarios(); + public Configuration build() { + final ResolvingConfigurationStrategy resolving = getResolvingConfigurationStrategy(); + final Processor processor = resolving.createProcessor(); + final ContentRepository contentRepository = new ContentRepository(processor, getScenarioRepository(), + resolving.createResolver(this.getScenarioRepository())); + contentRepository.setSchemaFactory(resolving.createSchemaFactory()); + contentRepository.setResolvingConfigurationStrategy(resolving); + + final Scenarios def = loadScenarios(contentRepository.getScenarioSchema(), processor); final List scenarios = initializeScenarios(def, contentRepository); final Scenario fallbackScenario = createFallback(def, contentRepository); - final RuntimeArtefacts runtimeArtefacts = new RuntimeArtefacts(scenarios, fallbackScenario); - runtimeArtefacts.setAuthor(def.getAuthor()); - runtimeArtefacts.setDate(def.getDate().toString()); - runtimeArtefacts.setName(def.getName()); - return runtimeArtefacts; + 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); + return (configuration); } private static List initializeScenarios(final Scenarios def, final ContentRepository contentRepository) { return def.getScenario().stream().map(s -> initialize(s, contentRepository)).collect(Collectors.toList()); } - private ContentRepository buildContentRepository() { - return new ContentRepository(getProcessor(), getScenarioRepository()); + 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(); } - @Override - public ContentRepository getContentRepository() { - return buildContentRepository(); - } - - private Scenarios loadScenarios() { + private Scenarios loadScenarios(final Schema scenarioSchema, final Processor processor) { final ConversionService conversionService = new ConversionService(); - checkVersion(this.scenarioDefinition); + 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, - getContentRepository().getScenarioSchema(), handler); + final Scenarios scenarios = conversionService.readXml(this.scenarioDefinition, Scenarios.class, scenarioSchema, handler); if (!handler.hasErrors()) { - log.info("Loading scenario content from {}", this.scenarioRepository); + 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())); @@ -152,27 +166,23 @@ public class LoadConfiguration extends BaseConfiguration { private static Scenario initialize(final ScenarioType def, final ContentRepository repository) { final Scenario s = new Scenario(def); - s.setSchema(repository.createSchema(def)); - s.setReportTransformation(repository.createReportTransformation(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; } - @Override - public String getAuthor() { - return null; + public ConfigurationLoader setResolvingMode(final ResolvingMode mode) { + this.resolvingMode = mode; + return this; } - @Override - public String getName() { - return null; - } - - @Override - public String getDate() { - return null; + public ConfigurationLoader addParameter(final String name, final Object value) { + this.parameters.put(name, value); + return this; } } diff --git a/src/main/java/de/kosit/validationtool/config/DefaultConfiguration.java b/src/main/java/de/kosit/validationtool/config/DefaultConfiguration.java new file mode 100644 index 0000000..1af8820 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/DefaultConfiguration.java @@ -0,0 +1,40 @@ +package de.kosit.validationtool.config; + +import java.util.List; +import java.util.Map; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.api.Configuration; +import de.kosit.validationtool.impl.ContentRepository; +import de.kosit.validationtool.impl.Scenario; + +/** + * Default implementation class for {@link Configuration}. This class contains all information to run a + * {@link de.kosit.validationtool.impl.DefaultCheck}. + * + * @author Andreas Penski + */ +@Slf4j +@RequiredArgsConstructor +@Getter +@Setter +public class DefaultConfiguration implements Configuration { + + private final List scenarios; + + private final Scenario fallbackScenario; + + private ContentRepository contentRepository; + + private String name; + + private String author; + + private String date; + + public Map additionalParameters; +} diff --git a/src/main/java/de/kosit/validationtool/config/FallbackBuilder.java b/src/main/java/de/kosit/validationtool/config/FallbackBuilder.java new file mode 100644 index 0000000..5399f39 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/FallbackBuilder.java @@ -0,0 +1,95 @@ +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; + +/** + * @author Andreas Penski + */ +public class FallbackBuilder implements Builder { + + private final ReportBuilder internal = new ReportBuilder().name("fallback"); + + @Override + public Result build(final ContentRepository repository) { + final ScenarioType object = createObject(); + final Result, String> build = this.internal.build(repository); + final Result result; + if (build.isValid()) { + object.setCreateReport(build.getObject().getLeft()); + final Scenario s = new Scenario(object); + s.setFallback(true); + s.setReportTransformation(build.getObject().getRight()); + result = new Result<>(s); + } else { + result = new Result<>(build.getErrors()); + } + return result; + } + + private static ScenarioType createObject() { + final ScenarioType t = new ScenarioType(); + t.setName("Fallback-Scenario"); + t.setMatch("count(/)<0"); + // always reject + t.setAcceptMatch("count(/)<0"); + return t; + } + + /** + * Specifices a source for this report. This is either used to compile the report transformation or as documentation for + * a precompiled tranformation. + * + * @param source the source + * @return this + */ + public FallbackBuilder source(final String source) { + this.internal.source(source); + return this; + } + + /** + * Specifices a source for this report. This is either used to compile the report transformation or as documentation for + * a precompiled tranformation. + * + * @param source the source + * @return this + */ + public FallbackBuilder source(final URI source) { + this.internal.source(source); + return this; + } + + /** + * Specifices a source for this report. This is either used to compile the report transformation or as documentation for + * a precompiled tranformation. + * + * @param source the source + * @return this + */ + public FallbackBuilder source(final Path source) { + this.internal.source(source); + return this; + } + + /** + * Sets the name of the report source to a specific value. + * + * @param name the name + * @return this + */ + public FallbackBuilder name(final String name) { + this.internal.name(name); + return this; + } + +} diff --git a/src/main/java/de/kosit/validationtool/config/ReportBuilder.java b/src/main/java/de/kosit/validationtool/config/ReportBuilder.java new file mode 100644 index 0000000..1c69053 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/ReportBuilder.java @@ -0,0 +1,116 @@ +package de.kosit.validationtool.config; + +import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Collections; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.impl.ContentRepository; +import de.kosit.validationtool.impl.Scenario.Transformation; +import de.kosit.validationtool.impl.model.Result; +import de.kosit.validationtool.model.scenarios.CreateReportType; +import de.kosit.validationtool.model.scenarios.ResourceType; + +import net.sf.saxon.s9api.XsltExecutable; + +/** + * Builder style configuration for the report transformation. + * + * @author Andreas Penski + */ +@Slf4j +public class ReportBuilder implements Builder> { + + private static final String DEFAULT_NAME = "manually created report"; + + private XsltExecutable executable; + + private URI source; + + private String name; + + @Override + public Result, String> build(final ContentRepository repository) { + if (this.executable == null && this.source == null) { + return createError("Must supply source location and/or executable"); + } + final CreateReportType object = createObject(); + Result, String> result; + + try { + if (this.executable == null) { + this.executable = repository.createTransformation(object.getResource()).getExecutable(); + } + result = new Result<>(new ImmutablePair<>(object, new Transformation(this.executable, object.getResource()))); + } catch (final IllegalStateException e) { + log.error(e.getMessage(), e); + result = createError( + String.format("Can not create report configuration based on %s. Exception is %s", this.source, e.getMessage())); + } + return result; + } + + private CreateReportType createObject() { + final CreateReportType o = new CreateReportType(); + final ResourceType r = new ResourceType(); + r.setLocation(this.source.toASCIIString()); + r.setName(isNotEmpty(this.name) ? this.name : DEFAULT_NAME); + o.setResource(r); + return o; + } + + private static Result, String> createError(final String msg) { + return new Result<>(null, Collections.singletonList(msg)); + } + + /** + * Specifices a source for this report. This is either used to compile the report transformation or as documentation for + * a precompiled tranformation. + * + * @param source the source + * @return this + */ + public ReportBuilder source(final String source) { + return source(URI.create(source)); + } + + /** + * Specifices a source for this report. This is either used to compile the report transformation or as documentation for + * a precompiled tranformation. + * + * @param source the source + * @return this + */ + public ReportBuilder source(final URI source) { + this.source = source; + return this; + } + + /** + * Specifices a source for this report. This is either used to compile the report transformation or as documentation for + * a precompiled tranformation. + * + * @param source the source + * @return this + */ + public ReportBuilder source(final Path source) { + return source(source.toUri()); + } + + /** + * Sets the name of the report source to a specific value. + * + * @param name the name + * @return this + */ + public ReportBuilder name(final String name) { + this.name = name; + return this; + } +} diff --git a/src/main/java/de/kosit/validationtool/config/ScenarioBuilder.java b/src/main/java/de/kosit/validationtool/config/ScenarioBuilder.java index 0a2a700..18f6536 100644 --- a/src/main/java/de/kosit/validationtool/config/ScenarioBuilder.java +++ b/src/main/java/de/kosit/validationtool/config/ScenarioBuilder.java @@ -1,72 +1,270 @@ package de.kosit.validationtool.config; -import java.net.URL; -import java.util.Collections; +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.w3c.dom.ls.LSResourceResolver; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import de.kosit.validationtool.impl.ContentRepository; -import de.kosit.validationtool.impl.ObjectFactory; +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.XsltExecutable; +import net.sf.saxon.s9api.XPathExecutable; /** + * Builder for {@link Scenario} configuration. + * * @author Andreas Penski */ -public class ScenarioBuilder { +@RequiredArgsConstructor +public class ScenarioBuilder implements Builder { - private final ScenarioType scenario; + private static int nameCount = 0; - private final ContentRepository contentRepository = new ContentRepository(ObjectFactory.createProcessor(), null); + private static final String DEFAULT_DESCRIPTION = "Dieses Scenario wurde per API erstellt"; - ScenarioBuilder(final String name) { - this.scenario = new ScenarioType(); - this.scenario.setName(name); + private final Map namespaces = new HashMap<>(); + + private final XPathBuilder matchConfig = new XPathBuilder(); + + private final XPathBuilder acceptConfig = new XPathBuilder(); + + @Getter(AccessLevel.PACKAGE) + private final String name; + + private SchemaBuilder schemaBuilder; + + private final List schematronBuilders = new ArrayList<>(); + + private ReportBuilder reportBuilder; + + private String description; + + @Override + public Result build(final ContentRepository repository) { + final List errors = new ArrayList<>(); + final Scenario scenario = new Scenario(createType()); + buildMatch(repository, errors, scenario); + buildSchema(repository, errors, scenario); + buildSchematron(repository, errors, scenario); + buildReport(repository, errors, scenario); + buildAccept(repository, errors, scenario); + buildNamespaces(scenario); + return new Result<>(scenario, errors); } - public ScenarioBuilder matches(final String xpath) { - return matches(xpath, Collections.emptyMap()); - } - - private ScenarioBuilder matches(final String xpath, final Map namespaces) { - // final XPathExecutable matchExecutable = this.contentRepository.createXPath(xpath, namespaces); - // this.scenario.setMatchExecutable(matchExecutable); - // this.scenario.setMatch(xpath); - // if (namespaces != null) { - // this.scenario.getNamespace().addAll(namespaces.entrySet().stream().map(e -> { - // NamespaceType t = new NamespaceType(); - // t.setPrefix(e.getKey()); - // t.setValue(e.getValue()); - // return t; - // }).collect(Collectors.toList())); - // } else { - // this.scenario.getNamespace().clear(); - // } + /** + * 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; } - public ScenarioBuilder schemaValidation(final Schema schema) { + /** + * 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; } - public ScenarioBuilder schemaValidation(final URL url) { - return schemalidation(url, null); - } - - private ScenarioBuilder schemalidation(final URL url, final LSResourceResolver resolver) { + /** + * 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; } - public ScenarioBuilder addSchematronValidation(final XsltExecutable executable) { + /** + * 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; } - public ScenarioBuilder withReportGenerator(final XsltExecutable executable) { + /** + * Add an xpath expression to compute acceptance for the scenario. You can leverage declared namespaces. + * + * @param acceptXpath the xpath expresison + * @return this + */ + public ScenarioBuilder acceptWith(final String acceptXpath) { + this.acceptConfig.setXpath(acceptXpath); return this; } + /** + * Add a schematron validation configuration for this scenario. + * + * @param schematron the schematron configuration + * @return this + */ + public ScenarioBuilder validate(final SchematronBuilder schematron) { + if (schematron != null) { + this.schematronBuilders.add(schematron); + } + return this; + } + + /** + * Validate matching {@link de.kosit.validationtool.api.Input Inputs} with the specified schema configuration. + * + * @param schema the schema configuration + * @return this + */ + public ScenarioBuilder validate(final SchemaBuilder schema) { + this.schemaBuilder = schema; + return this; + } + + /** + * Add description for this scenario. This is part of the + * {@link de.kosit.validationtool.model.reportInput.CreateReportInput} configuration and can be used while creating the + * report + * + * @param description the description + * @return this + */ + public ScenarioBuilder description(final String description) { + this.description = description; + return this; + } + + /** + * Add a configuration for generating the final report for the {@link de.kosit.validationtool.api.Input}. + * + * @param reportBuilder the report configuration + * @return this + */ + public ScenarioBuilder with(final ReportBuilder reportBuilder) { + this.reportBuilder = reportBuilder; + return this; + } + + private static String generateName() { + return "manually created scenario " + nameCount++; + } + + private void buildNamespaces(final Scenario scenario) { + this.namespaces.putAll(this.acceptConfig.getNamespaces()); + this.namespaces.putAll(this.matchConfig.getNamespaces()); + final List all = this.namespaces.entrySet().stream().map(e -> { + final NamespaceType n = new NamespaceType(); + n.setPrefix(e.getKey()); + n.setValue(e.getValue()); + return n; + }).collect(Collectors.toList()); + scenario.getConfiguration().getNamespace().addAll(all); + } + + private void buildMatch(final ContentRepository repository, final List errors, final Scenario scenario) { + this.matchConfig.setNamespaces(this.namespaces); + final Result result = this.matchConfig.build(repository); + if (result.isValid()) { + scenario.setMatchExecutable(result.getObject()); + scenario.getConfiguration().setMatch(this.matchConfig.getXPath()); + this.namespaces.putAll(this.matchConfig.getNamespaces()); + } else { + errors.addAll(result.getErrors()); + } + } + + private void buildAccept(final ContentRepository repository, final List errors, final Scenario scenario) { + this.acceptConfig.setNamespaces(this.namespaces); + final Result result = this.acceptConfig.build(repository); + if (result.isValid()) { + scenario.setAcceptExecutable(result.getObject()); + scenario.getConfiguration().setAcceptMatch(this.acceptConfig.getXPath()); + this.namespaces.putAll(this.acceptConfig.getNamespaces()); + } else { + errors.addAll(result.getErrors()); + } + } + + private void buildReport(final ContentRepository repository, final List errors, final Scenario scenario) { + if (this.reportBuilder == null) { + errors.add("Must supply report configuration"); + } else { + final Result, String> result = this.reportBuilder.build(repository); + if (result.isValid()) { + scenario.setReportTransformation(result.getObject().getRight()); + scenario.getConfiguration().setCreateReport(result.getObject().getLeft()); + } else { + errors.addAll(result.getErrors()); + } + } + } + + private void buildSchematron(final ContentRepository repository, final List errors, final Scenario scenario) { + this.schematronBuilders.forEach(e -> { + final Result, String> result = e.build(repository); + if (result.isValid()) { + scenario.getConfiguration().getValidateWithSchematron().add(result.getObject().getLeft()); + scenario.getSchematronValidations().add(result.getObject().getRight()); + } else { + errors.addAll(result.getErrors()); + } + }); + } + + private void buildSchema(final ContentRepository repository, final List errors, final Scenario scenario) { + if (this.schemaBuilder == null) { + errors.add("Must supply schema for validation"); + } else { + final Result, String> result = this.schemaBuilder.build(repository); + if (result.isValid()) { + scenario.setSchema(result.getObject().getRight()); + scenario.getConfiguration().setValidateWithXmlSchema(result.getObject().getLeft()); + } else { + errors.addAll(result.getErrors()); + } + } + } + + private ScenarioType createType() { + final ScenarioType type = new ScenarioType(); + type.setName(isNotEmpty(this.name) ? this.name : generateName()); + final DescriptionType desc = new DescriptionType(); + desc.getPOrOlOrUl() + .add(new ObjectFactory().createDescriptionTypeP(StringUtils.defaultIfBlank(this.description, DEFAULT_DESCRIPTION))); + type.setDescription(desc); + return type; + } + } diff --git a/src/main/java/de/kosit/validationtool/config/SchemaBuilder.java b/src/main/java/de/kosit/validationtool/config/SchemaBuilder.java new file mode 100644 index 0000000..49dd722 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/SchemaBuilder.java @@ -0,0 +1,121 @@ +package de.kosit.validationtool.config; + +import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Collections; + +import javax.xml.validation.Schema; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.impl.ContentRepository; +import de.kosit.validationtool.impl.model.Result; +import de.kosit.validationtool.model.scenarios.ResourceType; +import de.kosit.validationtool.model.scenarios.ValidateWithXmlSchema; + +/** + * Builder for Schema validation configuration. + * + * @author Andreas Penski + */ +@Slf4j +public class SchemaBuilder implements Builder> { + + private static final String DEFAULT_NAME = "manually configured"; + + private Schema schema; + + private URI schemaLocation; + + private String name; + + @Override + public Result, String> build(final ContentRepository repository) { + if (this.schema == null && this.schemaLocation == null) { + return createError("Must supply schema location and/or schema"); + } + Result, String> result; + try { + if (this.schema == null) { + this.schema = repository.createSchema(this.schemaLocation); + } + result = new Result<>(new ImmutablePair<>(createObject(), this.schema)); + } catch (final IllegalStateException e) { + log.error(e.getMessage(), e); + result = createError(String.format("Can not create schema based %s. Exception is %s", this.schemaLocation, e.getMessage())); + } + + return result; + } + + private ValidateWithXmlSchema createObject() { + final ValidateWithXmlSchema o = new ValidateWithXmlSchema(); + final ResourceType r = new ResourceType(); + r.setName(isNotEmpty(this.name) ? this.name : DEFAULT_NAME); + r.setLocation(this.schemaLocation.toASCIIString()); + o.getResource().add(r); + return o; + } + + private static Result, String> createError(final String msg) { + return new Result<>(null, Collections.singletonList(msg)); + } + + /** + * Set a specific precompiled schema to check. + * + * @param schema the {@link Schema} + * @return this + */ + public SchemaBuilder schema(final Schema schema) { + this.schema = schema; + return this; + } + + /** + * Set a specific schema location either to compile or to document the precompiled one . + * + * @param schemaLocation the schema location as uri + * @return this + */ + public SchemaBuilder schemaLocation(final URI schemaLocation) { + this.schemaLocation = schemaLocation; + return this; + } + + /** + * Set a specific schema location either to compile or to document the precompiled one . + * + * @param schemaLocation the schema location as uri + * @return this + */ + public SchemaBuilder schemaLocation(final String schemaLocation) { + return schemaLocation(URI.create(schemaLocation)); + } + + /** + * Set a specific schema location either to compile or to document the precompiled one . + * + * @param schemaLocation the schema location as uri + * @return this + */ + public SchemaBuilder schemaLocation(final Path schemaLocation) { + return schemaLocation(schemaLocation.toUri()); + } + + /** + * Set a specific name to identify this schema. + * + * @param name the name of the schema + * @return this + */ + public SchemaBuilder name(final String name) { + this.name = name; + return this; + } +} diff --git a/src/main/java/de/kosit/validationtool/config/SchematronBuilder.java b/src/main/java/de/kosit/validationtool/config/SchematronBuilder.java new file mode 100644 index 0000000..ad74f43 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/SchematronBuilder.java @@ -0,0 +1,116 @@ +package de.kosit.validationtool.config; + +import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Collections; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.impl.ContentRepository; +import de.kosit.validationtool.impl.Scenario.Transformation; +import de.kosit.validationtool.impl.model.Result; +import de.kosit.validationtool.model.scenarios.ResourceType; +import de.kosit.validationtool.model.scenarios.ValidateWithSchematron; + +import net.sf.saxon.s9api.XsltExecutable; + +/** + * Builder for schematron validation configuration. + * + * @author Andreas Penski + */ +@Slf4j +public class SchematronBuilder implements Builder> { + + private static final String DEFAULT_NAME = "manually configured"; + + private XsltExecutable executable; + + private URI source; + + private String name; + + @Override + public Result, String> build(final ContentRepository repository) { + if (this.executable == null && this.source == null) { + return createError("Must supply source location and/or executable"); + } + final ValidateWithSchematron object = createObject(); + Result, String> result; + + try { + if (this.executable == null) { + this.executable = repository.createSchematronTransformation(object).getExecutable(); + } + result = new Result<>(new ImmutablePair<>(object, new Transformation(this.executable, object.getResource()))); + } catch (final IllegalStateException e) { + log.error(e.getMessage(), e); + result = createError( + String.format("Can not create schematron configuration based on %s. Exception is %s", this.source, e.getMessage())); + } + return result; + } + + private ValidateWithSchematron createObject() { + final ValidateWithSchematron o = new ValidateWithSchematron(); + final ResourceType r = new ResourceType(); + r.setLocation(this.source.toASCIIString()); + r.setName(isNotEmpty(this.name) ? this.name : DEFAULT_NAME); + o.setResource(r); + return o; + } + + private static Result, String> createError(final String msg) { + return new Result<>(null, Collections.singletonList(msg)); + } + + /** + * Specifices a source for this schematron validation. This is either used to compile the schematron transformation or + * as documentation for a precompiled tranformation. + * + * @param source the source + * @return this + */ + public SchematronBuilder source(final String source) { + return source(URI.create(source)); + } + + /** + * Specifices a source for this schematron validation. This is either used to compile the schematron transformation or + * as documentation for a precompiled tranformation. + * + * @param source the source + * @return this + */ + public SchematronBuilder source(final URI source) { + this.source = source; + return this; + } + + /** + * Specifices a source for this schematron validation. This is either used to compile the schematron transformation or + * as documentation for a precompiled tranformation. + * + * @param source the source + * @return this + */ + public SchematronBuilder source(final Path source) { + return source(source.toUri()); + } + + /** + * Sets the name of the schematron source to a specific value. + * + * @param name the name + * @return this + */ + public SchematronBuilder name(final String name) { + this.name = name; + return this; + } +} diff --git a/src/main/java/de/kosit/validationtool/config/XPathBuilder.java b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java new file mode 100644 index 0000000..464d745 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java @@ -0,0 +1,83 @@ +package de.kosit.validationtool.config; + +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 lombok.AccessLevel; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +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 + */ +@Data +class XPathBuilder implements Builder { + + private static final String[] IGNORED_PREFIXES = new String[] { "xsd" }; + + private String xpath; + + private XPathExecutable executable; + + @Setter(AccessLevel.PACKAGE) + @Getter(AccessLevel.PACKAGE) + private Map 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; + + } + + @Override + public Result build(final ContentRepository repository) { + if (this.executable == null && this.xpath == null) { + return createError("No configuration for xpath expression found"); + } + if (this.executable == null) { + this.executable = repository.createXPath(this.xpath, this.namespaces); + } else { + this.xpath = extractExpression(); + this.namespaces = extractNamespaces(); + } + return new Result<>(this.executable); + } + + private Map extractNamespaces() { + final Map ns = new HashMap<>(); + final Iterator iterator = this.executable.getUnderlyingExpression().getInternalExpression().getRetainedStaticContext() + .iteratePrefixes(); + final Iterable iterable = () -> iterator; + StreamSupport.stream(iterable.spliterator(), false).filter(e -> !ArrayUtils.contains(IGNORED_PREFIXES, e)).forEach(e -> { + ns.put(e, + this.executable.getUnderlyingExpression().getInternalExpression().getRetainedStaticContext().getURIForPrefix(e, false)); + }); + return ns; + } + + private String extractExpression() { + return this.executable.getUnderlyingExpression().getInternalExpression().toString(); + } + + private static Result createError(final String msg) { + return new Result<>(null, Collections.singletonList(msg)); + } +} diff --git a/src/main/java/de/kosit/validationtool/daemon/Daemon.java b/src/main/java/de/kosit/validationtool/daemon/Daemon.java new file mode 100644 index 0000000..dc4ca80 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/daemon/Daemon.java @@ -0,0 +1,114 @@ +package de.kosit.validationtool.daemon; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; + +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.HttpServer; + +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.DefaultCheck; +import de.kosit.validationtool.impl.ObjectFactory; + +/** + * HTTP-Daemon für die Bereitstellung der Prüf-Funktionalität via http. + * + * @author Roula Antoun + */ +@RequiredArgsConstructor +@Setter +@Getter +@Slf4j +public class Daemon { + + 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 + */ + 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 + */ + 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 . + */ + 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 + * + * @param config the configuration to use + */ + public void startServer(final Configuration config) { + 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(config)); + server.setExecutor(Executors.newFixedThreadPool(this.threadCount)); + server.start(); + log.info("Server unter Port {} ist erfolgreich gestartet", this.port); + } catch (final IOException e) { + log.error("Fehler beim HttpServer erstellen: {}", e.getMessage(), e); + } + } +} diff --git a/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java b/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java new file mode 100644 index 0000000..28b1ff7 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java @@ -0,0 +1,141 @@ +package de.kosit.validationtool.daemon; + +import java.io.IOException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.TransformerException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.api.Configuration; +import de.kosit.validationtool.impl.ObjectFactory; + +/** + * 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 +class HealthHandler implements HttpHandler { + + /** + * Klasse zur Erzeugung Health Xml , die optiamle Status. + * + * @author Roula Antoun + */ + @Slf4j + static class Health { + + private final long freeMemory; + + private final long maxMemory; + + private final long totalMemory; + + private final Configuration config; + + Health(final Configuration config) { + + final Runtime runtime = Runtime.getRuntime(); + this.freeMemory = runtime.freeMemory(); + this.maxMemory = runtime.maxMemory(); + this.totalMemory = runtime.totalMemory(); + this.config = config; + } + + /** + * Methode, die schreibt das Health Xml für optimale Status + * + */ + Document writeHealthXml() { + final DocumentBuilder dBuilder = ObjectFactory.createDocumentBuilder(false); + final Document doc = dBuilder.newDocument(); + final Element rootElement = doc.createElementNS("https://localhost:8080/Health", "Health"); + doc.appendChild(rootElement); + rootElement.appendChild(getMemory(doc, this.freeMemory, this.maxMemory, this.totalMemory)); + rootElement.appendChild(getState(doc)); + rootElement.appendChild(getScenario(doc, this.config)); + return doc; + } + + /** + * Methode, die schreibt das System Status Node im Xml File + * + * @param doc Vom Typ Dokument. + * + */ + private static Node getState(final Document doc) { + final Element state = doc.createElement("state"); + state.setAttribute("indicator", "OK"); + final 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 config Vom Typ {@link Configuration} das verwendete scenario. + * + */ + private static Node getScenario(final Document doc, final Configuration config) { + final Element scenario = doc.createElement("scenario"); + final Element scenarioNameNode = doc.createElement("name"); + scenarioNameNode.appendChild(doc.createTextNode(config.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(final Document doc, final long freeMemory, final long maxMemory, final long totalMemory) { + final Element memory = doc.createElement("memoryState"); + final String freeM = Long.toString(freeMemory); + final Element freeMNode = doc.createElement("freeMemory"); + freeMNode.appendChild(doc.createTextNode(freeM)); + memory.appendChild(freeMNode); + final String maxM = Long.toString(maxMemory); + final Element maxMNode = doc.createElement("maxMemory"); + maxMNode.appendChild(doc.createTextNode(maxM)); + memory.appendChild(maxMNode); + final String totalM = Long.toString(totalMemory); + final Element totalMNode = doc.createElement("totalMemory"); + totalMNode.appendChild(doc.createTextNode(totalM)); + memory.appendChild(totalMNode); + return memory; + } + } + + private final Configuration scenarios; + + HealthHandler(final Configuration config) { + this.scenarios = config; + } + + @Override + public void handle(final HttpExchange httpExchange) throws IOException { + final Health health = new Health(this.scenarios); + final Document doc = health.writeHealthXml(); + try { + Daemon.writeOutputstreamArray(httpExchange, doc); + } catch (final TransformerException e) { + Daemon.writeError(httpExchange, 500, e.getMessage()); + log.error("Fehler beim Erzeugen der Status-Information", e); + } + } +} diff --git a/src/main/java/de/kosit/validationtool/daemon/HttpServerHandler.java b/src/main/java/de/kosit/validationtool/daemon/HttpServerHandler.java new file mode 100644 index 0000000..126f7e4 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/daemon/HttpServerHandler.java @@ -0,0 +1,61 @@ +package de.kosit.validationtool.daemon; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicLong; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.api.Check; +import de.kosit.validationtool.api.InputFactory; +import de.kosit.validationtool.impl.input.SourceInput; + +/** + * Wir benötigen einen Handler, der zur Verarbeitung von HTTP-Anforderungen aufgerufen wird um hier die Verarbeitung des + * POST Request zu realisieren. + */ +@Slf4j +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(); + + if (inputStream.available() > 0) { + final SourceInput serverInput = (SourceInput) InputFactory.read(inputStream, "Prüfling" + counter.incrementAndGet()); + Daemon.writeOutputstreamArray(httpExchange, this.implemenation.check(serverInput)); + } else { + Daemon.writeError(httpExchange, 400, "XML-Inhalt erforderlich!"); + } + + } else { + Daemon.writeError(httpExchange, 405, "Es ist nur die POST-Methode erlaubt!"); + } + } catch (final Exception e) { + Daemon.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); + } + } + +} diff --git a/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java b/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java index 50d96d6..46ff31f 100644 --- a/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java +++ b/src/main/java/de/kosit/validationtool/impl/ClassPathResourceResolver.java @@ -34,6 +34,8 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import de.kosit.validationtool.impl.xml.RelativeUriResolver; + /** * {@link LSResourceResolver} der objekte relativ zu einem Basis-Pfad aus dem Classpath der Anwendung laden kann. * diff --git a/src/main/java/de/kosit/validationtool/impl/ContentRepository.java b/src/main/java/de/kosit/validationtool/impl/ContentRepository.java index 4e56e4a..139e776 100644 --- a/src/main/java/de/kosit/validationtool/impl/ContentRepository.java +++ b/src/main/java/de/kosit/validationtool/impl/ContentRepository.java @@ -24,11 +24,13 @@ 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; @@ -40,12 +42,16 @@ import org.xml.sax.SAXException; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.Setter; 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; @@ -70,9 +76,16 @@ public class ContentRepository { private final URI repository; - private final ResolvingMode mode = ResolvingMode.STRICT_RELATIVE; + private final URIResolver resolver; - private Source resolve(final URL resource) { + @Setter + private SchemaFactory schemaFactory; + + @Setter + @Getter + private ResolvingConfigurationStrategy resolvingConfigurationStrategy; + + private static Source resolve(final URL resource) { try { return new StreamSource(resource.openStream(), resource.toURI().getRawPath()); } catch (final IOException | URISyntaxException e) { @@ -82,7 +95,7 @@ public class ContentRepository { 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) { @@ -106,9 +119,13 @@ public class ContentRepository { final CollectingErrorEventHandler listener = new CollectingErrorEventHandler(); try { xsltCompiler.setErrorListener(listener); - xsltCompiler.setURIResolver(createResolver()); + final URIResolver resolver = getResolver(); + if (resolver != null) { + // otherwise use default resolver + xsltCompiler.setURIResolver(resolver); + } - 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); @@ -130,6 +147,10 @@ public class ContentRepository { return createSchema(url, null); } + 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); @@ -150,11 +171,11 @@ public class ContentRepository { * @return ReportInput-Schema */ public Schema getReportInputSchema() { - if (reportInputSchema == null) { + if (this.reportInputSchema == null) { final Source source = resolve(ContentRepository.class.getResource("/xsd/createReportInput.xsd")); - reportInputSchema = createSchema(new Source[] { source }, new ClassPathResourceResolver("/xsd")); + this.reportInputSchema = createSchema(new Source[] { source }, new ClassPathResourceResolver("/xsd")); } - return reportInputSchema; + return this.reportInputSchema; } /** @@ -164,7 +185,7 @@ public class ContentRepository { * @return das Schema */ public Schema createSchema(final Collection uris) { - return createSchema(uris.stream().map(s -> resolve(URI.create(s))).toArray(Source[]::new)); + return createSchema(uris.stream().map(s -> resolveInRepository(URI.create(s))).toArray(Source[]::new)); } /** @@ -182,9 +203,19 @@ public class ContentRepository { return schema; } - private Source resolve(final URI source) { - final URI resolved = this.mode.resolve(source, this.repository); - return new StreamSource(resolved.toASCIIString()); + 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); + } } /** @@ -212,8 +243,8 @@ public class ContentRepository { * * @return ein neuer Resolver */ - public URIResolver createResolver() { - return this.mode.createResolver(this.repository); + public URIResolver getResolver() { + return this.resolver; } /** @@ -223,6 +254,10 @@ public class ContentRepository { */ 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); } @@ -238,4 +273,13 @@ public class ContentRepository { .collect(Collectors.toMap(NamespaceType::getPrefix, NamespaceType::getValue)); return createXPath(s.getAcceptMatch(), namespaces); } + + public List createSchematronTransformations(final ScenarioType s) { + return s.getValidateWithSchematron() != null && s.getValidateWithSchematron().isEmpty() ? Collections.emptyList() + : s.getValidateWithSchematron().stream().map(this::createSchematronTransformation).collect(Collectors.toList()); + } + + public Transformation createSchematronTransformation(final ValidateWithSchematron validateWithSchematron) { + return createTransformation(validateWithSchematron.getResource()); + } } diff --git a/src/main/java/de/kosit/validationtool/impl/ConversionService.java b/src/main/java/de/kosit/validationtool/impl/ConversionService.java index 2bc4185..0fc6cd0 100644 --- a/src/main/java/de/kosit/validationtool/impl/ConversionService.java +++ b/src/main/java/de/kosit/validationtool/impl/ConversionService.java @@ -36,7 +36,6 @@ import javax.xml.bind.Unmarshaller; import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.annotation.XmlRegistry; import javax.xml.namespace.QName; -import javax.xml.parsers.DocumentBuilder; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; @@ -47,7 +46,6 @@ import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import org.apache.commons.lang3.StringUtils; -import org.w3c.dom.Document; import lombok.extern.slf4j.Slf4j; @@ -86,6 +84,15 @@ public class ConversionService { // context setup private JAXBContext jaxbContext; + public JAXBContext + + getJaxbContext() { + if (this.jaxbContext == null) { + initialize(); + } + return this.jaxbContext; + } + private static QName createQName(final T model) { return new QName(model.getClass().getSimpleName().toLowerCase()); } @@ -141,13 +148,6 @@ public class ConversionService { } } - private JAXBContext getJaxbContext() { - if (this.jaxbContext == null) { - initialize(); - } - return this.jaxbContext; - } - /** * Unmarshalls a specifc xml model into a defined java object. * @@ -233,24 +233,8 @@ public class ConversionService { } } - public Document writeDocument(final T input) { - if (input == null) { - throw new ConversionExeption("Can not serialize null"); - } - final DocumentBuilder builder = ObjectFactory.createDocumentBuilder(false); - final Document document = builder.newDocument(); - // Marshal the Object to a Document - Marshaller marshaller = null; - try { - marshaller = getJaxbContext().createMarshaller(); - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - marshaller.marshal(input, document); - return document; - } catch (final JAXBException e) { - throw new ConversionExeption(String.format("Error serializing Object %s to document", input.getClass().getName()), e); - } - } + public T readDocument(final Source source, final Class type) { try { diff --git a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java index 2d9fa78..b337854 100644 --- a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java +++ b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java @@ -21,9 +21,15 @@ package de.kosit.validationtool.impl; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; import java.util.List; import java.util.stream.Collectors; +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -35,6 +41,7 @@ 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 +63,11 @@ import net.sf.saxon.s9api.Processor; @Slf4j public class DefaultCheck implements Check { - @Getter - private final ScenarioRepository repository; - - @Getter - private final ContentRepository contentRepository; - @Getter private final ConversionService conversionService; + private final Configuration configuration; + @Getter private final List checkSteps; @@ -74,21 +77,19 @@ public class DefaultCheck implements Check { * @param configuration die Konfiguration */ public DefaultCheck(final Configuration configuration) { - final Processor processor = ObjectFactory.createProcessor(); + this.configuration = configuration; + final ContentRepository content = configuration.getContentRepository(); + final Processor processor = content.getProcessor(); this.conversionService = new ConversionService(); - this.repository = new ScenarioRepository(configuration); - // TODO get rid of it - this.contentRepository = configuration.getContentRepository(); 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, configuration.getContentRepository().getReportInputSchema())); - this.checkSteps.add(new CreateReportAction(processor, this.conversionService, 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()); } @@ -97,11 +98,22 @@ 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; } + private 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); + } + } + @Override public Result checkInput(final Input input) { final CheckAction.Bag t = new CheckAction.Bag(input, createReport()); @@ -124,7 +136,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) { diff --git a/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java b/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java index 59bdc38..bde98a1 100644 --- a/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java +++ b/src/main/java/de/kosit/validationtool/impl/HtmlExtractor.java @@ -6,13 +6,16 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Element; import lombok.RequiredArgsConstructor; import net.sf.saxon.dom.NodeOverNodeInfo; +import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.Serializer; +import net.sf.saxon.s9api.XPathCompiler; import net.sf.saxon.s9api.XPathExecutable; import net.sf.saxon.s9api.XPathSelector; import net.sf.saxon.s9api.XdmItem; @@ -26,38 +29,51 @@ import net.sf.saxon.s9api.XdmNode; @RequiredArgsConstructor public class HtmlExtractor { - private final ContentRepository repository; + private final Processor processor; private XPathExecutable executable; - public List extract(XdmNode xdmSource) { + public List extract(final XdmNode xdmSource) { try { final XPathSelector selector = getSelector(); selector.setContextItem(xdmSource); - return selector.stream().map(this::castToNode).collect(Collectors.toList()); + return selector.stream().map(HtmlExtractor::castToNode).collect(Collectors.toList()); - } catch (SaxonApiException e) { + } catch (final SaxonApiException e) { throw new IllegalStateException("Can not extract html content", e); } } - private XdmNode castToNode(final XdmItem xdmItem) { + private static XdmNode castToNode(final XdmItem xdmItem) { return (XdmNode) xdmItem; } private XPathSelector getSelector() { - if (executable == null) { - Map ns = new HashMap<>(); + if (this.executable == null) { + final Map ns = new HashMap<>(); ns.put("html", "http://www.w3.org/1999/xhtml"); - executable = repository.createXPath("//html:html", ns); + this.executable = createXPath("//html:html", ns); } - return executable.load(); + return this.executable.load(); } - private static String convertToString(final XdmNode element) { + private XPathExecutable createXPath(final String expression, final Map namespaces) { + try { + final XPathCompiler compiler = this.processor.newXPathCompiler(); + if (namespaces != null) { + namespaces.forEach(compiler::declareNamespace); + } + return compiler.compile(expression); + } catch (final SaxonApiException e) { + throw new IllegalStateException(String.format("Can not compile xpath match expression '%s'", + StringUtils.isNotBlank(expression) ? expression : "EMPTY EXPRESSION"), e); + } + } + + private String convertToString(final XdmNode element) { try { final StringWriter writer = new StringWriter(); - final Serializer serializer = ObjectFactory.createProcessor().newSerializer(writer); + final Serializer serializer = this.processor.newSerializer(writer); serializer.serializeNode(element); return writer.toString(); } catch (final SaxonApiException e) { @@ -72,7 +88,7 @@ public class HtmlExtractor { * @return HTML-Fragment als String */ public List extractAsString(final XdmNode node) { - return extract(node).stream().map(HtmlExtractor::convertToString).collect(Collectors.toList()); + return extract(node).stream().map(this::convertToString).collect(Collectors.toList()); } public List extractAsElement(final XdmNode node) { diff --git a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java b/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java index 76b66ee..8c96eae 100644 --- a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java +++ b/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java @@ -24,13 +24,8 @@ 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; @@ -40,13 +35,6 @@ 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; @@ -70,6 +58,7 @@ import net.sf.saxon.trans.XPathException; * @author Andreas Penski */ @Slf4j +@Deprecated public class ObjectFactory { private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver { @@ -116,7 +105,7 @@ public class ObjectFactory { } } - private ObjectFactory() { + ObjectFactory() { // hide, it's a factory } @@ -154,13 +143,17 @@ public class ObjectFactory { public static Transformer createTransformer(final boolean prettyPrint) { Transformer transformer = null; try { - transformer = TransformerFactory.newInstance().newTransformer(); + final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); // Compliant + transformer = transformerFactory.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) { @@ -168,21 +161,6 @@ public class ObjectFactory { } } - /** - * 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 { @@ -223,38 +201,6 @@ public class ObjectFactory { return processor; } - /** - * Erzeugt einen Validier für das angegebenen Schema. - * - * @param schema das Schema mit dem validiert werden soll - * @return einen vorkonfigurierten Validator - */ - public static Validator createValidator(final Schema schema) { - final Validator validator = schema.newValidator(); - try { - validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - } catch (final SAXNotRecognizedException | SAXNotSupportedException e) { - log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used."); - log.debug(e.getMessage(), e); - } - try { - validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); - } catch (final SAXNotRecognizedException | SAXNotSupportedException e) { - log.warn("Can not disable external DTD access. Maybe an unsupported JAXP implementation is used."); - log.debug(e.getMessage(), e); - } - return validator; - } - public static SchemaFactory createSchemaFactory() { - final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - try { - sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file"); - } catch (final SAXException e) { - log.warn("Can not disable external DTD access, maybe an unsupported JAXP implementation is used", e); - } - return sf; - } } diff --git a/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java b/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java index 618c74a..885674b 100644 --- a/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java +++ b/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java @@ -1,45 +1,34 @@ package de.kosit.validationtool.impl; -import java.net.URI; +import lombok.Getter; +import lombok.RequiredArgsConstructor; -import javax.xml.transform.URIResolver; - -import org.apache.commons.lang3.NotImplementedException; +import de.kosit.validationtool.api.ResolvingConfigurationStrategy; +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 { + STRICT_RELATIVE(new StrictRelativeResolvingStrategy()) { - @Override - public URI resolve(final URI source, final URI repository) { - return RelativeUriResolver.resolve(source, repository); - } - - @Override - public URIResolver createResolver(final URI repository) { - return new RelativeUriResolver(repository); - } }, - STRICT_LOCAL, + STRICT_LOCAL(new StrictLocalResolvingStrategy()), - JDK_SUPPORTED, + JDK_SUPPORTED(null), - CUSTOM; + CUSTOM(null); - public URI resolve(final URI source, final URI repository) { - throw new NotImplementedException("Not yet implemented"); - } + @Getter + private final ResolvingConfigurationStrategy strategy; - public URIResolver createResolver(final URI repository) { - throw new NotImplementedException("Not yet implemented"); - } } diff --git a/src/main/java/de/kosit/validationtool/impl/Scenario.java b/src/main/java/de/kosit/validationtool/impl/Scenario.java index 159fa87..0e0ef5c 100644 --- a/src/main/java/de/kosit/validationtool/impl/Scenario.java +++ b/src/main/java/de/kosit/validationtool/impl/Scenario.java @@ -22,7 +22,6 @@ import net.sf.saxon.s9api.XsltExecutable; * @author Andreas Penski */ @RequiredArgsConstructor - @Setter @Getter public class Scenario { diff --git a/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java b/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java index de5b7de..a8ddc26 100644 --- a/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java +++ b/src/main/java/de/kosit/validationtool/impl/ScenarioRepository.java @@ -45,7 +45,6 @@ public class ScenarioRepository { public ScenarioRepository(final Configuration configuration) { this.configuration = configuration; - configuration.build(); log.info("Loaded scenarios for {} by {} from {}. The following scenarios are available:\n\n{}", configuration.getName(), configuration.getAuthor(), configuration.getDate(), summarizeScenarios()); } diff --git a/src/main/java/de/kosit/validationtool/impl/CreateDocumentIdentificationAction.java b/src/main/java/de/kosit/validationtool/impl/tasks/CreateDocumentIdentificationAction.java similarity index 82% rename from src/main/java/de/kosit/validationtool/impl/CreateDocumentIdentificationAction.java rename to src/main/java/de/kosit/validationtool/impl/tasks/CreateDocumentIdentificationAction.java index d0b87f0..3608b5c 100644 --- a/src/main/java/de/kosit/validationtool/impl/CreateDocumentIdentificationAction.java +++ b/src/main/java/de/kosit/validationtool/impl/tasks/CreateDocumentIdentificationAction.java @@ -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) { diff --git a/src/main/java/de/kosit/validationtool/impl/tasks/CreateReportAction.java b/src/main/java/de/kosit/validationtool/impl/tasks/CreateReportAction.java index cfd9ad2..444aeec 100644 --- a/src/main/java/de/kosit/validationtool/impl/tasks/CreateReportAction.java +++ b/src/main/java/de/kosit/validationtool/impl/tasks/CreateReportAction.java @@ -19,23 +19,31 @@ package de.kosit.validationtool.impl.tasks; +import java.io.IOException; import java.util.Collection; import java.util.stream.Collectors; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.util.JAXBSource; import javax.xml.transform.URIResolver; -import javax.xml.transform.dom.DOMSource; -import org.w3c.dom.Document; +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.Scenario; import de.kosit.validationtool.model.reportInput.XMLSyntaxError; @@ -58,12 +66,108 @@ import net.sf.saxon.s9api.XsltTransformer; @RequiredArgsConstructor 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 final XMLReader delegate; + + public ReaderWrapper(final XMLReader xmlReader) { + this.delegate = xmlReader; + } + + @Override + public boolean getFeature(final String name) throws SAXNotRecognizedException, SAXNotSupportedException { + if (name.equals("http://xml.org/sax/features/namespaces")) { + return true; + } else if (name.equals("http://xml.org/sax/features/namespace-prefixes")) { + 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("http://xml.org/sax/features/namespaces") && !value) { + throw new SAXNotRecognizedException(name); + } + if (name.equals("http://xml.org/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 ContentRepository contentRepository; + private final URIResolver resolver; private static XsltExecutable loadFromScenario(final Scenario object) { return object.getReportTransformation().getExecutable(); @@ -77,14 +181,17 @@ 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 URIResolver resolver = this.contentRepository.createResolver(); transformer.setMessageListener(e); - transformer.setURIResolver(resolver); + transformer.setURIResolver(this.resolver); // transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver); if (parsedDocument != null) { transformer.setParameter(new QName("input-document"), parsedDocument); @@ -94,13 +201,13 @@ public class CreateReportAction implements CheckAction { transformer.transform(); results.setReport(destination.getXdmNode()); - } catch (final SaxonApiException | SAXException e) { + } catch (final SaxonApiException | SAXException | JAXBException e) { throw new IllegalStateException("Can not create final report", e); } } - private static XdmNode createErrorInformation(final Collection errors) throws SaxonApiException, SAXException { - final BuildingContentHandler contentHandler = ObjectFactory.createProcessor().newDocumentBuilder().newBuildingContentHandler(); + private XdmNode createErrorInformation(final Collection 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()); diff --git a/src/main/java/de/kosit/validationtool/impl/tasks/DocumentParseAction.java b/src/main/java/de/kosit/validationtool/impl/tasks/DocumentParseAction.java index 0a166f5..c5559a7 100644 --- a/src/main/java/de/kosit/validationtool/impl/tasks/DocumentParseAction.java +++ b/src/main/java/de/kosit/validationtool/impl/tasks/DocumentParseAction.java @@ -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 parseDocument(final Input content) { + public Result parseDocument(final Input content) { if (content == null) { throw new IllegalArgumentException("Input may not be null"); } Result 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()); diff --git a/src/main/java/de/kosit/validationtool/impl/tasks/SchemaValidationAction.java b/src/main/java/de/kosit/validationtool/impl/tasks/SchemaValidationAction.java index 89ddd7c..a2396c8 100644 --- a/src/main/java/de/kosit/validationtool/impl/tasks/SchemaValidationAction.java +++ b/src/main/java/de/kosit/validationtool/impl/tasks/SchemaValidationAction.java @@ -36,12 +36,13 @@ 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; @@ -49,6 +50,7 @@ import de.kosit.validationtool.model.reportInput.CreateReportInput; import de.kosit.validationtool.model.reportInput.ValidationResultsXmlSchema; import de.kosit.validationtool.model.reportInput.XMLSyntaxError; +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,6 +138,10 @@ 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; @@ -137,7 +151,7 @@ public class SchemaValidationAction implements CheckAction { final CollectingErrorEventHandler errorHandler = new CollectingErrorEventHandler(); try ( final SourceProvider validateInput = resolveSource(results) ) { - final Validator validator = ObjectFactory.createValidator(scenario.getSchema()); + final Validator validator = this.factory.createValidator(scenario.getSchema()); validator.setErrorHandler(errorHandler); validator.validate(validateInput.getSource()); return new Result<>(!errorHandler.hasErrors(), errorHandler.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; diff --git a/src/main/java/de/kosit/validationtool/impl/tasks/SchematronValidationAction.java b/src/main/java/de/kosit/validationtool/impl/tasks/SchematronValidationAction.java index 377e195..8f52cfe 100644 --- a/src/main/java/de/kosit/validationtool/impl/tasks/SchematronValidationAction.java +++ b/src/main/java/de/kosit/validationtool/impl/tasks/SchematronValidationAction.java @@ -26,21 +26,19 @@ 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.Scenario; import de.kosit.validationtool.model.reportInput.CreateReportInput; import de.kosit.validationtool.model.reportInput.ValidationResultsSchematron; -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; @@ -53,7 +51,7 @@ import net.sf.saxon.s9api.XsltTransformer; @Slf4j public class SchematronValidationAction implements CheckAction { - private final ContentRepository repository; + private final URIResolver resolver; private final ConversionService conversionService; @@ -67,18 +65,19 @@ public class SchematronValidationAction implements CheckAction { try { final XsltTransformer transformer = validation.getExecutable().load(); // resolving nur relative zum Repository - final URIResolver 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) { diff --git a/src/main/java/de/kosit/validationtool/impl/xml/BaseResolvingStrategy.java b/src/main/java/de/kosit/validationtool/impl/xml/BaseResolvingStrategy.java new file mode 100644 index 0000000..fe8c1e3 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/impl/xml/BaseResolvingStrategy.java @@ -0,0 +1,108 @@ +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.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; + +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.api.ResolvingConfigurationStrategy; + +/** + * @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"; + + 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 SAXNotRecognizedException | SAXNotSupportedException 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 SAXNotRecognizedException, SAXNotSupportedException; + } +} diff --git a/src/main/java/de/kosit/validationtool/impl/RelativeUriResolver.java b/src/main/java/de/kosit/validationtool/impl/xml/RelativeUriResolver.java similarity index 89% rename from src/main/java/de/kosit/validationtool/impl/RelativeUriResolver.java rename to src/main/java/de/kosit/validationtool/impl/xml/RelativeUriResolver.java index e57a05e..1f1d4c2 100644 --- a/src/main/java/de/kosit/validationtool/impl/RelativeUriResolver.java +++ b/src/main/java/de/kosit/validationtool/impl/xml/RelativeUriResolver.java @@ -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)); } } @@ -87,7 +86,7 @@ public class RelativeUriResolver implements URIResolver, UnparsedTextURIResolver return r.startsWith(base); } - @Override + public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException { if (isUnderBaseUri(absoluteURI)) { try { diff --git a/src/main/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingStrategy.java b/src/main/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingStrategy.java new file mode 100644 index 0000000..35c19b1 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingStrategy.java @@ -0,0 +1,51 @@ +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; + +import net.sf.saxon.s9api.Processor; + +/** + * + * + * @author Andreas Penski + */ +@Slf4j +public class StrictLocalResolvingStrategy extends StrictRelativeResolvingStrategy { + + /** + * e.g. don't allow any scheme + */ + + @Override + public SchemaFactory createSchemaFactory() { + final SchemaFactory schemaFactory = super.createSchemaFactory(); + allowExternalSchema(schemaFactory, "file", "jar"); + return schemaFactory; + } + + @Override + public Processor createProcessor() { + return super.createProcessor(); + } + + @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", "jar"); + return validator; + } + +} diff --git a/src/main/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingStrategy.java b/src/main/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingStrategy.java new file mode 100644 index 0000000..66824d6 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingStrategy.java @@ -0,0 +1,127 @@ +package de.kosit.validationtool.impl.xml; + +import java.io.Reader; +import java.io.UnsupportedEncodingException; +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 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 + public 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); + return processor; + } + + 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); + } + } + + @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; + + } + +} diff --git a/src/test/java/de/kosit/validationtool/cmd/CheckAssertionActionTest.java b/src/test/java/de/kosit/validationtool/cmd/CheckAssertionActionTest.java index 6e24b3c..a170d84 100644 --- a/src/test/java/de/kosit/validationtool/cmd/CheckAssertionActionTest.java +++ b/src/test/java/de/kosit/validationtool/cmd/CheckAssertionActionTest.java @@ -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"); } } diff --git a/src/test/java/de/kosit/validationtool/cmd/CommandlineApplicationTest.java b/src/test/java/de/kosit/validationtool/cmd/CommandlineApplicationTest.java index 1207a1a..c692172 100644 --- a/src/test/java/de/kosit/validationtool/cmd/CommandlineApplicationTest.java +++ b/src/test/java/de/kosit/validationtool/cmd/CommandlineApplicationTest.java @@ -105,12 +105,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 +120,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 +130,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 +138,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 +147,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 +157,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 +167,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(""); @@ -174,7 +177,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 +186,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); diff --git a/src/test/java/de/kosit/validationtool/cmd/ExtractHtmlActionTest.java b/src/test/java/de/kosit/validationtool/cmd/ExtractHtmlActionTest.java index 75c0449..b392d27 100644 --- a/src/test/java/de/kosit/validationtool/cmd/ExtractHtmlActionTest.java +++ b/src/test/java/de/kosit/validationtool/cmd/ExtractHtmlActionTest.java @@ -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 diff --git a/src/test/java/de/kosit/validationtool/cmd/PrintReportActionTest.java b/src/test/java/de/kosit/validationtool/cmd/PrintReportActionTest.java index e1b16f4..303eff1 100644 --- a/src/test/java/de/kosit/validationtool/cmd/PrintReportActionTest.java +++ b/src/test/java/de/kosit/validationtool/cmd/PrintReportActionTest.java @@ -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 diff --git a/src/test/java/de/kosit/validationtool/cmd/SerializeReportActionTest.java b/src/test/java/de/kosit/validationtool/cmd/SerializeReportActionTest.java index d0373f2..6807665 100644 --- a/src/test/java/de/kosit/validationtool/cmd/SerializeReportActionTest.java +++ b/src/test/java/de/kosit/validationtool/cmd/SerializeReportActionTest.java @@ -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 diff --git a/src/test/java/de/kosit/validationtool/config/SimpleConfigTest.java b/src/test/java/de/kosit/validationtool/config/SimpleConfigTest.java new file mode 100644 index 0000000..1fadac4 --- /dev/null +++ b/src/test/java/de/kosit/validationtool/config/SimpleConfigTest.java @@ -0,0 +1,49 @@ +package de.kosit.validationtool.config; + +import static de.kosit.validationtool.config.ConfigurationBuilder.fallback; +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 static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; + +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 = + Configuration.create().name("Simple-API") + .with(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") +// .description("awesome api") + ) + .with(fallback().name("default").source("report.xsl")) + + .resolvingMode(ResolvingMode.STRICT_RELATIVE) + .useRepository(Simple.REPOSITORY_URI).build(); + //@formatter:on + final DefaultCheck check = new DefaultCheck(config); + final Result result = check.checkInput(InputFactory.read(Simple.SIMPLE_VALID)); + assertThat(result).isNotNull(); + } +} diff --git a/src/test/java/de/kosit/validationtool/impl/ContentRepositoryTest.java b/src/test/java/de/kosit/validationtool/impl/ContentRepositoryTest.java index 4eb20cb..9a54bee 100644 --- a/src/test/java/de/kosit/validationtool/impl/ContentRepositoryTest.java +++ b/src/test/java/de/kosit/validationtool/impl/ContentRepositoryTest.java @@ -55,7 +55,7 @@ public class ContentRepositoryTest { @Before public void setup() { - this.repository = new ContentRepository(ObjectFactory.createProcessor(), Simple.REPOSITORY); + this.repository = Simple.createContentRepository(); } @Test @@ -114,7 +114,7 @@ public class ContentRepositoryTest { @Test public void loadFromJar() throws URISyntaxException { - this.repository = new ContentRepository(ObjectFactory.createProcessor(), Helper.JAR_REPOSITORY.toURI()); + this.repository = new ContentRepository(TestObjectFactory.createProcessor(), Helper.JAR_REPOSITORY.toURI(), null); final XsltExecutable xsltExecutable = this.repository.loadXsltScript(URI.create("resources/eRechnung/report.xsl")); assertThat(xsltExecutable).isNotNull(); } @@ -136,7 +136,7 @@ public class ContentRepositoryTest { // @Test // public void loadFromJar() throws URISyntaxException { - // this.content = new ContentRepository(ObjectFactory.createProcessor(), Helper.JAR_REPOSITORY.toURI()); + // 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()); diff --git a/src/test/java/de/kosit/validationtool/impl/ConversionServiceTest.java b/src/test/java/de/kosit/validationtool/impl/ConversionServiceTest.java index 60500c5..2bbef72 100644 --- a/src/test/java/de/kosit/validationtool/impl/ConversionServiceTest.java +++ b/src/test/java/de/kosit/validationtool/impl/ConversionServiceTest.java @@ -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 diff --git a/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java b/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java index 0792d48..1b86c24 100644 --- a/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java +++ b/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java @@ -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); } diff --git a/src/test/java/de/kosit/validationtool/impl/DocumentParserTest.java b/src/test/java/de/kosit/validationtool/impl/DocumentParserTest.java index d914d9e..7a39c60 100644 --- a/src/test/java/de/kosit/validationtool/impl/DocumentParserTest.java +++ b/src/test/java/de/kosit/validationtool/impl/DocumentParserTest.java @@ -28,7 +28,6 @@ import org.junit.rules.ExpectedException; 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.reportInput.XMLSyntaxError; import net.sf.saxon.s9api.XdmNode; @@ -45,7 +44,7 @@ public class DocumentParserTest { @Test public void testSimple() { - final Result result = DocumentParseAction.parseDocument(read(Simple.SIMPLE_VALID)); + final Result result = Helper.parseDocument(read(Simple.SIMPLE_VALID)); assertThat(result).isNotNull(); assertThat(result.getObject()).isNotNull(); assertThat(result.getErrors()).isEmpty(); @@ -54,7 +53,7 @@ public class DocumentParserTest { @Test public void testIllformed() { - final Result result = DocumentParseAction.parseDocument(read(Simple.NOT_WELLFORMED)); + final Result result = Helper.parseDocument(read(Simple.NOT_WELLFORMED)); assertThat(result).isNotNull(); assertThat(result.getErrors()).isNotEmpty(); assertThat(result.getObject()).isNull(); @@ -64,7 +63,7 @@ public class DocumentParserTest { @Test public void testNullInput() { this.exception.expect(IllegalArgumentException.class); - DocumentParseAction.parseDocument(null); + Helper.parseDocument(null); } diff --git a/src/test/java/de/kosit/validationtool/impl/Helper.java b/src/test/java/de/kosit/validationtool/impl/Helper.java index 1d50fc4..ad78c94 100644 --- a/src/test/java/de/kosit/validationtool/impl/Helper.java +++ b/src/test/java/de/kosit/validationtool/impl/Helper.java @@ -36,6 +36,12 @@ 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.SaxonApiException; import net.sf.saxon.s9api.XdmNode; @@ -61,7 +67,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,8 +79,16 @@ 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 ContentRepository createContentRepository() { + final ResolvingConfigurationStrategy strategy = ResolvingMode.STRICT_RELATIVE.getStrategy(); + final ContentRepository rep = new ContentRepository(TestObjectFactory.createProcessor(), Simple.REPOSITORY_URI, + strategy.createResolver(Simple.REPOSITORY_URI)); + rep.setResolvingConfigurationStrategy(strategy); + rep.setSchemaFactory(strategy.createSchemaFactory()); + return rep; + } public static URI getSchemaLocation() { return ROOT.resolve("repository/simple.xsd"); } @@ -118,7 +132,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); @@ -140,12 +154,12 @@ public class Helper { * @return ein {@link ContentRepository} */ public static ContentRepository loadTestRepository() { - return new ContentRepository(ObjectFactory.createProcessor(), new File("src/test/resources/examples/repository").toURI()); + return new ContentRepository(TestObjectFactory.createProcessor(), new File("src/test/resources/examples/repository").toURI(), null); } public static String serialize(final Document doc) { try ( final StringWriter writer = new StringWriter() ) { - final Transformer transformer = ObjectFactory.createTransformer(true); + final Transformer transformer = TestObjectFactory.createTransformer(true); transformer.transform(new DOMSource(doc), new StreamResult(writer)); return writer.toString(); } catch (final IOException | TransformerException e) { @@ -157,4 +171,7 @@ public class Helper { return serialize((Document) NodeOverNodeInfo.wrap(node.getUnderlyingNode())); } + public static Result parseDocument(final Input input) { + return new DocumentParseAction(TestObjectFactory.createProcessor()).parseDocument(input); + } } diff --git a/src/test/java/de/kosit/validationtool/impl/RelativeUriResolverTest.java b/src/test/java/de/kosit/validationtool/impl/RelativeUriResolverTest.java index c7b83d6..2106f65 100644 --- a/src/test/java/de/kosit/validationtool/impl/RelativeUriResolverTest.java +++ b/src/test/java/de/kosit/validationtool/impl/RelativeUriResolverTest.java @@ -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 * @@ -63,13 +65,13 @@ public class RelativeUriResolverTest { @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()); } diff --git a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java b/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java index 0729446..1c79f51 100644 --- a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java +++ b/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java @@ -35,21 +35,19 @@ import org.w3c.dom.Document; import lombok.extern.slf4j.Slf4j; +import de.kosit.validationtool.api.InputFactory; import de.kosit.validationtool.impl.Helper.Simple; +import de.kosit.validationtool.impl.model.Result; +import de.kosit.validationtool.impl.xml.RelativeUriResolver; +import de.kosit.validationtool.model.reportInput.XMLSyntaxError; import net.sf.saxon.s9api.DOMDestination; import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.XdmNode; import net.sf.saxon.s9api.XsltCompiler; import net.sf.saxon.s9api.XsltExecutable; import net.sf.saxon.s9api.XsltTransformer; -import de.kosit.validationtool.api.InputFactory; -import de.kosit.validationtool.impl.model.Result; -import de.kosit.validationtool.impl.tasks.DocumentParseAction; -import de.kosit.validationtool.model.reportInput.XMLSyntaxError; - - -import net.sf.saxon.s9api.XdmNode; /** @@ -62,19 +60,19 @@ public class SaxonSecurityTest { @Test public void testEvilStylesheets() throws IOException { - final Processor p = ObjectFactory.createProcessor(); + final Processor p = TestObjectFactory.createProcessor(); for (int i = 1; i <= 5; i++) { try { final URL resource = SaxonSecurityTest.class.getResource(String.format("/evil/evil%s.xsl", i)); final XsltCompiler compiler = p.newXsltCompiler(); - final RelativeUriResolver resolver = new RelativeUriResolver(Simple.REPOSITORY); + final RelativeUriResolver resolver = new RelativeUriResolver(Simple.REPOSITORY_URI); compiler.setURIResolver(resolver); final XsltExecutable exetuable = compiler.compile(new StreamSource(resource.openStream())); final XsltTransformer transformer = exetuable.load(); - final Document document = ObjectFactory.createDocumentBuilder(false).newDocument(); + final Document document = TestObjectFactory.createDocumentBuilder(false).newDocument(); document.createElement("root"); - final Document result = ObjectFactory.createDocumentBuilder(false).newDocument(); - transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver); + final Document result = TestObjectFactory.createDocumentBuilder(false).newDocument(); + // transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver); transformer.setURIResolver(resolver); transformer.setSource(new DOMSource(document)); transformer.setDestination(new DOMDestination(result)); @@ -94,7 +92,7 @@ public class SaxonSecurityTest { @Test public void testXxe() { final URL resource = SaxonSecurityTest.class.getResource("/evil/xxe.xml"); - final Result result = DocumentParseAction.parseDocument(InputFactory.read(resource)); + final Result result = Helper.parseDocument(InputFactory.read(resource)); assertThat(result.isValid()).isFalse(); assertThat(result.getObject()).isNull(); assertThat(result.getErrors().stream().map(XMLSyntaxError::getMessage).collect(Collectors.joining())) diff --git a/src/test/java/de/kosit/validationtool/impl/ScenarioRepositoryTest.java b/src/test/java/de/kosit/validationtool/impl/ScenarioRepositoryTest.java index e450050..ceb8e85 100644 --- a/src/test/java/de/kosit/validationtool/impl/ScenarioRepositoryTest.java +++ b/src/test/java/de/kosit/validationtool/impl/ScenarioRepositoryTest.java @@ -27,6 +27,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.Rule; @@ -38,7 +39,6 @@ import lombok.Data; import de.kosit.validationtool.api.Configuration; 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 net.sf.saxon.s9api.Processor; @@ -70,10 +70,8 @@ public class ScenarioRepositoryTest { private ContentRepository contentRepository; - @Override - public void build() { - // nothing - } + private Map additionalParameters; + } @Rule @@ -137,11 +135,10 @@ public class ScenarioRepositoryTest { } private static XdmNode load(final URI uri) throws IOException { - final DocumentParseAction p = new DocumentParseAction(); - return DocumentParseAction.parseDocument(read(uri.toURL())).getObject(); + return Helper.parseDocument(read(uri.toURL())).getObject(); } private static XPathExecutable createXpath(final String expression) { - return new ContentRepository(ObjectFactory.createProcessor(), null).createXPath(expression, new HashMap<>()); + return new ContentRepository(TestObjectFactory.createProcessor(), null, null).createXPath(expression, new HashMap<>()); } } diff --git a/src/test/java/de/kosit/validationtool/impl/SimpleScenarioCheckTest.java b/src/test/java/de/kosit/validationtool/impl/SimpleScenarioCheckTest.java index 23b1c5f..0040d31 100644 --- a/src/test/java/de/kosit/validationtool/impl/SimpleScenarioCheckTest.java +++ b/src/test/java/de/kosit/validationtool/impl/SimpleScenarioCheckTest.java @@ -25,7 +25,7 @@ public class SimpleScenarioCheckTest { @Before public void setup() { final CheckConfiguration d = new CheckConfiguration(Simple.SCENARIOS); - d.setScenarioRepository(Simple.REPOSITORY); + d.setScenarioRepository(Simple.REPOSITORY_URI); this.implementation = new DefaultCheck(d); } diff --git a/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java b/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java new file mode 100644 index 0000000..1c47ad7 --- /dev/null +++ b/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java @@ -0,0 +1,7 @@ +package de.kosit.validationtool.impl; + +/** + * @author Andreas Penski + */ +public class TestObjectFactory extends ObjectFactory { +} diff --git a/src/test/java/de/kosit/validationtool/impl/VersioningTest.java b/src/test/java/de/kosit/validationtool/impl/VersioningTest.java index ff67943..38cb77a 100644 --- a/src/test/java/de/kosit/validationtool/impl/VersioningTest.java +++ b/src/test/java/de/kosit/validationtool/impl/VersioningTest.java @@ -56,7 +56,7 @@ public class VersioningTest { @Before public void setup() { - this.repository = new ContentRepository(ObjectFactory.createProcessor(), Simple.REPOSITORY); + this.repository = Simple.createContentRepository(); this.service = new ConversionService(); } diff --git a/src/test/java/de/kosit/validationtool/impl/tasks/ComputeAcceptanceActionTest.java b/src/test/java/de/kosit/validationtool/impl/tasks/ComputeAcceptanceActionTest.java index 0065997..541d1c9 100644 --- a/src/test/java/de/kosit/validationtool/impl/tasks/ComputeAcceptanceActionTest.java +++ b/src/test/java/de/kosit/validationtool/impl/tasks/ComputeAcceptanceActionTest.java @@ -10,7 +10,7 @@ import org.junit.Test; import de.kosit.validationtool.api.AcceptRecommendation; import de.kosit.validationtool.impl.ContentRepository; -import de.kosit.validationtool.impl.ObjectFactory; +import de.kosit.validationtool.impl.TestObjectFactory; import de.kosit.validationtool.impl.tasks.CheckAction.Bag; import net.sf.saxon.s9api.XPathExecutable; @@ -22,6 +22,7 @@ import net.sf.saxon.s9api.XPathExecutable; */ public class ComputeAcceptanceActionTest { + private static final String DOESNOT_EXIST = "count(//doesnotExist) = 0"; private final ComputeAcceptanceAction action = new ComputeAcceptanceAction(); @Test @@ -49,7 +50,7 @@ public class ComputeAcceptanceActionTest { @Test public void testValidAcceptMatch() { final Bag bag = createBag(true, true); - bag.getScenarioSelectionResult().getObject().setAcceptExecutable(createXpath("count(//doesnotExist) = 0")); + bag.getScenarioSelectionResult().getObject().setAcceptExecutable(createXpath(DOESNOT_EXIST)); this.action.check(bag); assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.ACCEPTABLE); } @@ -65,7 +66,7 @@ public class ComputeAcceptanceActionTest { @Test public void testAcceptMatchOverridesSchematronErrors() { final Bag bag = createBag(true, false); - bag.getScenarioSelectionResult().getObject().setAcceptExecutable(createXpath("count(//doesnotExist) = 0")); + bag.getScenarioSelectionResult().getObject().setAcceptExecutable(createXpath(DOESNOT_EXIST)); this.action.check(bag); assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.ACCEPTABLE); } @@ -73,7 +74,7 @@ public class ComputeAcceptanceActionTest { @Test public void testValidAcceptMatchOnSchemaFailed() { final Bag bag = createBag(false, true); - bag.getScenarioSelectionResult().getObject().setAcceptExecutable(createXpath("count(//doesnotExist) = 0")); + bag.getScenarioSelectionResult().getObject().setAcceptExecutable(createXpath(DOESNOT_EXIST)); this.action.check(bag); assertThat(bag.getAcceptStatus()).isEqualTo(AcceptRecommendation.REJECT); } @@ -104,6 +105,6 @@ public class ComputeAcceptanceActionTest { private static XPathExecutable createXpath(final String expression) { - return new ContentRepository(ObjectFactory.createProcessor(), null).createXPath(expression, new HashMap<>()); + return new ContentRepository(TestObjectFactory.createProcessor(), null, null).createXPath(expression, new HashMap<>()); } } diff --git a/src/test/java/de/kosit/validationtool/impl/tasks/SchemaValidatorActionTest.java b/src/test/java/de/kosit/validationtool/impl/tasks/SchemaValidatorActionTest.java index 354076e..bdc6248 100644 --- a/src/test/java/de/kosit/validationtool/impl/tasks/SchemaValidatorActionTest.java +++ b/src/test/java/de/kosit/validationtool/impl/tasks/SchemaValidatorActionTest.java @@ -43,12 +43,13 @@ import org.xml.sax.SAXException; import de.kosit.validationtool.api.Input; import de.kosit.validationtool.api.InputFactory; -import de.kosit.validationtool.impl.ContentRepository; +import de.kosit.validationtool.impl.Helper; import de.kosit.validationtool.impl.Helper.Simple; -import de.kosit.validationtool.impl.ObjectFactory; import de.kosit.validationtool.impl.Scenario; +import de.kosit.validationtool.impl.TestObjectFactory; import de.kosit.validationtool.impl.input.SourceInput; import de.kosit.validationtool.impl.tasks.CheckAction.Bag; +import de.kosit.validationtool.impl.xml.StrictRelativeResolvingStrategy; /** * Tests die {@link SchemaValidationAction}. @@ -63,7 +64,7 @@ public class SchemaValidatorActionTest { @Before public void setup() { - this.service = new SchemaValidationAction(); + this.service = new SchemaValidationAction(new StrictRelativeResolvingStrategy(), TestObjectFactory.createProcessor()); } @Test @@ -89,7 +90,7 @@ public class SchemaValidatorActionTest { @Test public void testSchemaReferences() { - final Schema reportInputSchema = new ContentRepository(ObjectFactory.createProcessor(), Simple.REPOSITORY).getReportInputSchema(); + final Schema reportInputSchema = Simple.createContentRepository().getReportInputSchema(); assertThat(reportInputSchema).isNotNull(); } @@ -98,7 +99,7 @@ public class SchemaValidatorActionTest { try ( final InputStream inputStream = Simple.SIMPLE_VALID.toURL().openStream() ) { final Bag bag = createBag(InputFactory.read(new StreamSource(inputStream))); // don't read the real inputstream here! - bag.setParserResult(DocumentParseAction.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); + bag.setParserResult(Helper.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); this.service.check(bag); assertThat(bag.getSchemaValidationResult()).isNotNull(); assertThat(bag.getSchemaValidationResult().isValid()).isTrue(); @@ -114,7 +115,7 @@ public class SchemaValidatorActionTest { this.service.setInMemoryLimit(5L); input.setLength(6L); - bag.setParserResult(DocumentParseAction.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); + bag.setParserResult(Helper.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); this.service.check(bag); assertThat(bag.getSchemaValidationResult()).isNotNull(); assertThat(bag.getSchemaValidationResult().isValid()).isTrue(); @@ -127,7 +128,7 @@ public class SchemaValidatorActionTest { final Reader reader = new InputStreamReader(inputStream) ) { final SourceInput input = (SourceInput) InputFactory.read(new StreamSource(reader)); final Bag bag = createBag(input); - bag.setParserResult(DocumentParseAction.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); + bag.setParserResult(Helper.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); this.service.check(bag); this.service.check(bag); assertThat(bag.getSchemaValidationResult()).isNotNull(); @@ -143,7 +144,7 @@ public class SchemaValidatorActionTest { final Bag bag = createBag(input); // set limit and length for serialization to 5 bytes this.service.setInMemoryLimit(5L); - bag.setParserResult(DocumentParseAction.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); + bag.setParserResult(Helper.parseDocument(InputFactory.read(Simple.SIMPLE_VALID.toURL()))); this.service.check(bag); this.service.check(bag); assertThat(bag.getSchemaValidationResult()).isNotNull(); diff --git a/src/test/java/de/kosit/validationtool/impl/tasks/SchematronValidationActionTest.java b/src/test/java/de/kosit/validationtool/impl/tasks/SchematronValidationActionTest.java index 80b7176..809a412 100644 --- a/src/test/java/de/kosit/validationtool/impl/tasks/SchematronValidationActionTest.java +++ b/src/test/java/de/kosit/validationtool/impl/tasks/SchematronValidationActionTest.java @@ -13,12 +13,11 @@ import org.junit.Before; import org.junit.Test; import de.kosit.validationtool.api.InputFactory; -import de.kosit.validationtool.impl.ContentRepository; import de.kosit.validationtool.impl.ConversionService; import de.kosit.validationtool.impl.Helper.Simple; -import de.kosit.validationtool.impl.ObjectFactory; import de.kosit.validationtool.impl.Scenario; import de.kosit.validationtool.impl.Scenario.Transformation; +import de.kosit.validationtool.impl.xml.RelativeUriResolver; import de.kosit.validationtool.model.scenarios.ResourceType; import net.sf.saxon.s9api.SaxonApiException; @@ -36,8 +35,7 @@ public class SchematronValidationActionTest { @Before public void setup() { - final ContentRepository repository = new ContentRepository(ObjectFactory.createProcessor(), Simple.REPOSITORY); - this.action = new SchematronValidationAction(repository, new ConversionService()); + this.action = new SchematronValidationAction(new RelativeUriResolver(Simple.REPOSITORY_URI), new ConversionService()); } @Test diff --git a/src/test/java/de/kosit/validationtool/impl/tasks/TestBagBuilder.java b/src/test/java/de/kosit/validationtool/impl/tasks/TestBagBuilder.java index 78a14f5..f7a5d34 100644 --- a/src/test/java/de/kosit/validationtool/impl/tasks/TestBagBuilder.java +++ b/src/test/java/de/kosit/validationtool/impl/tasks/TestBagBuilder.java @@ -16,8 +16,9 @@ import de.kosit.validationtool.api.Input; import de.kosit.validationtool.api.InputFactory; import de.kosit.validationtool.impl.ContentRepository; import de.kosit.validationtool.impl.Helper; -import de.kosit.validationtool.impl.ObjectFactory; +import de.kosit.validationtool.impl.ResolvingMode; import de.kosit.validationtool.impl.Scenario; +import de.kosit.validationtool.impl.TestObjectFactory; import de.kosit.validationtool.impl.model.Result; import de.kosit.validationtool.impl.tasks.CheckAction.Bag; import de.kosit.validationtool.model.reportInput.CreateReportInput; @@ -48,7 +49,7 @@ public class TestBagBuilder { public static Bag createBag(final Input input, final boolean parse, final CreateReportInput reportInput) { final Bag bag = new Bag(input, reportInput); if (parse) { - bag.setParserResult(DocumentParseAction.parseDocument(bag.getInput())); + bag.setParserResult(Helper.parseDocument(bag.getInput())); } bag.setScenarioSelectionResult(new Result<>(createScenario(Helper.Simple.getSchemaLocation()))); return bag; @@ -73,11 +74,13 @@ public class TestBagBuilder { } private static Schema createSchema(final URL toURL) { - return new ContentRepository(ObjectFactory.createProcessor(), null).createSchema(toURL); + final ContentRepository contentRepository = new ContentRepository(TestObjectFactory.createProcessor(), null, null); + contentRepository.setSchemaFactory(ResolvingMode.STRICT_RELATIVE.getStrategy().createSchemaFactory()); + return contentRepository.createSchema(toURL); } private static XdmNode createReport() { - return DocumentParseAction.parseDocument(InputFactory.read("xml".getBytes(), "someXml")).getObject(); + return Helper.parseDocument(InputFactory.read("xml".getBytes(), "someXml")).getObject(); } static Bag createBag(final boolean schemaValid, final boolean schematronValid) {