(enhance) introduce resolving strategy (configurable xml security); introduce API configuration

This commit is contained in:
Andreas Penski (init) 2020-04-29 10:06:00 +02:00
parent 7a86f049ac
commit 35c0797898
67 changed files with 2441 additions and 845 deletions

View file

@ -42,12 +42,15 @@ import org.apache.commons.lang3.StringUtils;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.Configuration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.InputFactory;
import de.kosit.validationtool.cmd.assertions.Assertions;
import de.kosit.validationtool.config.ConfigurationLoader;
import de.kosit.validationtool.daemon.Daemon;
import de.kosit.validationtool.impl.ConversionService;
import de.kosit.validationtool.impl.ObjectFactory;
import net.sf.saxon.s9api.Processor;
/**
* Commandline Version des Prüftools. Parsed die Kommandozeile und führt die konfigurierten Aktionen aus.
@ -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());

View file

@ -1,199 +0,0 @@
package de.kosit.validationtool.cmd;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.Check;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.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);
}
}
}

View file

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

View file

@ -24,10 +24,10 @@ import java.nio.file.Path;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.ContentRepository;
import de.kosit.validationtool.impl.HtmlExtractor;
import de.kosit.validationtool.impl.tasks.CheckAction;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
@ -49,12 +49,12 @@ class ExtractHtmlContentAction implements CheckAction {
private HtmlExtractor htmlExtraction;
private ContentRepository repository;
private Processor processor;
public ExtractHtmlContentAction(final ContentRepository repository, final Path outputDirectory) {
public ExtractHtmlContentAction(final Processor p, final Path outputDirectory) {
this.outputDirectory = outputDirectory;
this.htmlExtraction = new HtmlExtractor(repository);
this.repository = repository;
this.htmlExtraction = new HtmlExtractor(p);
this.processor = p;
}
@Override
@ -66,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);

View file

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

View file

@ -21,7 +21,7 @@ package de.kosit.validationtool.cmd;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.api.CheckConfiguration;
import de.kosit.validationtool.api.Configuration;
import de.kosit.validationtool.api.Input;
import de.kosit.validationtool.api.Result;
import de.kosit.validationtool.impl.DefaultCheck;
@ -45,7 +45,7 @@ class InternalCheck extends DefaultCheck {
*
* @param configuration die Konfiguration
*/
InternalCheck(final CheckConfiguration configuration) {
InternalCheck(final Configuration configuration) {
super(configuration);
}

View file

@ -21,11 +21,12 @@ package de.kosit.validationtool.cmd;
import java.io.StringWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
@ -35,13 +36,16 @@ import net.sf.saxon.s9api.Serializer;
* @author Andreas Penski
*/
@Slf4j
@RequiredArgsConstructor
class PrintReportAction implements CheckAction {
private final Processor processor;
@Override
public void check(Bag results) {
try {
final StringWriter writer = new StringWriter();
final Serializer serializer = ObjectFactory.createProcessor().newSerializer(writer);
final Serializer serializer = processor.newSerializer(writer);
serializer.serializeNode(results.getReport());
System.out.print(writer.toString());
} catch (SaxonApiException e) {

View file

@ -24,9 +24,9 @@ import java.nio.file.Path;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import de.kosit.validationtool.impl.ObjectFactory;
import de.kosit.validationtool.impl.tasks.CheckAction;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
@ -41,12 +41,14 @@ class SerializeReportAction implements CheckAction {
private final Path outputDirectory;
private final Processor processor;
@Override
public void check(Bag results) {
final Path file = outputDirectory.resolve(results.getName() + "-report.xml");
try {
log.info("Serializing result to {}", file.toAbsolutePath());
final Serializer serializer = ObjectFactory.createProcessor().newSerializer(file.toFile());
final Serializer serializer = processor.newSerializer(file.toFile());
serializer.serializeNode(results.getReport());
} catch (SaxonApiException e) {
log.error("Can not serialize result report to {}", file.toAbsolutePath(), e);