diff --git a/.idea/compiler.xml b/.idea/compiler.xml index a26cfb6..9f64100 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -2,6 +2,7 @@ + diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 63fc954..ecaea3d 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -2,6 +2,7 @@ + diff --git a/src/generated/java/de/kosit/validationtool/model/daemon/ObjectFactory.java b/src/generated/java/de/kosit/validationtool/model/daemon/ObjectFactory.java new file mode 100644 index 0000000..548fd2d --- /dev/null +++ b/src/generated/java/de/kosit/validationtool/model/daemon/ObjectFactory.java @@ -0,0 +1,80 @@ +// +// Diese Datei wurde mit der JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.3.0 generiert +// Siehe https://javaee.github.io/jaxb-v2/ +// Änderungen an dieser Datei gehen bei einer Neukompilierung des Quellschemas verloren. +// Generiert: 2020.04.29 um 03:45:08 PM CEST +// + + +package de.kosit.validationtool.model.daemon; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlElementDecl; +import javax.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the de.xoev.de.validator.framework._1.daemon package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + private final static QName _Health_QNAME = new QName("http://www.xoev.de/de/validator/framework/1/daemon", "health"); + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: de.xoev.de.validator.framework._1.daemon + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link HealthType } + * + */ + public static HealthType createHealthType() { + return new HealthType(); + } + + /** + * Create an instance of {@link ApplicationType } + * + */ + public static ApplicationType createApplicationType() { + return new ApplicationType(); + } + + /** + * Create an instance of {@link MemoryType } + * + */ + public static MemoryType createMemoryType() { + return new MemoryType(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link HealthType }{@code >} + * + * @param value + * Java instance representing xml element's value. + * @return + * the new instance of {@link JAXBElement }{@code <}{@link HealthType }{@code >} + */ + @XmlElementDecl(namespace = "http://www.xoev.de/de/validator/framework/1/daemon", name = "health") + public static JAXBElement createHealth(final HealthType value) { + return new JAXBElement(_Health_QNAME, HealthType.class, null, value); + } + +} diff --git a/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java b/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java index 4c472d6..537a3be 100644 --- a/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java +++ b/src/main/java/de/kosit/validationtool/cmd/ExtractHtmlContentAction.java @@ -71,7 +71,7 @@ class ExtractHtmlContentAction implements CheckAction { log.info("Writing report html '{}' to {}", name, file.toAbsolutePath()); serializer.serializeNode(node); } catch (final SaxonApiException e) { - log.info("Error extracting html content to {}", file.toAbsolutePath(), e); + log.error("Error extracting html content to {}", file.toAbsolutePath(), e); } } diff --git a/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java index 5572ee6..d6e91f5 100644 --- a/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java +++ b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java @@ -1,5 +1,7 @@ package de.kosit.validationtool.config; +import static de.kosit.validationtool.impl.DateFactory.createTimestamp; + import java.net.URI; import java.time.LocalDate; import java.util.ArrayList; @@ -12,6 +14,7 @@ import java.util.stream.Collectors; import javax.xml.validation.Schema; import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; import lombok.extern.slf4j.Slf4j; @@ -21,6 +24,10 @@ import de.kosit.validationtool.impl.ContentRepository; import de.kosit.validationtool.impl.ResolvingMode; import de.kosit.validationtool.impl.Scenario; import de.kosit.validationtool.impl.model.Result; +import de.kosit.validationtool.model.scenarios.DescriptionType; +import de.kosit.validationtool.model.scenarios.NoScenarioReportType; +import de.kosit.validationtool.model.scenarios.ObjectFactory; +import de.kosit.validationtool.model.scenarios.Scenarios; import net.sf.saxon.s9api.Processor; @@ -52,6 +59,8 @@ public class ConfigurationBuilder { private URI repository; + private String description; + public ConfigurationBuilder author(final String authorName) { this.author = authorName; return this; @@ -84,6 +93,11 @@ public class ConfigurationBuilder { return this; } + public ConfigurationBuilder description(final String description) { + this.description = description; + return this; + } + /** * Create a fallback scenario configuration. * @@ -182,9 +196,29 @@ public class ConfigurationBuilder { configuration.setDate(this.date); configuration.setName(this.name); configuration.setContentRepository(contentRepository); + configuration.getAdditionalParameters().put(Keys.SCENARIO_DEFINITION, createDefinition(configuration)); return (configuration); } + private Scenarios createDefinition(final DefaultConfiguration configuration) { + final Scenarios s = new Scenarios(); + s.setAuthor(configuration.getAuthor()); + s.setDate(createTimestamp()); + final DescriptionType d = new DescriptionType(); + d.getPOrOlOrUl().add(new ObjectFactory().createDescriptionTypeP(StringUtils.defaultIfBlank(this.description, ""))); + s.setDescription(d); + s.setName(configuration.getName()); + s.getScenario().addAll(configuration.getScenarios().stream().map(Scenario::getConfiguration).collect(Collectors.toList())); + s.setNoScenarioReport(createNoScenarioReportType(configuration.getFallbackScenario())); + return s; + } + + private static NoScenarioReportType createNoScenarioReportType(final Scenario fallbackScenario) { + final NoScenarioReportType no = new NoScenarioReportType(); + no.setResource(fallbackScenario.getConfiguration().getCreateReport().getResource()); + return no; + } + private Scenario initializeFallback(final ContentRepository contentRepository) { if (this.fallbackBuilder == null) { throw new IllegalStateException("No fallback configuration specified"); diff --git a/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java b/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java index 00589c8..684695d 100644 --- a/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java +++ b/src/main/java/de/kosit/validationtool/config/ConfigurationLoader.java @@ -129,6 +129,8 @@ public class ConfigurationLoader { configuration.setDate(def.getDate().toString()); configuration.setName(def.getName()); configuration.setContentRepository(contentRepository); + configuration.getAdditionalParameters().put(Keys.SCENARIOS_FILE, this.scenarioDefinition); + configuration.getAdditionalParameters().put(Keys.SCENARIO_DEFINITION, def); return (configuration); } diff --git a/src/main/java/de/kosit/validationtool/config/Keys.java b/src/main/java/de/kosit/validationtool/config/Keys.java new file mode 100644 index 0000000..6670e71 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/config/Keys.java @@ -0,0 +1,20 @@ +package de.kosit.validationtool.config; + +/** + * Defines some keys used for supplying additional parameters internally. + * + * @author Andreas Penski + */ +public class Keys { + + /** + * The actual scenarios file location as used with {@link ConfigurationLoader}. + */ + public static final String SCENARIOS_FILE = "scenarios_file"; + + /** + * The actual scenarios configuration represented as serializable tree. This either loaded from file or build manually + * via {@link ConfigurationBuilder} + */ + public static final String SCENARIO_DEFINITION = "scenario_definition"; +} diff --git a/src/main/java/de/kosit/validationtool/daemon/BaseHandler.java b/src/main/java/de/kosit/validationtool/daemon/BaseHandler.java new file mode 100644 index 0000000..014652f --- /dev/null +++ b/src/main/java/de/kosit/validationtool/daemon/BaseHandler.java @@ -0,0 +1,34 @@ +package de.kosit.validationtool.daemon; + +import java.io.IOException; +import java.io.OutputStream; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +/** + * Simple base implemenation for http handlers. Doing I/O stuff. + * + * @author Andreas Penski + */ +public abstract class BaseHandler implements HttpHandler { + + protected static final String APPLICATION_XML = "application/xml"; + + protected static void write(final HttpExchange exchange, final byte[] content, final String contentType) throws IOException { + final OutputStream os = exchange.getResponseBody(); + exchange.getResponseHeaders().add("Content-Type", contentType); + exchange.sendResponseHeaders(200, content.length); + os.write(content); + os.close(); + } + + protected static void error(final HttpExchange httpExchange, final int statusCode, final String message) throws IOException { + final byte[] bytes = message.getBytes(); + httpExchange.sendResponseHeaders(statusCode, bytes.length); + final OutputStream os = httpExchange.getResponseBody(); + os.write(bytes); + os.close(); + } + +} diff --git a/src/main/java/de/kosit/validationtool/daemon/HttpServerHandler.java b/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java similarity index 53% rename from src/main/java/de/kosit/validationtool/daemon/HttpServerHandler.java rename to src/main/java/de/kosit/validationtool/daemon/CheckHandler.java index 126f7e4..1476d8c 100644 --- a/src/main/java/de/kosit/validationtool/daemon/HttpServerHandler.java +++ b/src/main/java/de/kosit/validationtool/daemon/CheckHandler.java @@ -1,32 +1,37 @@ package de.kosit.validationtool.daemon; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.atomic.AtomicLong; import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import de.kosit.validationtool.api.Check; import de.kosit.validationtool.api.InputFactory; +import de.kosit.validationtool.api.Result; import de.kosit.validationtool.impl.input.SourceInput; +import net.sf.saxon.s9api.Processor; +import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.Serializer; + /** * Wir benötigen einen Handler, der zur Verarbeitung von HTTP-Anforderungen aufgerufen wird um hier die Verarbeitung des * POST Request zu realisieren. */ @Slf4j -class HttpServerHandler implements HttpHandler { +@RequiredArgsConstructor +class CheckHandler extends BaseHandler { private static final AtomicLong counter = new AtomicLong(0); private final Check implemenation; - HttpServerHandler(final Check check) { - this.implemenation = check; - } + private final Processor processor; /** * Methode, die eine gegebene Anforderung verarbeitet und eine entsprechende Antwort generiert @@ -41,20 +46,31 @@ class HttpServerHandler implements HttpHandler { 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)); + final SourceInput serverInput = (SourceInput) InputFactory.read(inputStream, + "supplied_instance_" + counter.incrementAndGet()); + final Result result = this.implemenation.checkInput(serverInput); + write(httpExchange, serialize(result), APPLICATION_XML); } else { - Daemon.writeError(httpExchange, 400, "XML-Inhalt erforderlich!"); + error(httpExchange, 400, "No content supplied"); } } else { - Daemon.writeError(httpExchange, 405, "Es ist nur die POST-Methode erlaubt!"); + error(httpExchange, 405, "Method not supported"); } } 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); + error(httpExchange, 500, "Internal error: " + e.getMessage()); + } + } + + private byte[] serialize(final Result result) { + try ( final ByteArrayOutputStream out = new ByteArrayOutputStream() ) { + final Serializer serializer = this.processor.newSerializer(out); + serializer.serializeNode(result.getReport()); + return out.toByteArray(); + } catch (final SaxonApiException | IOException e) { + log.error("Error serializing result", e); + throw new IllegalStateException("Can not serialize result", e); } } diff --git a/src/main/java/de/kosit/validationtool/daemon/ConfigHandler.java b/src/main/java/de/kosit/validationtool/daemon/ConfigHandler.java new file mode 100644 index 0000000..57b71ae --- /dev/null +++ b/src/main/java/de/kosit/validationtool/daemon/ConfigHandler.java @@ -0,0 +1,77 @@ +package de.kosit.validationtool.daemon; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.net.URI; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; + +import com.sun.net.httpserver.HttpExchange; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import de.kosit.validationtool.api.Configuration; +import de.kosit.validationtool.config.Keys; +import de.kosit.validationtool.impl.ConversionService; +import de.kosit.validationtool.model.scenarios.Scenarios; + +/** + * Handler that returns the actual configuration used for this daemon instance. + * + * @author Andreas Penski + */ +@Slf4j +@RequiredArgsConstructor +public class ConfigHandler extends BaseHandler { + + private final Configuration configuration; + + private final ConversionService conversionService; + + @Override + public void handle(final HttpExchange exchange) throws IOException { + try { + final Optional xml = getSource(); + if (xml.isPresent()) { + write(exchange, xml.get().getBytes(), APPLICATION_XML); + } else { + error(exchange, 404, "No configuration found"); + } + } catch (final Exception e) { + log.error("Error grabbing configuration", e); + error(exchange, 500, "Error grabbing configuration: " + e.getMessage()); + } + } + + private Optional getSource() { + final URI fileUri = (URI) this.configuration.getAdditionalParameters().get(Keys.SCENARIOS_FILE); + return fileUri != null ? loadFile(fileUri) : loadFromConfig(); + } + + private static Optional loadFile(final URI fileUri) { + try ( final Reader in = new InputStreamReader(fileUri.toURL().openStream()); + final StringWriter out = new StringWriter() ) { + IOUtils.copy(in, out); + return Optional.of(out.toString()); + } catch (final IOException e) { + return Optional.empty(); + } + } + + private Optional loadFromConfig() { + final Optional result; + final Scenarios scenarios = (Scenarios) this.configuration.getAdditionalParameters().get(Keys.SCENARIO_DEFINITION); + if (scenarios != null) { + final String s = this.conversionService.writeXml(scenarios); + result = Optional.of(s); + } else { + result = Optional.empty(); + } + return result; + } + +} diff --git a/src/main/java/de/kosit/validationtool/daemon/Daemon.java b/src/main/java/de/kosit/validationtool/daemon/Daemon.java index dc4ca80..6eeda08 100644 --- a/src/main/java/de/kosit/validationtool/daemon/Daemon.java +++ b/src/main/java/de/kosit/validationtool/daemon/Daemon.java @@ -1,19 +1,9 @@ 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; @@ -22,8 +12,9 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import de.kosit.validationtool.api.Configuration; +import de.kosit.validationtool.impl.ConversionService; import de.kosit.validationtool.impl.DefaultCheck; -import de.kosit.validationtool.impl.ObjectFactory; +import de.kosit.validationtool.model.daemon.HealthType; /** * HTTP-Daemon für die Bereitstellung der Prüf-Funktionalität via http. @@ -42,56 +33,6 @@ public class Daemon { 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 * @@ -100,10 +41,14 @@ public class Daemon { public void startServer(final Configuration config) { HttpServer server = null; try { + final ConversionService converter = new ConversionService(); + converter.initialize(HealthType.class.getPackage()); + 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.createContext("/", new CheckHandler(check, config.getContentRepository().getProcessor())); + server.createContext("/server/health", new HealthHandler(config, converter)); + server.createContext("/server/config", new ConfigHandler(config, new ConversionService())); server.setExecutor(Executors.newFixedThreadPool(this.threadCount)); server.start(); log.info("Server unter Port {} ist erfolgreich gestartet", this.port); diff --git a/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java b/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java index 28b1ff7..d11dabc 100644 --- a/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java +++ b/src/main/java/de/kosit/validationtool/daemon/HealthHandler.java @@ -2,140 +2,50 @@ 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.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import de.kosit.validationtool.api.Configuration; -import de.kosit.validationtool.impl.ObjectFactory; +import de.kosit.validationtool.impl.ConversionService; +import de.kosit.validationtool.model.daemon.HealthType; +import de.kosit.validationtool.model.daemon.MemoryType; /** - * 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 + * Handler that implements a simple health check. Useful for monitoring the service. + * + * @author Andreas Penski` */ @Slf4j -class HealthHandler implements HttpHandler { +@RequiredArgsConstructor +class HealthHandler extends BaseHandler { - /** - * 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; - } + private final ConversionService conversionService; @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); - } + final HealthType health = createHealth(); + final String xml = this.conversionService.writeXml(health); + write(httpExchange, xml.getBytes(), APPLICATION_XML); + + } + + private static HealthType createHealth() { + final HealthType h = new HealthType(); + h.setMemory(createMemory()); + return h; + } + + private static MemoryType createMemory() { + final MemoryType m = new MemoryType(); + final Runtime runtime = Runtime.getRuntime(); + m.setFreeMemory(runtime.freeMemory()); + m.setMaxMemory(runtime.maxMemory()); + m.setTotalMemory(runtime.totalMemory()); + return m; } } diff --git a/src/main/java/de/kosit/validationtool/impl/DateFactory.java b/src/main/java/de/kosit/validationtool/impl/DateFactory.java new file mode 100644 index 0000000..4665c97 --- /dev/null +++ b/src/main/java/de/kosit/validationtool/impl/DateFactory.java @@ -0,0 +1,25 @@ +package de.kosit.validationtool.impl; + +import java.util.Date; +import java.util.GregorianCalendar; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * @author Andreas Penski + */ +public class DateFactory { + + 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); + } + } +} diff --git a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java index b337854..f87fb00 100644 --- a/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java +++ b/src/main/java/de/kosit/validationtool/impl/DefaultCheck.java @@ -19,17 +19,13 @@ package de.kosit.validationtool.impl; +import static de.kosit.validationtool.impl.DateFactory.createTimestamp; + 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; @@ -103,16 +99,7 @@ public class DefaultCheck implements Check { 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) { diff --git a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java b/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java deleted file mode 100644 index f1917ba..0000000 --- a/src/main/java/de/kosit/validationtool/impl/ObjectFactory.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under - * one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. KoSIT licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package de.kosit.validationtool.impl; - -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Result; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; - -import lombok.extern.slf4j.Slf4j; - -import net.sf.saxon.Configuration; -import net.sf.saxon.expr.XPathContext; -import net.sf.saxon.lib.CollectionFinder; -import net.sf.saxon.lib.FeatureKeys; -import net.sf.saxon.lib.OutputURIResolver; -import net.sf.saxon.lib.ResourceCollection; -import net.sf.saxon.lib.UnparsedTextURIResolver; -import net.sf.saxon.s9api.Processor; -import net.sf.saxon.trans.XPathException; - -/** - * Eine Factory für häufig verwendete Objekte mit XML. Zentralisiert die XML Security Konfiguration. Die Konfiguration - * basiert auf den OWASP-Empfehlungen. - * - * Diese Klasse ist stark abhängig von der Verwendung eines Oracle JDK. Alternative JDKs haben u.U. eine andere SAX- / - * StAX- / XML-Implementierug und profitieren entsprechend NICHT von den hier getroffenen Einstellungen. - * - * @author Andreas Penski - */ -@Slf4j -@Deprecated -public class ObjectFactory { - - private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver { - - public static final String MESSAGE = "Configuration error. Resolving ist not allowed"; - - @Override - public OutputURIResolver newInstance() { - return this; - } - - @Override - public Result resolve(final String href, final String base) throws TransformerException { - throw new IllegalStateException(MESSAGE); - } - - @Override - public void close(final Result result) throws TransformerException { - throw new IllegalStateException(MESSAGE); - } - - @Override - public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException { - throw new IllegalStateException(MESSAGE); - } - - @Override - public ResourceCollection findCollection(final XPathContext context, final String collectionURI) throws XPathException { - throw new IllegalStateException(MESSAGE); - } - } - private static final String ORACLE_XERCES_CLASS = "com.sun.org.apache.xerces.internal.impl.Constants"; - private static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; - private static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; - private static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; - private static Processor processor; - - static { - try { - Class.forName(ORACLE_XERCES_CLASS); - } catch (final ClassNotFoundException e) { - log.warn("No oracle JDK version of XERCES found. Configured security features may not have any effect."); - log.warn("Please take care of XML security while checking your xml contents"); - } - } - - ObjectFactory() { - // hide, it's a factory - } - - private static DocumentBuilderFactory createDocumentBuilderFactory(final boolean validating) { - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - try { - dbf.setValidating(validating); - dbf.setNamespaceAware(true); - - // explicitly enable secure processing - dbf.setFeature(FEATURE_SECURE_PROCESSING, true); - - // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented - dbf.setFeature(DISSALLOW_DOCTYPE_DECL_FEATURE, true); - - // Disable external DTDs as well - dbf.setFeature(LOAD_EXTERNAL_DTD_FEATURE, false); - - // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - return dbf; - } catch (final ParserConfigurationException e) { - throw new IllegalStateException("Can not create DocumentBuilderFactory due to underlying configuration error", e); - } - - } - - /** - * Transformer für die Ausgabe. Nutzt nicht Saxon! - * - * @param prettyPrint pretty-printing der Ausgabe - * @return einen vorkonfigurierten Transformer - */ - public static Transformer createTransformer(final boolean prettyPrint) { - Transformer transformer = null; - try { - 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) { - throw new IllegalStateException("Can not create Transformer due to underlying configuration error", e); - } - } - - - public static DocumentBuilder createDocumentBuilder(final boolean validating) { - try { - return createDocumentBuilderFactory(validating).newDocumentBuilder(); - } catch (final ParserConfigurationException e) { - throw new IllegalStateException("Can not create DocumentFactory due to underlying configuration error", e); - } - } - - private static String encode(final String input) { - try { - return URLEncoder.encode(input, StandardCharsets.UTF_8.name()); - } catch (final UnsupportedEncodingException e) { - throw new IllegalStateException("Error encoding property while initializing saxon", e); - } - } - - public static Processor createProcessor() { - if (processor == null) { - processor = new Processor(false); - //verhindere global im Prinzip alle resolving strategien - final SecureUriResolver resolver = new SecureUriResolver(); - processor.getUnderlyingConfiguration().setCollectionFinder(resolver); - processor.getUnderlyingConfiguration().setOutputURIResolver(resolver); - processor.getUnderlyingConfiguration().setUnparsedTextURIResolver(resolver); - - //grundsätzlich Feature-konfiguration: - processor.setConfigurationProperty(FeatureKeys.DTD_VALIDATION, false); - processor.setConfigurationProperty(FeatureKeys.ENTITY_RESOLVER_CLASS, ""); - processor.setConfigurationProperty(FeatureKeys.XINCLUDE, false); - processor.setConfigurationProperty(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, false); - - // Konfiguration des zu verwendenden Parsers, wenn Saxon selbst einen erzeugen muss, bspw. beim XSL parsen - processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(FEATURE_SECURE_PROCESSING), true); - processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(DISSALLOW_DOCTYPE_DECL_FEATURE), true); - processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(LOAD_EXTERNAL_DTD_FEATURE), false); - } - return processor; - } - - - -} diff --git a/src/main/model/binding/global.xjb b/src/main/model/binding/global.xjb index 33eced9..be1e39c 100644 --- a/src/main/model/binding/global.xjb +++ b/src/main/model/binding/global.xjb @@ -45,15 +45,18 @@ - + - - de.kosit.validationtool.impl.model.BaseOutput + + + + + \ No newline at end of file diff --git a/src/main/model/xsd/daemon.xsd b/src/main/model/xsd/daemon.xsd new file mode 100644 index 0000000..5d1186a --- /dev/null +++ b/src/main/model/xsd/daemon.xsd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/de/kosit/validationtool/impl/Helper.java b/src/test/java/de/kosit/validationtool/impl/Helper.java index 735f9a3..f06d755 100644 --- a/src/test/java/de/kosit/validationtool/impl/Helper.java +++ b/src/test/java/de/kosit/validationtool/impl/Helper.java @@ -28,23 +28,17 @@ import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Paths; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import org.w3c.dom.Document; - import de.kosit.validationtool.api.Input; import de.kosit.validationtool.api.ResolvingConfigurationStrategy; import de.kosit.validationtool.impl.model.Result; import de.kosit.validationtool.impl.tasks.DocumentParseAction; import de.kosit.validationtool.model.reportInput.XMLSyntaxError; -import net.sf.saxon.dom.NodeOverNodeInfo; import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.Serializer; import net.sf.saxon.s9api.XdmNode; /** @@ -148,20 +142,17 @@ public class Helper { new File("src/test/resources/examples/repository").toURI()); } - public static String serialize(final Document doc) { + public static String serialize(final XdmNode node) { try ( final StringWriter writer = new StringWriter() ) { - final Transformer transformer = TestObjectFactory.createTransformer(true); - transformer.transform(new DOMSource(doc), new StreamResult(writer)); + final Processor processor = Helper.getTestProcessor(); + final Serializer serializer = processor.newSerializer(writer); + serializer.serializeNode(node); return writer.toString(); - } catch (final IOException | TransformerException e) { + } catch (final SaxonApiException | IOException e) { throw new IllegalStateException("Can not serialize document", e); } } - public static String serialize(final XdmNode node) { - return serialize((Document) NodeOverNodeInfo.wrap(node.getUnderlyingNode())); - } - public static Result parseDocument(final Processor processor, final Input input) { return new DocumentParseAction(processor).parseDocument(input); } diff --git a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java b/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java index 1c79f51..70ac28c 100644 --- a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java +++ b/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java @@ -26,12 +26,11 @@ import java.io.IOException; import java.net.URL; import java.util.stream.Collectors; -import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import org.apache.commons.lang3.StringUtils; import org.junit.Test; -import org.w3c.dom.Document; import lombok.extern.slf4j.Slf4j; @@ -41,9 +40,9 @@ 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.XdmDestination; import net.sf.saxon.s9api.XdmNode; import net.sf.saxon.s9api.XsltCompiler; import net.sf.saxon.s9api.XsltExecutable; @@ -67,19 +66,18 @@ public class SaxonSecurityTest { final XsltCompiler compiler = p.newXsltCompiler(); 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 = TestObjectFactory.createDocumentBuilder(false).newDocument(); - document.createElement("root"); - final Document result = TestObjectFactory.createDocumentBuilder(false).newDocument(); + final XsltExecutable executable = compiler.compile(new StreamSource(resource.openStream())); + final XsltTransformer transformer = executable.load(); + final Source document = InputFactory.read("".getBytes(), "dummy").getSource(); // transformer.getUnderlyingController().setUnparsedTextURIResolver(resolver); transformer.setURIResolver(resolver); - transformer.setSource(new DOMSource(document)); - transformer.setDestination(new DOMDestination(result)); + transformer.setSource(document); + final XdmDestination result = new XdmDestination(); + transformer.setDestination(result); transformer.transform(); // wenn der Punkt erreicht wird, sollte wenigstens, das Element evil nicht mit 'bösen' Inhalten gefüllt sein! - if (StringUtils.isNotBlank(result.getDocumentElement().getTextContent())) { + if (StringUtils.isNotBlank(result.getXdmNode().getStringValue())) { fail(String.format("Saxon configuration should prevent expansion within %s", resource)); } diff --git a/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java b/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java index 1c47ad7..f239d5b 100644 --- a/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java +++ b/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java @@ -1,7 +1,95 @@ package de.kosit.validationtool.impl; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import javax.xml.transform.Result; +import javax.xml.transform.TransformerException; + +import net.sf.saxon.Configuration; +import net.sf.saxon.expr.XPathContext; +import net.sf.saxon.lib.CollectionFinder; +import net.sf.saxon.lib.FeatureKeys; +import net.sf.saxon.lib.OutputURIResolver; +import net.sf.saxon.lib.ResourceCollection; +import net.sf.saxon.lib.UnparsedTextURIResolver; +import net.sf.saxon.s9api.Processor; +import net.sf.saxon.trans.XPathException; + /** * @author Andreas Penski */ -public class TestObjectFactory extends ObjectFactory { +public class TestObjectFactory { + + private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver { + + public static final String MESSAGE = "Configuration error. Resolving ist not allowed"; + + @Override + public OutputURIResolver newInstance() { + return this; + } + + @Override + public Result resolve(final String href, final String base) throws TransformerException { + throw new IllegalStateException(MESSAGE); + } + + @Override + public void close(final Result result) throws TransformerException { + throw new IllegalStateException(MESSAGE); + } + + @Override + public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException { + throw new IllegalStateException(MESSAGE); + } + + @Override + public ResourceCollection findCollection(final XPathContext context, final String collectionURI) throws XPathException { + throw new IllegalStateException(MESSAGE); + } + } + + private static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; + + private static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + + private static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; + + private static Processor processor; + + private static String encode(final String input) { + try { + return URLEncoder.encode(input, StandardCharsets.UTF_8.name()); + } catch (final UnsupportedEncodingException e) { + throw new IllegalStateException("Error encoding property while initializing saxon", e); + } + } + + public static Processor createProcessor() { + if (processor == null) { + processor = new Processor(false); + // verhindere global im Prinzip alle resolving strategien + final SecureUriResolver resolver = new SecureUriResolver(); + processor.getUnderlyingConfiguration().setCollectionFinder(resolver); + processor.getUnderlyingConfiguration().setOutputURIResolver(resolver); + processor.getUnderlyingConfiguration().setUnparsedTextURIResolver(resolver); + + // grundsätzlich Feature-konfiguration: + processor.setConfigurationProperty(FeatureKeys.DTD_VALIDATION, false); + processor.setConfigurationProperty(FeatureKeys.ENTITY_RESOLVER_CLASS, ""); + processor.setConfigurationProperty(FeatureKeys.XINCLUDE, false); + processor.setConfigurationProperty(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, false); + + // Konfiguration des zu verwendenden Parsers, wenn Saxon selbst einen erzeugen muss, bspw. beim XSL parsen + processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(FEATURE_SECURE_PROCESSING), true); + processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(DISSALLOW_DOCTYPE_DECL_FEATURE), true); + processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(LOAD_EXTERNAL_DTD_FEATURE), false); + } + return processor; + } }