diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e45ded..c394c53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - date conversion when using [ConfigurationBuilder#date(Date)](https://github.com/itplr-kosit/validator/blob/d7beb1040418ae5cbeb9427532fd87482f55756c/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java#L109) +### Added +- read saxon XdmNode with InputFactory + +### Changed +- InputFactory has methods to read any java.xml.transform.Source as Input not only StreamSources +- InputFactory uses a generated UUID as name for SourceInput, if no "real" name can be derived + ## 1.3.1 ### Fixed - `getFailedAsserts()` and `isSchematronValid()` in [DefaultResult.java](https://github.com/itplr-kosit/validator/blob/master/src/main/java/de/kosit/validationtool/impl/DefaultResult.java) diff --git a/pom.xml b/pom.xml index 0c0651b..ca017d9 100644 --- a/pom.xml +++ b/pom.xml @@ -426,7 +426,7 @@ java - true + false true true @@ -487,10 +487,34 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + 8 + none + + + + attach-javadocs + + jar + + + + + + + + + + + https://github.com/itplr-kosit/validationtool.git scm:git:https://projekte.kosit.org/kosit/validator.git diff --git a/src/main/java/de/kosit/validationtool/api/InputFactory.java b/src/main/java/de/kosit/validationtool/api/InputFactory.java index b491cb2..c0035fd 100644 --- a/src/main/java/de/kosit/validationtool/api/InputFactory.java +++ b/src/main/java/de/kosit/validationtool/api/InputFactory.java @@ -29,6 +29,7 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.file.Path; +import java.util.UUID; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; @@ -42,6 +43,9 @@ import de.kosit.validationtool.impl.input.ByteArrayInput; import de.kosit.validationtool.impl.input.ResourceInput; import de.kosit.validationtool.impl.input.SourceInput; import de.kosit.validationtool.impl.input.StreamHelper; +import de.kosit.validationtool.impl.input.XdmNodeInput; + +import net.sf.saxon.s9api.XdmNode; /** * Service zum Einlesen des Test-Objekts in den Speicher. Beim Einlesen wird gleichzeitig eine Prüfsumme ermittelt und @@ -54,6 +58,11 @@ public class InputFactory { static final String DEFAULT_ALGORITH = "SHA-256"; + /** + * Pseudo hashcode algorithm name, which indicates, thate the hashcode of the {@link Input} is actually the name. + */ + static final String PSEUDO_NAME_ALGORITHM = "NAME"; + private static final String MESSAGE_OPEN_STREAM_ERROR = "Can not open stream from"; @Getter @@ -164,15 +173,18 @@ public class InputFactory { } /** - * Reads a test document from a {@link Source}.
- * Note: computing the hashcode is only supported for {@link StreamSource}. You can not directly use other {@link Source - * Soures}. You need to supply the hashcode for identification then. + * Reads a test document from a {@link Source}. Note: computing the hashcode is only supported for {@link StreamSource}. + * You can not directly use other {@link Source Soures}. You need to supply the hashcode for identification then. * * @param source source * @return an {@link Input} */ - public static Input read(final StreamSource source) { - return read(source, DEFAULT_ALGORITH); + public static Input read(final Source source) { + if (source instanceof StreamSource) { + return read(source, source.getSystemId(), DEFAULT_ALGORITH); + } + final String name = UUID.randomUUID().toString(); + return read(source, name, PSEUDO_NAME_ALGORITHM, name.getBytes()); } /** @@ -182,11 +194,16 @@ public class InputFactory { * Soures}. You need to supply the hashcode for identification then. * * @param source source - * @param digestAlgorithm the digest algorithm + * @param name the digest algorithm * @return an {@link Input} */ - public static Input read(final StreamSource source, final String digestAlgorithm) { - return read(source, digestAlgorithm, null); + public static Input read(final Source source, final String name) { + checkNotEmpty(name); + return read(source, name, PSEUDO_NAME_ALGORITHM, name.getBytes()); + } + + public static Input read(final Source source, final String name, final String digestAlgorithm) { + return read(source, name, digestAlgorithm, null); } /** @@ -198,7 +215,12 @@ public class InputFactory { */ public static Input read(final Source source, final String digestAlgorithm, final byte[] hashcode) { checkNull(source); - return new SourceInput(source, source.getSystemId(), digestAlgorithm, hashcode); + return read(source, source.getSystemId(), digestAlgorithm, hashcode); + } + + public static Input read(final Source source, final String name, final String digestAlgorithm, final byte[] hashcode) { + checkNull(source); + return new SourceInput(source, name, digestAlgorithm, hashcode); } /** @@ -281,4 +303,17 @@ public class InputFactory { return read(new StreamSource(inputStream, name), digestAlgorithm); } + /** + * Reads a saxon {@link XdmNode} with a given name. Hashcode identification is based on the name of the supplied input. + * Now real hashcode is computed. + * + * @param node the node to read + * @param name the name of the {@link Input} + * @return an {@link Input} to validate + */ + public static Input read(final XdmNode node, final String name) { + checkNull(node); + return new XdmNodeInput(node, name, PSEUDO_NAME_ALGORITHM, name.getBytes()); + } + } diff --git a/src/main/java/de/kosit/validationtool/api/Result.java b/src/main/java/de/kosit/validationtool/api/Result.java index a32adb2..91617ae 100644 --- a/src/main/java/de/kosit/validationtool/api/Result.java +++ b/src/main/java/de/kosit/validationtool/api/Result.java @@ -9,7 +9,7 @@ import org.w3c.dom.Document; import net.sf.saxon.s9api.XdmNode; /** - * API Rückgabe Objekt des Ergebnisses des Validierungsprozesses. + * API result object holding various information of the validation process results. * * @author Andreas Penski */ diff --git a/src/main/java/de/kosit/validationtool/daemon/Daemon.java b/src/main/java/de/kosit/validationtool/daemon/Daemon.java index fa0dcbe..24da017 100644 --- a/src/main/java/de/kosit/validationtool/daemon/Daemon.java +++ b/src/main/java/de/kosit/validationtool/daemon/Daemon.java @@ -1,5 +1,6 @@ package de.kosit.validationtool.daemon; +import static de.kosit.validationtool.impl.Printer.writeOut; import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import java.io.IOException; @@ -10,7 +11,6 @@ import java.util.concurrent.Executors; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -48,7 +48,7 @@ public class Daemon { * @param port the port to expose * @param threadCount the number of working threads */ - public Daemon(String hostname, int port, int threadCount) { + public Daemon(final String hostname, final int port, final int threadCount) { this.bindAddress = hostname; this.port = port; this.threadCount = threadCount; @@ -73,17 +73,20 @@ public class Daemon { server.setExecutor(createExecutor()); server.start(); log.info("Server {} started", server.getAddress()); + if (!log.isInfoEnabled()) { + writeOut("Server {0} started", server.getAddress()); + } } catch (final IOException e) { log.error("Error starting HttpServer for Valdidator: {}", e.getMessage(), e); } } - private HttpHandler createRootHandler(Configuration config) { - HttpHandler rootHandler; + private HttpHandler createRootHandler(final Configuration config) { + final HttpHandler rootHandler; final DefaultCheck check = new DefaultCheck(config); final CheckHandler checkHandler = new CheckHandler(check, config.getContentRepository().getProcessor()); - if (guiEnabled) { - GuiHandler gui = new GuiHandler(); + if (this.guiEnabled) { + final GuiHandler gui = new GuiHandler(); rootHandler = new RoutingHandler(checkHandler, gui); } else { rootHandler = checkHandler; diff --git a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java index fc6a9d9..81baa2c 100644 --- a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java +++ b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java @@ -62,6 +62,7 @@ public class DefaultCheck implements Check { @Getter private final ConversionService conversionService; + @Getter private final Configuration configuration; @Getter diff --git a/src/main/java/de/kosit/validationtool/impl/Printer.java b/src/main/java/de/kosit/validationtool/impl/Printer.java new file mode 100644 index 0000000..3a1eb51 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/impl/Printer.java @@ -0,0 +1,21 @@ +package de.kosit.validationtool.impl; + +import java.text.MessageFormat; + +/** + * Wrapper for {@link System Systems} printing capability. + * + * @author Andreas Penski + */ +public class Printer { + + /** + * Writes to standard output channel. + * + * @param message the message with placeholders + * @param params the params. + */ + public static void writeOut(final String message, final Object... params) { + System.out.println(MessageFormat.format(message, params)); + } +} diff --git a/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java b/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java index 5cd7753..637990c 100644 --- a/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java +++ b/src/main/java/de/kosit/validationtool/impl/input/SourceInput.java @@ -1,9 +1,13 @@ package de.kosit.validationtool.impl.input; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; + import java.io.IOException; import java.nio.charset.Charset; +import javax.xml.bind.util.JAXBSource; import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import org.apache.commons.io.input.ReaderInputStream; @@ -11,21 +15,23 @@ import org.apache.commons.io.input.ReaderInputStream; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.sf.saxon.om.TreeInfo; + /** - * A validator {@link de.kosit.validationtool.api.Input} based on a {@link Source}.
+ * A validator {@link de.kosit.validationtool.api.Input} based on a {@link Source}. *

* Note: The various implementations of {@link Source} varies wether the can be read twice or no. This implementation * tries to handle this with respect document identification (hashcode). * - * This class is know to work with: + * This class is known to work with: *

    *
  • {@link StreamSource} - both {@link java.io.InputStream} based and {@link java.io.Reader} based
  • *
  • {@link javax.xml.transform.dom.DOMSource}
  • *
  • {@link javax.xml.bind.util.JAXBSource}
  • + *
  • {@link TreeInfo}
  • *
* * Other {@link Source Sources} may work as well, please try and let us know. - *

* * @author Andreas Penski */ @@ -51,28 +57,33 @@ public class SourceInput extends AbstractInput { validate(); } + @Override + public String getName() { + return defaultIfBlank(this.name, this.source.getClass().getSimpleName()); + } + private void validate() { - if (!isHashcodeComputed() && isNotSupported()) { + if (!isHashcodeComputed() && !isHashcodeComputationSupported()) { throw new IllegalStateException("Unsupported source. Only StreamSource supported yet"); } if (!isHashcodeComputed() && ((StreamSource) this.source).getInputStream() == null) { log.warn("No hashcode supplied, will wrap the reader using system default charset"); } + if (!(isTreeInfo() || isDomSource() || isStreamSource() || isJaxbSource())) { + log.warn("No known to be working Source implementation provided."); + } } @Override public Source getSource() throws IOException { - if (!isHashcodeComputed() && isNotSupported()) { - throw new IllegalStateException("Unsupported source. Only InputStream-based StreamSource supported yet"); - } if (isConsumed()) { throw new IllegalStateException("A SourceInput can only read once"); } return isHashcodeComputed() ? this.source : wrappedSource(); } - private boolean isNotSupported() { - return !isStreamSource(); + private boolean isHashcodeComputationSupported() { + return isStreamSource(); } private boolean isConsumed() throws IOException { @@ -94,6 +105,18 @@ public class SourceInput extends AbstractInput { return this.source instanceof StreamSource; } + private boolean isDomSource() { + return this.source instanceof DOMSource; + } + + public boolean isTreeInfo() { + return this.source instanceof TreeInfo; + } + + private boolean isJaxbSource() { + return this.source instanceof JAXBSource; + } + private Source wrappedSource() { Source result = this.source; if (isStreamSource()) { @@ -107,11 +130,9 @@ public class SourceInput extends AbstractInput { return result; } - - @Override public boolean supportsMultipleReads() { - return false; + return isDomSource() || isTreeInfo(); } } diff --git a/src/main/java/de/kosit/validationtool/impl/input/XdmNodeInput.java b/src/main/java/de/kosit/validationtool/impl/input/XdmNodeInput.java new file mode 100644 index 0000000..8780d3e --- /dev/null +++ b/src/main/java/de/kosit/validationtool/impl/input/XdmNodeInput.java @@ -0,0 +1,34 @@ +package de.kosit.validationtool.impl.input; + +import javax.xml.transform.Source; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import de.kosit.validationtool.api.Input; + +import net.sf.saxon.s9api.XdmNode; + +/** + * An {@link Input} implementation holding saxon's {@link XdmNode} object. + * + * @author Andreas Penski + */ +@RequiredArgsConstructor +@Getter +public class XdmNodeInput implements Input { + + private final XdmNode node; + + private final String name; + + private final String digestAlgorithm; + + private final byte[] hashCode; + + @Override + public Source getSource() { + // usually not neccessary to be called. + return this.node.getUnderlyingNode(); + } +} diff --git a/src/main/java/de/kosit/validationtool/impl/tasks/CheckAction.java b/src/main/java/de/kosit/validationtool/impl/tasks/CheckAction.java index 23d6476..a5b65aa 100644 --- a/src/main/java/de/kosit/validationtool/impl/tasks/CheckAction.java +++ b/src/main/java/de/kosit/validationtool/impl/tasks/CheckAction.java @@ -134,7 +134,7 @@ public interface CheckAction { * Prüfschritt bedingt auszuführen. * * @param results die bisher gesammelten Information - * @return true wenn der Schritt ausgelassen werden soll + * @return true wenn der Schritt ausgelassen werden soll */ default boolean isSkipped(final Bag results) { return false; 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 c5559a7..89166a1 100644 --- a/src/main/java/de/kosit/validationtool/impl/tasks/DocumentParseAction.java +++ b/src/main/java/de/kosit/validationtool/impl/tasks/DocumentParseAction.java @@ -27,6 +27,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import de.kosit.validationtool.api.Input; +import de.kosit.validationtool.impl.input.XdmNodeInput; import de.kosit.validationtool.impl.model.Result; import de.kosit.validationtool.model.reportInput.ValidationResultsWellformedness; import de.kosit.validationtool.model.reportInput.XMLSyntaxError; @@ -47,6 +48,7 @@ import net.sf.saxon.s9api.XdmNode; 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 @@ -60,11 +62,17 @@ public class DocumentParseAction implements CheckAction { throw new IllegalArgumentException("Input may not be null"); } Result result; + try { - final DocumentBuilder builder = this.processor.newDocumentBuilder(); - builder.setLineNumbering(true); - final XdmNode doc = builder.build(content.getSource()); - result = new Result<>(doc, Collections.emptyList()); + if (content instanceof XdmNodeInput && hasCompatibleConfiguration((XdmNodeInput) content)) { + // parsing not neccessary + result = new Result<>(((XdmNodeInput) content).getNode()); + } else { + final DocumentBuilder builder = this.processor.newDocumentBuilder(); + builder.setLineNumbering(true); + final XdmNode doc = builder.build(content.getSource()); + result = new Result<>(doc, Collections.emptyList()); + } } catch (final SaxonApiException | IOException e) { log.debug("Exception while parsing {}", content.getName(), e); final XMLSyntaxError error = new XMLSyntaxError(); @@ -76,6 +84,10 @@ public class DocumentParseAction implements CheckAction { return result; } + private boolean hasCompatibleConfiguration(final XdmNodeInput content) { + return content.getNode().getProcessor().getUnderlyingConfiguration().isCompatible(this.processor.getUnderlyingConfiguration()); + } + @Override public void check(final Bag results) { final Result parserResult = parseDocument(results.getInput()); diff --git a/src/main/resources/gui/docs/configurations.md b/src/main/resources/gui/docs/configurations.md index 3e8678d..0f3b31e 100644 --- a/src/main/resources/gui/docs/configurations.md +++ b/src/main/resources/gui/docs/configurations.md @@ -11,5 +11,5 @@ Currently, there are two public third party validation configurations available. * Source code is available on [GitHub](https://github.com/itplr-kosit/validator-configuration-xgewerbeanzeige) * [Releases](https://github.com/itplr-kosit/validator-configuration-xgewerbeanzeige/releases) can also be downloaded -For creating custom configurations see [configaration documentation](https://github.com/itplr-kosit/validator/blob/master/docs/configurations.md) +For creating custom configurations see [configuration documentation](https://github.com/itplr-kosit/validator/blob/master/docs/configurations.md) for details \ No newline at end of file diff --git a/src/test/java/de/kosit/validationtool/api/InputFactoryTest.java b/src/test/java/de/kosit/validationtool/api/InputFactoryTest.java index fd1a209..55c294d 100644 --- a/src/test/java/de/kosit/validationtool/api/InputFactoryTest.java +++ b/src/test/java/de/kosit/validationtool/api/InputFactoryTest.java @@ -19,6 +19,7 @@ package de.kosit.validationtool.api; +import static de.kosit.validationtool.impl.Helper.Simple.SIMPLE_VALID; import static de.kosit.validationtool.impl.input.StreamHelper.drain; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +32,6 @@ import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Paths; -import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; @@ -188,8 +188,7 @@ public class InputFactoryTest { final Document dom = NodeOverNodeInfo.wrap(handler.getDocumentNode().getUnderlyingNode()).getOwnerDocument(); final Input domInput = InputFactory.read(new DOMSource(dom), "MD5", "id".getBytes()); assertThat(domInput).isNotNull(); - final Source source = domInput.getSource(); - assertThat(source).isNotNull(); + assertThat(domInput.getSource()).isNotNull(); final Result parsed = Helper.parseDocument(domInput); assertThat(parsed.isValid()).isTrue(); @@ -197,4 +196,17 @@ public class InputFactoryTest { assertThat(Helper.parseDocument(domInput).getObject()).isNotNull(); } + @Test + public void testXdmNode() throws Exception { + final XdmNode node = TestObjectFactory.createProcessor().newDocumentBuilder().build(new StreamSource(SIMPLE_VALID.toASCIIString())); + final Input nodeInput = InputFactory.read(node, "node test"); + assertThat(nodeInput).isNotNull(); + assertThat(nodeInput.getSource()).isNotNull(); + final Result parsed = Helper.parseDocument(nodeInput); + assertThat(parsed.isValid()).isTrue(); + + // read twice + assertThat(Helper.parseDocument(nodeInput).getObject()).isNotNull(); + } + } diff --git a/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java b/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java index 1d92e18..5217705 100644 --- a/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java +++ b/src/test/java/de/kosit/validationtool/impl/DefaultCheckTest.java @@ -35,6 +35,8 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; +import javax.xml.transform.stream.StreamSource; + import org.junit.Before; import org.junit.Test; import org.w3c.dom.Document; @@ -42,9 +44,12 @@ import org.w3c.dom.Document; import de.kosit.validationtool.api.AcceptRecommendation; import de.kosit.validationtool.api.Configuration; import de.kosit.validationtool.api.Input; +import de.kosit.validationtool.api.InputFactory; import de.kosit.validationtool.api.Result; import de.kosit.validationtool.impl.Helper.Simple; +import net.sf.saxon.s9api.XdmNode; + /** * Test das Check-Interface * @@ -238,4 +243,18 @@ public class DefaultCheckTest { assertThat(result.getProcessingErrors()).hasSize(1); } + @Test + public void testXdmNode() throws Exception { + XdmNode node = TestObjectFactory.createProcessor().newDocumentBuilder().build(new StreamSource(SIMPLE_VALID.toASCIIString())); + Input domInput = InputFactory.read(node, "node test"); + Result result = this.validCheck.checkInput(domInput); + assertThat(result.isProcessingSuccessful()).isEqualTo(true); + + // test compatible configuration + node = this.validCheck.getConfiguration().getContentRepository().getProcessor().newDocumentBuilder() + .build(new StreamSource(SIMPLE_VALID.toASCIIString())); + domInput = InputFactory.read(node, "node test"); + result = this.validCheck.checkInput(domInput); + assertThat(result.isProcessingSuccessful()).isEqualTo(true); + } }