mirror of
https://github.com/itplr-kosit/validator.git
synced 2026-05-26 01:05:38 +00:00
#10 Daemon Mode umsetzen
This commit is contained in:
parent
87a05cb456
commit
d1d3d25f36
35 changed files with 17482 additions and 25 deletions
|
|
@ -20,12 +20,12 @@
|
|||
package de.kosit.validationtool.cmd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -37,6 +37,7 @@ import org.apache.commons.cli.HelpFormatter;
|
|||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -50,20 +51,21 @@ import de.kosit.validationtool.impl.ObjectFactory;
|
|||
|
||||
/**
|
||||
* Commandline Version des Prüftools. Parsed die Kommandozeile und führt die konfigurierten Aktionen aus.
|
||||
*
|
||||
*
|
||||
* @author Andreas Penski
|
||||
*/
|
||||
@Slf4j
|
||||
public class CommandLineApplication {
|
||||
|
||||
|
||||
private static final Option HELP = Option.builder("?").longOpt("help").argName("Help").desc("Displays this help").build();
|
||||
|
||||
|
||||
private static final Option SCENARIOS = Option.builder("s").required().longOpt("scenarios").hasArg()
|
||||
.desc("Location of scenarios.xml e.g.").build();
|
||||
|
||||
private static final Option REPOSITORY = Option.builder("r").longOpt("repository").hasArg()
|
||||
.desc("Directory containing scenario content").build();
|
||||
|
||||
private static final Option PRINT = Option.builder("p").longOpt("print").desc("Prints the check result to stdout").build();
|
||||
|
||||
private static final Option OUTPUT = Option.builder("o").longOpt("output-directory")
|
||||
|
|
@ -76,6 +78,17 @@ public class CommandLineApplication {
|
|||
|
||||
private static final Option CHECK_ASSERTIONS = Option.builder("c").longOpt("check-assertions").hasArg()
|
||||
.desc("Check the result using defined assertions").argName("assertions-file").build();
|
||||
private static final Option SERVER = Option.builder("D").longOpt("daemon").desc("Starts a daemon listing for validation requests").build();
|
||||
|
||||
private static final Option HOST = Option.builder("H").longOpt("host").hasArg()
|
||||
.desc("The hostname / IP address to bind the daemon. Default is localhost").build();
|
||||
|
||||
private static final Option PORT = Option.builder("P").longOpt("port").hasArg()
|
||||
.desc("The port to bind the daemon. Default is 8080").build();
|
||||
|
||||
private static final Option WORKER_COUNT = Option.builder("T").longOpt("threads").hasArg().desc("Number of threads processing validation requests").build();
|
||||
public static final int DAEMON_SIGNAL = 100;
|
||||
|
||||
|
||||
private static final Option PRINT_MEM_STATS = Option.builder("m").longOpt("memory-stats").desc("Prints some memory stats").build();
|
||||
|
||||
|
|
@ -90,15 +103,19 @@ public class CommandLineApplication {
|
|||
*/
|
||||
public static void main(String[] args) {
|
||||
final int resultStatus = mainProgram(args);
|
||||
System.exit(resultStatus);
|
||||
if (DAEMON_SIGNAL != resultStatus) {
|
||||
System.exit(resultStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hauptprogramm für die Kommandozeilen-Applikation.
|
||||
*
|
||||
* @param args die Eingabe-Argumente
|
||||
*/
|
||||
static int mainProgram(String[] args) {
|
||||
int returnValue = 0;
|
||||
Options options = createOptions();
|
||||
if (isHelpRequested(args)) {
|
||||
printHelp(options);
|
||||
|
|
@ -106,10 +123,12 @@ public class CommandLineApplication {
|
|||
try {
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
final CommandLine cmd = parser.parse(options, args);
|
||||
if (cmd.getArgList().isEmpty()) {
|
||||
if (cmd.hasOption(SERVER.getOpt())) {
|
||||
returnValue = startDaemonMode(cmd);
|
||||
} else if (cmd.getArgList().isEmpty()) {
|
||||
printHelp(createOptions());
|
||||
} else {
|
||||
return processActions(cmd);
|
||||
returnValue = processActions(cmd);
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
log.error("Error processing command line arguments: " + e.getMessage());
|
||||
|
|
@ -119,6 +138,46 @@ public class CommandLineApplication {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private static int determinePort(CommandLine cmd) {
|
||||
int port = 8080;
|
||||
if (checkOptionWithValue(PORT, cmd)) {
|
||||
port = Integer.parseInt(cmd.getOptionValue(PORT.getOpt()));
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
private static int determineThreads(CommandLine cmd) {
|
||||
int threads = Runtime.getRuntime().availableProcessors();
|
||||
if (checkOptionWithValue(WORKER_COUNT, cmd)) {
|
||||
threads = Integer.parseInt(cmd.getOptionValue(WORKER_COUNT.getOpt()));
|
||||
}
|
||||
return threads;
|
||||
}
|
||||
|
||||
private static String determineHost(CommandLine cmd) {
|
||||
String host = "localhost";
|
||||
if (checkOptionWithValue(HOST, cmd)) {
|
||||
host = cmd.getOptionValue(HOST.getOpt());
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
private static int startDaemonMode(CommandLine cmd) {
|
||||
Option[] unavailable = new Option[]{PRINT, CHECK_ASSERTIONS, DEBUG, OUTPUT, EXTRACT_HTML};
|
||||
warnUnusedOptions(cmd, unavailable, true);
|
||||
Daemon validDaemon = new Daemon(determineDefinition(cmd), determineRepository(cmd), determineHost(cmd), determinePort(cmd), determineThreads(cmd));
|
||||
validDaemon.startServer();
|
||||
return DAEMON_SIGNAL;
|
||||
}
|
||||
|
||||
private static void warnUnusedOptions(CommandLine cmd, Option[] unavailable, boolean daemon) {
|
||||
Arrays.stream(cmd.getOptions()).filter(o -> ArrayUtils.contains(unavailable, o)).map(o -> "The option " + o.getLongOpt() + " is not available in daemon mode").forEach(log::error);
|
||||
if (daemon && !cmd.getArgList().isEmpty()) {
|
||||
log.info("Ignoring test targets in daemon mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean isHelpRequested(String[] args) {
|
||||
Options helpOptions = createHelpOptions();
|
||||
try {
|
||||
|
|
@ -137,6 +196,8 @@ public class CommandLineApplication {
|
|||
try {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
Option[] unavailable = new Option[]{HOST, PORT, WORKER_COUNT};
|
||||
warnUnusedOptions(cmd, unavailable, false);
|
||||
CheckConfiguration d = new CheckConfiguration(determineDefinition(cmd));
|
||||
d.setScenarioRepository(determineRepository(cmd));
|
||||
InternalCheck check = new InternalCheck(d);
|
||||
|
|
@ -149,6 +210,7 @@ public class CommandLineApplication {
|
|||
if (cmd.hasOption(PRINT.getOpt())) {
|
||||
check.getCheckSteps().add(new PrintReportAction());
|
||||
}
|
||||
|
||||
if (cmd.hasOption(CHECK_ASSERTIONS.getOpt())) {
|
||||
Assertions assertions = loadAssertions(cmd.getOptionValue(CHECK_ASSERTIONS.getOpt()));
|
||||
check.getCheckSteps().add(new CheckAssertionAction(assertions, ObjectFactory.createProcessor()));
|
||||
|
|
@ -236,7 +298,7 @@ public class CommandLineApplication {
|
|||
|
||||
}
|
||||
|
||||
private static URI determineRepository(CommandLine cmd) throws MalformedURLException {
|
||||
private static URI determineRepository(CommandLine cmd) {
|
||||
if (checkOptionWithValue(REPOSITORY, cmd)) {
|
||||
Path d = Paths.get(cmd.getOptionValue(REPOSITORY.getOpt()));
|
||||
if (Files.isDirectory(d)) {
|
||||
|
|
@ -249,7 +311,7 @@ public class CommandLineApplication {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static URI determineDefinition(CommandLine cmd) throws MalformedURLException {
|
||||
private static URI determineDefinition(CommandLine cmd) {
|
||||
checkOptionWithValue(SCENARIOS, cmd);
|
||||
Path f = Paths.get(cmd.getOptionValue(SCENARIOS.getOpt()));
|
||||
if (Files.isRegularFile(f)) {
|
||||
|
|
@ -291,6 +353,9 @@ public class CommandLineApplication {
|
|||
private static Options createOptions() {
|
||||
Options options = new Options();
|
||||
options.addOption(HELP);
|
||||
options.addOption(SERVER);
|
||||
options.addOption(HOST);
|
||||
options.addOption(PORT);
|
||||
options.addOption(SCENARIOS);
|
||||
options.addOption(REPOSITORY);
|
||||
options.addOption(PRINT);
|
||||
|
|
@ -299,6 +364,7 @@ public class CommandLineApplication {
|
|||
options.addOption(DEBUG);
|
||||
options.addOption(CHECK_ASSERTIONS);
|
||||
options.addOption(PRINT_MEM_STATS);
|
||||
options.addOption(WORKER_COUNT);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
197
src/main/java/de/kosit/validationtool/cmd/Daemon.java
Normal file
197
src/main/java/de/kosit/validationtool/cmd/Daemon.java
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
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.Input;
|
||||
import de.kosit.validationtool.api.InputFactory;
|
||||
import de.kosit.validationtool.impl.DefaultCheck;
|
||||
import de.kosit.validationtool.impl.ObjectFactory;
|
||||
import de.kosit.validationtool.model.scenarios.Scenarios;
|
||||
|
||||
/**
|
||||
* 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(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(HttpExchange httpExchange) throws IOException {
|
||||
try {
|
||||
String requestMethod = httpExchange.getRequestMethod();
|
||||
if (requestMethod.equals("POST")) {
|
||||
InputStream inputStream = httpExchange.getRequestBody();
|
||||
Input serverInput = InputFactory.read(inputStream, "Prüfling" + counter.incrementAndGet());
|
||||
|
||||
int contentLength = serverInput.getContent().length;
|
||||
if (contentLength != 0) {
|
||||
writeOutputstreamArray(httpExchange, implemenation.check(serverInput));
|
||||
} else {
|
||||
writeError(httpExchange, 400, "XML-Inhalt erforderlich!");
|
||||
}
|
||||
} else {
|
||||
writeError(httpExchange, 405, "Es ist nur die POST-Methode erlaubt!");
|
||||
}
|
||||
} catch (TransformerException 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 Scenarios scenarios;
|
||||
|
||||
HealthHandler(Scenarios scenarios) {
|
||||
this.scenarios = scenarios;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange httpExchange) throws IOException {
|
||||
Health health = new Health(scenarios);
|
||||
Document doc = health.writeHealthXml();
|
||||
try {
|
||||
writeOutputstreamArray(httpExchange, doc);
|
||||
} catch (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(HttpExchange httpExchange, int rCode, String response) throws IOException {
|
||||
httpExchange.sendResponseHeaders(rCode, response.length());
|
||||
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(HttpExchange httpExchange, Document doc) throws IOException, TransformerException {
|
||||
final byte[] bytes = serialize(doc);
|
||||
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(Document report) throws TransformerException {
|
||||
|
||||
try ( ByteArrayOutputStream bArrayOS = new ByteArrayOutputStream() ) {
|
||||
DOMSource source = new DOMSource(report);
|
||||
StreamResult streamResult = new StreamResult(bArrayOS);
|
||||
Transformer transformer = ObjectFactory.createTransformer(true);
|
||||
transformer.transform(source, streamResult);
|
||||
return bArrayOS.toByteArray();
|
||||
} catch (IOException e) {
|
||||
log.error("Report {}", e.getMessage(), e);
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methode zum Starten des Servers
|
||||
*/
|
||||
void startServer() {
|
||||
CheckConfiguration config = new CheckConfiguration(scenarioDefinition);
|
||||
config.setScenarioRepository(repository);
|
||||
HttpServer server = null;
|
||||
try {
|
||||
server = HttpServer.create(new InetSocketAddress(hostName, port), 0);
|
||||
DefaultCheck check = new DefaultCheck(config);
|
||||
server.createContext("/", new HttpServerHandler(check));
|
||||
server.createContext("/health", new HealthHandler(check.getRepository().getScenarios()));
|
||||
server.setExecutor(Executors.newFixedThreadPool(threadCount));
|
||||
server.start();
|
||||
log.info("Server ist erfolgreich gestartet");
|
||||
} catch (IOException e) {
|
||||
log.error("Fehler beim HttpServer erstellen!", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/main/java/de/kosit/validationtool/cmd/Health.java
Normal file
117
src/main/java/de/kosit/validationtool/cmd/Health.java
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package de.kosit.validationtool.cmd;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import de.kosit.validationtool.model.scenarios.Scenarios;
|
||||
|
||||
/**
|
||||
* 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 Scenarios scenarios;
|
||||
|
||||
Health(Scenarios scenarios) {
|
||||
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
freeMemory = runtime.freeMemory();
|
||||
maxMemory = runtime.maxMemory();
|
||||
totalMemory = runtime.totalMemory();
|
||||
this.scenarios = scenarios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Methode, die schreibt das Health Xml für optimale Status
|
||||
*
|
||||
*/
|
||||
Document writeHealthXml() {
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder;
|
||||
Document doc = null;
|
||||
try {
|
||||
dBuilder = dbFactory.newDocumentBuilder();
|
||||
doc = dBuilder.newDocument();
|
||||
Element rootElement = doc.createElementNS("https://localhost:8080/Health", "Health");
|
||||
doc.appendChild(rootElement);
|
||||
rootElement.appendChild(getMemory(doc, freeMemory, maxMemory, totalMemory));
|
||||
rootElement.appendChild(getState(doc));
|
||||
rootElement.appendChild(getScenario(doc, scenarios));
|
||||
} catch (ParserConfigurationException e) {
|
||||
log.error("Fehler beim Schreiben der Status-Informationen", e);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Methode, die schreibt das System Status Node im Xml File
|
||||
*
|
||||
* @param doc Vom Typ Dokument.
|
||||
*
|
||||
*/
|
||||
private Node getState(Document doc) {
|
||||
Element state = doc.createElement("state");
|
||||
state.setAttribute("indicator", "OK");
|
||||
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 scenarios Vom Typ {@link Scenarios} das verwendete scenario.
|
||||
*
|
||||
*/
|
||||
private Node getScenario(Document doc, Scenarios scenarios) {
|
||||
Element scenario = doc.createElement("scenario");
|
||||
Element scenarioNameNode = doc.createElement("name");
|
||||
scenarioNameNode.appendChild(doc.createTextNode(scenarios.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(Document doc, long freeMemory, long maxMemory, long totalMemory) {
|
||||
Element memory = doc.createElement("memoryState");
|
||||
String freeM = Long.toString(freeMemory);
|
||||
Element freeMNode = doc.createElement("freeMemory");
|
||||
freeMNode.appendChild(doc.createTextNode(freeM));
|
||||
memory.appendChild(freeMNode);
|
||||
String maxM = Long.toString(maxMemory);
|
||||
Element maxMNode = doc.createElement("maxMemory");
|
||||
maxMNode.appendChild(doc.createTextNode(maxM));
|
||||
memory.appendChild(maxMNode);
|
||||
String totalM = Long.toString(totalMemory);
|
||||
Element totalMNode = doc.createElement("totalMemory");
|
||||
totalMNode.appendChild(doc.createTextNode(totalM));
|
||||
memory.appendChild(totalMNode);
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue