#67 [CLI,DAEMON] Return proper return codes / status codes

This commit is contained in:
Andreas Penski 2020-09-02 12:34:20 +00:00
parent fa7faf9961
commit f2223552ad
21 changed files with 424 additions and 248 deletions

View file

@ -27,37 +27,10 @@ build-java-14:
script:
- mvn $MAVEN_CLI_OPTS $BUILD_PROPS $CI_JOB_TIMESTAMP verify
artifacts:
when: on_failure
name: java-14
paths:
- target/*.jar
reports:
junit:
- target/surefire-reports/*.xml
- target/failsafe-reports/*.xml
build-java-13:
stage: build
image: maven:3-jdk-13
script:
- mvn $MAVEN_CLI_OPTS $BUILD_PROPS $CI_JOB_TIMESTAMP verify
artifacts:
name: java-13
paths:
- target/*.jar
reports:
junit:
- target/surefire-reports/*.xml
- target/failsafe-reports/*.xml
build-java-12:
stage: build
image: maven:3-jdk-12
script:
- mvn $MAVEN_CLI_OPTS $BUILD_PROPS $CI_JOB_TIMESTAMP verify
artifacts:
name: java-12
paths:
- target/*.jar
- target/*
reports:
junit:
- target/surefire-reports/*.xml

View file

@ -15,11 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [CLI] custom output without the various log messages
- [CLI] options to set the log level (`-X` = full debug output, `-l <level>` set a specific level)
- [CLI] return code ist <> 0 on rejected results
- [CLI] read (single) test target from stdin
- [DAEMON] name inputs via request URI (see [daemon documentation](./docs/daemon.md#status-codes))
### 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
- saxon dependency update (minor, 9.9.1-7)
- [DAEMON] proper status codes when returning results
## 1.3.1
### Fixed
@ -29,7 +32,7 @@ do not reflect actual schematron validation result
- exception while resolving when using XSLT's `unparsed-text()` function within report generation
### Added
- [CLI] add summary report
- [CLI] summary report
### Changed
- engine info contains version number of the validator (configurations can output this in the report for maintainance puposes)

View file

@ -1,27 +1,34 @@
# Validator
# KoSIT Validator
- [Introduction](#introduction)
- [Validation Configurations](#validation-configurations)
* [Third Party Validation Configurations](#third-party-validation-configurations)
- [Usage](#usage)
* [Standalone Command-Line Interface](#standalone-command-line-interface)
* [Application User Interface (API / embedded usage)](#application-user-interface--api---embedded-usage-)
* [Daemon-Mode](#daemon-mode)
- [Packages](#packages)
The validator is an XML validation-engine. It validates XML documents against XML Schema and Schematron Rules depending on self defined [scenarios](docs/configurations.md) which are used to fully configure the validation process.
The validator always outputs a [validation report in XML](docs/configurations.md#validators-report) including all validation errors and data about the validation.
## Introduction
The validator is an XML validation engine to validate and process XML files in various formats. It basically does the following in order:
See [architecture](docs/architecture.md) for informations about the actual validation process.
1. identify actual xml format
1. validate the xml file (using schema and schematron rules)
1. generate a custom report / extract custom data from the xml file
1. compute an acceptance status (according the supplied schema and rules)
## Packages
The validator depends on self defined [scenarios](docs/configurations.md) which are used to fully configure the process.
It always creates a [validation report in XML](docs/configurations.md#validators-report). The actual content of this is controlled by the scenario.
The validator distribution contains the following artifacts:
1. **validationtool-`<version>`.jar**: Java library for embedded use within an application
1. **validationtool-`<version`>-standalone.jar**: Uber-JAR for standalone usage containing all dependencies in one jar file. This file comes with JAXB *embedded* and can be used with Java 8 and Java >= 11)
1. **validationtool-`<version`>-java8-standalone.jar**: Uber-JAR for standalone usage with Java JDK 8 containing all dependencies in one jar file. This file file *does not* contain JAXB and depends on the bundled version of the JDK.
1. **libs/***: directory containing all (incl. optional) dependencies of the validator
See [architecture](docs/architecture.md) for information about the actual validation process.
## Validation Configurations
## Validation configurations
The validator is just an engine and does not know anything about XML Documents and has no own validation rules.
The validator is just an engine and does not know anything about XML documents and has no own validation rules.
Validation rules and details are defined in [validation scenarios](docs/configurations.md) which are used to fully configure the validation process.
All configurations are self-contained modules which are deployed and developed on their own.
### Third Party Validation Configurations
### Third party validation configurations
Currently, there are two public third party validation configurations available.
@ -34,7 +41,7 @@ Currently, there are two public third party validation configurations available.
## Usage
The validator is designed to be used in three different ways:
The validator can be used in three different ways:
* as standalone application running from the cli
* as library embedded within a custom application
@ -57,6 +64,8 @@ java -jar validationtool-<version>-standalone.jar --help
A concrete example with a specific validator configuration can be found on
[GitHub](https://github.com/itplr-kosit/validator-configuration-xrechnung)
The [CLI documentation](./docs/cli.md) shows further configuration options.
### Application User Interface (API / embedded usage)
The validator can also be used in own Java Applications via the API. An example use of the API as follows:
@ -85,3 +94,11 @@ java -jar validationtool-<version>-standalone.jar -s <scenario-config-file> -D
The [daemon documentation](./docs/daemon.md) shows more usage details and further configuration options.
## Packages
The validator distribution contains the following artifacts:
1. **validationtool-`<version>`.jar**: Java library for embedded use within an application
1. **validationtool-`<version`>-standalone.jar**: Uber-JAR for standalone usage containing all dependencies in one jar file. This file comes with JAXB *embedded* and can be used with Java 8 and Java >= 11)
1. **validationtool-`<version`>-java8-standalone.jar**: Uber-JAR for standalone usage with Java JDK 8 containing all dependencies in one jar file. This file file *does not* contain JAXB and depends on the bundled version of the JDK.
1. **libs/***: directory containing all (incl. optional) dependencies of the validator

46
docs/cli.md Normal file
View file

@ -0,0 +1,46 @@
# Validator CLI
The validator comes with a commandline interface (CLI) which allows validating any number of input xml files.
The general way using the CLI is:
```shell
java -jar validationtool-<version>-standalone.jar -s <scenario-config-file> [OPTIONS] [FILE] [FILE] [FILE] ...
```
The validator can also read the xml file from the standard input
```shell script
# via redirection
java -jar validationtool-<version>-standalone.jar -s <scenario-config-file> [OPTIONS] < my-input.xml
# read from pipe
cat my-input.xml | validationtool-<version>-standalone.jar -s <scenario-config-file> [OPTIONS]
```
The help option displays further CLI options to customize the process:
```shell
java -jar validationtool-<version>-standalone.jar --help
```
## Special features
Besides the obvious functionality of validating, the cli provides additional functionality to customize the processing:
|name | option | description |
| - | - | - |
| [Daemon mode](daemon.md) | `-D` | Starts the validator in daemon mode as an HTTP service |
| print mode | `-p` | Print the report to stdout |
| extract html | `-h` | Extracts any html blocks within the report and saves the content to the filesystem. Note: the file name is derived from the node name the html appears in |
| print memory stats | `-m` | Prints some memory usage information. Mainly for debugging purposes on processing huge xml files |
| check assertions | `-c <file>` | Check assertions on the generated reports. This is mainly useful for scenario developers. Ask KoSIT for documentation, if you want to use this feauture |
## Return codes
| code | description |
|-|-|
| 0 | All validated xml files are acceptable according to the scenario configurations |
| positive integer | Number of rejected (e.g. not acceptable) xml files according to the scenario configurations|
| -1 | Parsing error. The commandline arguments specified are incorrect |
| -2 | Configuration error. There is an error loading the configuration and/or validation targets |

View file

@ -43,8 +43,10 @@ The possible customizations are:
## Access the HTTP interface
The validation service listens to `POST`-requests on any server URL. You need to supply the xml/object to validate in the HTTP body.
The last segment of the request URI is treated as the name of the input. E.g. requests to `/myfile.xml`, `/mypath/myfile.xml` and `/mypath/myfile.xml?someParam=1`
would all result in an input named `myfile.xml`. If you don't specify a specific request URI (e.g. POST to `/`), the name is auto generated for you.
The service expects a single XML input in the HTTP body, e.g. `multipart/form-data` is not supported.
The service expects a single XML input in the HTTP body, e.g. `multipart/form-data` is NOT supported.
Examples:
@ -84,6 +86,15 @@ fetch("http://localhost:8080", requestOptions)
.then(result => console.log(result))
.catch(error => console.log('error', error));
```
## Status codes
| code | description |
|-|-|
| 200 | The xml file is acceptable according to the scenario configurations |
| 400 | Bad request. the request contains errors, e.g. no content supplied |
| 405 | Method not allowed. Thec check service is only answering on POST requests |
| 406 | The xml file is NOT acceptable according to the scenario configurations|
| 422 | Unprocessable entity. Indicates an error while processing the xml file. This hints to errors in the scenario configuration |
| 500 | Internal server error. Something went wrong |
## Authorization
There is no mechanism to check, whether client is allowed to consume the service or not. The user is responsible to secure access to the service.
@ -101,7 +112,7 @@ The daemon provides a simple GUI when issuing `GET` requests providing the follo
1. information about the actual [validator configuration](configurations.md) used by this daemon
1. a simple form to test the daemon with custom inputs
The GUI can be disabled using the API (see above) or via CLI
The GUI can be disabled using the API (see above) or via CLI:
```shell script
java -jar validationtool-<version>-standalone.jar -s <scenario-config-file> -D --disable-gui

View file

@ -504,8 +504,6 @@
</execution>
</executions>
</plugin>
</plugins>
</build>

View file

@ -19,7 +19,6 @@
package de.kosit.validationtool.cmd;
import static de.kosit.validationtool.cmd.CommandLineOptions.DAEMON_SIGNAL;
import static de.kosit.validationtool.cmd.CommandLineOptions.HELP;
import static de.kosit.validationtool.cmd.CommandLineOptions.createHelpOptions;
import static de.kosit.validationtool.cmd.CommandLineOptions.createOptions;
@ -59,16 +58,18 @@ public class CommandLineApplication {
* @param args die Eingabe-Argumente
*/
public static void main(final String[] args) {
final int resultStatus = mainProgram(args);
if (DAEMON_SIGNAL != resultStatus) {
final ReturnValue resultStatus = mainProgram(args);
if (!resultStatus.equals(ReturnValue.DAEMON_MODE)) {
sayGoodby(resultStatus);
System.exit(resultStatus);
System.exit(resultStatus.getCode());
} else {
Runtime.getRuntime().addShutdownHook(new Thread(() -> Printer.writeOut("Shutting down daemon ...")));
}
}
private static void sayGoodby(final int resultStatus) {
private static void sayGoodby(final ReturnValue resultStatus) {
Printer.writeOut("\n##############################");
if (resultStatus == 0) {
if (resultStatus.equals(ReturnValue.SUCCESS)) {
Printer.writeOut("# " + new Line(Code.GREEN).add("Validation succesful!").render(false, false) + " #");
} else {
Printer.writeOut("# " + new Line(Code.RED).add("Validation failed!").render(false, false) + " #");
@ -77,14 +78,14 @@ public class CommandLineApplication {
}
// for testing purposes. Unless jvm is terminated during tests. See above
static int mainProgram(final String[] args) {
static ReturnValue mainProgram(final String[] args) {
final Options options = createOptions();
int resultStatus;
ReturnValue resultStatus;
try {
if (isHelpRequested(args)) {
printHelp(options);
resultStatus = 0;
resultStatus = ReturnValue.SUCCESS;
} else {
final CommandLineParser parser = new DefaultParser();
final CommandLine cmd = parser.parse(options, args);
@ -94,7 +95,7 @@ public class CommandLineApplication {
} catch (final ParseException e) {
writeErr("Error processing command line arguments: {0}", e.getMessage(), e);
printHelp(options);
resultStatus = 1;
resultStatus = ReturnValue.PARSING_ERROR;
}
return resultStatus;
}

View file

@ -37,7 +37,6 @@ public class CommandLineOptions {
static final Option DEBUG_LOG = Option.builder("X").longOpt("debug-logging").desc("Enables full debug log. Alias for -l debug").build();
static final Option LOG_LEVEL = Option.builder("l").longOpt("log-level").hasArg()
.desc("Enables a certain log level for debugging " + "purposes").build();
public static final int DAEMON_SIGNAL = 100;
static final Option PRINT_MEM_STATS = Option.builder("m").longOpt("memory-stats").desc("Prints some memory stats").build();
private CommandLineOptions() {

View file

@ -20,10 +20,9 @@
package de.kosit.validationtool.cmd;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.fusesource.jansi.AnsiRenderer.Code;
@ -80,7 +79,7 @@ class InternalCheck extends DefaultCheck {
return result;
}
void printResults(final Map<Path, Result> results) {
void printResults(final Map<String, Result> results) {
final PrintWriter writer = new PrintWriter(System.out);// NOSONAR
writer.write("Results:\n");
writer.write(createResultGrid(results).render());
@ -105,14 +104,18 @@ class InternalCheck extends DefaultCheck {
}
@Override
public boolean isSuccessful(final Map<Path, Result> results) {
public boolean isSuccessful(final Map<String, Result> results) {
if (this.checkAssertions > 0) {
return this.failedAssertions == 0;
}
return super.isSuccessful(results);
}
private static String createStatusLine(final Map<Path, Result> results) {
public int getNotAcceptableCount(final Map<String, Result> results) {
return (int) (this.failedAssertions + results.values().stream().filter(e -> !e.isAcceptable()).count());
}
private static String createStatusLine(final Map<String, Result> results) {
final long acceptable = results.entrySet().stream().filter(e -> e.getValue().isAcceptable()).count();
final long rejected = results.entrySet().stream().filter(e -> !e.getValue().isAcceptable()).count();
final long errors = results.entrySet().stream().filter(e -> !e.getValue().isProcessingSuccessful()).count();
@ -125,7 +128,7 @@ class InternalCheck extends DefaultCheck {
return line.render(true, false);
}
private static Grid createResultGrid(final Map<Path, Result> results) {
private static Grid createResultGrid(final Map<String, Result> results) {
final Grid grid = new Grid(
//@formatter:off
new ColumnDefinition("filename", 60, 10, 1),
@ -135,11 +138,11 @@ class InternalCheck extends DefaultCheck {
new ColumnDefinition("Error/Description", 60,20,3)
);
//@formatter:on
results.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey().getFileName())).forEach(e -> {
results.entrySet().stream().sorted(Entry.comparingByKey()).forEach(e -> {
final Result value = e.getValue();
final Code textcolor = value.isAcceptable() ? Code.GREEN : Code.RED;
grid.addCell(e.getKey().getFileName(), textcolor);
grid.addCell(e.getKey(), textcolor);
grid.addCell(value.isSchemaValid() ? "Y" : "N", textcolor);
grid.addCell(value.isSchematronValid() ? "Y" : "N", textcolor);
grid.addCell(value.getAcceptRecommendation(), textcolor);

View file

@ -0,0 +1,28 @@
package de.kosit.validationtool.cmd;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* CLI return codes.
*
* @author Andreas Penski
*/
@RequiredArgsConstructor
@Getter
public class ReturnValue {
public static final ReturnValue SUCCESS = new ReturnValue(0);
public static final ReturnValue CONFIGURATION_ERROR = new ReturnValue(-2);
public static final ReturnValue DAEMON_MODE = new ReturnValue(-100);
public static final ReturnValue PARSING_ERROR = new ReturnValue(-1);;
private final int code;
public static ReturnValue createFailed(final int count) {
return new ReturnValue(count);
}
}

View file

@ -1,7 +1,6 @@
package de.kosit.validationtool.cmd;
import static de.kosit.validationtool.cmd.CommandLineOptions.CHECK_ASSERTIONS;
import static de.kosit.validationtool.cmd.CommandLineOptions.DAEMON_SIGNAL;
import static de.kosit.validationtool.cmd.CommandLineOptions.DEBUG;
import static de.kosit.validationtool.cmd.CommandLineOptions.DISABLE_GUI;
import static de.kosit.validationtool.cmd.CommandLineOptions.EXTRACT_HTML;
@ -75,20 +74,29 @@ public class Validator {
*
* @param cmd parsed commandline.
*/
static int mainProgram(final CommandLine cmd) {
static ReturnValue mainProgram(final CommandLine cmd) {
greeting();
final org.apache.commons.cli.Options options = createOptions();
int returnValue = 0;
final ReturnValue returnValue;
try {
if (cmd.hasOption(SERVER.getOpt())) {
returnValue = startDaemonMode(cmd);
} else if (cmd.hasOption(HELP.getOpt()) || cmd.getArgList().isEmpty()) {
printHelp(options);
} else if (cmd.getArgList().isEmpty()) {
startDaemonMode(cmd);
returnValue = ReturnValue.DAEMON_MODE;
} else if (cmd.hasOption(HELP.getOpt()) || (cmd.getArgList().isEmpty() && !isPiped())) {
printHelp(options);
returnValue = ReturnValue.PARSING_ERROR;
} else {
returnValue = processActions(cmd);
}
} catch (final Exception e) {
Printer.writeErr(e.getMessage());
if (cmd.hasOption(DEBUG.getOpt())) {
log.error(e.getMessage(), e);
} else {
log.error(e.getMessage());
}
return ReturnValue.CONFIGURATION_ERROR;
}
return returnValue;
}
@ -120,7 +128,7 @@ public class Validator {
return host;
}
private static int startDaemonMode(final CommandLine cmd) {
private static void startDaemonMode(final CommandLine cmd) {
final Option[] unavailable = new Option[] { PRINT, CHECK_ASSERTIONS, DEBUG, OUTPUT, EXTRACT_HTML, REPORT_POSTFIX, REPORT_PREFIX };
warnUnusedOptions(cmd, unavailable, true);
final ConfigurationLoader config = getConfiguration(cmd);
@ -132,7 +140,6 @@ public class Validator {
printScenarios(configuration);
Printer.writeOut("\nStarting daemon mode ...");
validDaemon.startServer(configuration);
return DAEMON_SIGNAL;
}
private static void warnUnusedOptions(final CommandLine cmd, final Option[] unavailable, final boolean daemon) {
@ -143,9 +150,7 @@ public class Validator {
}
}
private static int processActions(final CommandLine cmd) {
try {
private static ReturnValue processActions(final CommandLine cmd) throws IOException {
long start = System.currentTimeMillis();
final Option[] unavailable = new Option[] { HOST, PORT, WORKER_COUNT, DISABLE_GUI };
warnUnusedOptions(cmd, unavailable, false);
@ -175,14 +180,13 @@ public class Validator {
}
log.info("Setup completed in {}ms\n", System.currentTimeMillis() - start);
final Collection<Path> targets = determineTestTargets(cmd);
final Collection<Input> targets = determineTestTargets(cmd);
start = System.currentTimeMillis();
final Map<Path, Result> results = new HashMap<>();
final Map<String, Result> results = new HashMap<>();
Printer.writeOut("\nProcessing of {0} objects started", targets.size());
long tick = System.currentTimeMillis();
for (final Path p : targets) {
final Input input = InputFactory.read(p);
results.put(p, check.checkInput(input));
for (final Input input : targets) {
results.put(input.getName(), check.checkInput(input));
if (((System.currentTimeMillis() - tick) / 1000) > 5) {
tick = System.currentTimeMillis();
Printer.writeOut("{0}/{1} objects processed", results.size(), targets.size());
@ -193,18 +197,9 @@ public class Validator {
check.printResults(results);
log.info("Processing {} object(s) completed in {}ms", targets.size(), processingTime);
return check.isSuccessful(results) ? 0 : 1;
return check.isSuccessful(results) ? ReturnValue.SUCCESS : ReturnValue.createFailed(check.getNotAcceptableCount(results));
}
} catch (final Exception e) {
Printer.writeErr(e.getMessage());
if (cmd.hasOption(DEBUG.getOpt())) {
log.error(e.getMessage(), e);
} else {
log.error(e.getMessage());
}
return -1;
}
}
private static ConfigurationLoader getConfiguration(final CommandLine cmd) {
final URI scenarioLocation = determineDefinition(cmd);
@ -265,32 +260,44 @@ public class Validator {
return fir;
}
private static Collection<Path> determineTestTargets(final CommandLine cmd) {
final Collection<Path> targets = new ArrayList<>();
private static Collection<Input> determineTestTargets(final CommandLine cmd) throws IOException {
final Collection<Input> targets = new ArrayList<>();
if (!cmd.getArgList().isEmpty()) {
cmd.getArgList().forEach(e -> targets.addAll(determineTestTarget(e)));
}
if (isPiped()) {
targets.add(readFromPipe());
}
if (targets.isEmpty()) {
throw new IllegalStateException("No test targets found. Nothing to check. Will quit now!");
}
return targets;
}
private static Collection<Path> determineTestTarget(final String s) {
private static boolean isPiped() throws IOException {
return System.in.available() > 0;
}
private static Input readFromPipe() {
return InputFactory.read(System.in, "stdin");
}
private static Collection<Input> determineTestTarget(final String s) {
final Path d = Paths.get(s);
if (Files.isDirectory(d)) {
return listDirectoryTargets(d);
} else if (Files.exists(d)) {
return Collections.singleton(d);
return Collections.singleton(InputFactory.read(d));
}
log.warn("The specified test target {} does not exist. Will be ignored", s);
return Collections.emptyList();
}
private static Collection<Path> listDirectoryTargets(final Path d) {
private static Collection<Input> listDirectoryTargets(final Path d) {
try ( final Stream<Path> stream = Files.list(d) ) {
return stream.filter(path -> path.toString().toLowerCase().endsWith(".xml")).collect(Collectors.toList());
return stream.filter(path -> path.toString().toLowerCase().endsWith(".xml")).map(InputFactory::read)
.collect(Collectors.toList());
} catch (final IOException e) {
throw new IllegalStateException("IOException while list directory content. Can not determine test targets.", e);
}

View file

@ -15,15 +15,20 @@ abstract class BaseHandler implements HttpHandler {
protected static final String APPLICATION_XML = "application/xml";
static final int OK = 200;
protected static void write(final HttpExchange exchange, final byte[] content, final String contentType) throws IOException {
write(exchange, contentType, os -> os.write(content));
write(exchange, content, contentType, HttpStatus.SC_OK);
}
protected static void write(final HttpExchange exchange, final String contentType, final Write write) throws IOException {
protected static void write(final HttpExchange exchange, final byte[] content, final String contentType, final int statusCode)
throws IOException {
write(exchange, contentType, os -> os.write(content), statusCode);
}
protected static void write(final HttpExchange exchange, final String contentType, final Write write, final int statusCode)
throws IOException {
exchange.getResponseHeaders().add("Content-Type", contentType);
exchange.sendResponseHeaders(OK, 0);
exchange.sendResponseHeaders(statusCode, 0);
final OutputStream os = exchange.getResponseBody();
write.write(os);
os.close();

View file

@ -3,6 +3,7 @@ package de.kosit.validationtool.daemon;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.concurrent.atomic.AtomicLong;
import com.sun.net.httpserver.HttpExchange;
@ -49,21 +50,36 @@ class CheckHandler extends BaseHandler {
final InputStream inputStream = httpExchange.getRequestBody();
if (inputStream.available() > 0) {
final SourceInput serverInput = (SourceInput) InputFactory.read(inputStream,
"supplied_instance_" + counter.incrementAndGet());
resolveInputName(httpExchange.getRequestURI()));
final Result result = this.implemenation.checkInput(serverInput);
write(httpExchange, serialize(result), APPLICATION_XML);
write(httpExchange, serialize(result), APPLICATION_XML, resolveStatus(result));
} else {
error(httpExchange, 400, "No content supplied");
error(httpExchange, HttpStatus.SC_BAD_REQUEST, "No content supplied");
}
} else {
error(httpExchange, 405, "Method not supported");
error(httpExchange, HttpStatus.SC_METHOD_NOT_ALLOWED, "Method not supported");
}
} catch (final Exception e) {
error(httpExchange, 500, "Internal error: " + e.getMessage());
error(httpExchange, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal error: " + e.getMessage());
}
}
private static String resolveInputName(final URI requestURI) {
final String path = requestURI.getPath();
if (path.equalsIgnoreCase("/")) {
return "supplied_instance_" + counter.incrementAndGet();
}
return path.substring((path.lastIndexOf('/') + 1));
}
private static int resolveStatus(final Result result) {
if (result.isProcessingSuccessful()) {
return result.isAcceptable() ? HttpStatus.SC_OK : HttpStatus.SC_NOT_ACCEPTABLE;
}
return HttpStatus.SC_UNPROCESSABLE_ENTITY;
}
private byte[] serialize(final Result result) {
try ( final ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
final Serializer serializer = this.processor.newSerializer(out);

View file

@ -0,0 +1,32 @@
package de.kosit.validationtool.daemon;
/**
* Status codes for the HTTP daemon.
*
* @author Andreas Penski
*/
public interface HttpStatus {
// --- 2xx Success ---
/** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
int SC_OK = 200;
// --- 4xx Client Error ---
/** {@code 400 Bad Request} (HTTP/1.1 - RFC 2616) */
int SC_BAD_REQUEST = 400;
/** {@code 405 Method Not Allowed} (HTTP/1.1 - RFC 2616) */
int SC_METHOD_NOT_ALLOWED = 405;
/** {@code 406 Not Acceptable} (HTTP/1.1 - RFC 2616) */
int SC_NOT_ACCEPTABLE = 406;
/** {@code 422 Unprocessable Entity} (WebDAV - RFC 2518) */
public static final int SC_UNPROCESSABLE_ENTITY = 422;
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
int SC_INTERNAL_SERVER_ERROR = 500;
}

View file

@ -21,7 +21,6 @@ package de.kosit.validationtool.impl;
import static de.kosit.validationtool.impl.DateFactory.createTimestamp;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -103,7 +102,7 @@ public class DefaultCheck implements Check {
return type;
}
protected boolean isSuccessful(final Map<Path, Result> results) {
protected boolean isSuccessful(final Map<String, Result> results) {
return results.entrySet().stream().allMatch(e -> e.getValue().isAcceptable());
}

View file

@ -53,14 +53,14 @@ public class CheckAssertionActionTest {
@Before
public void setup() throws IOException {
this.commandLine = new CommandLine();
this.commandLine.activate();
CommandLine.activate();
}
@Test
public void testEmptyInput() {
final CheckAssertionAction a = new CheckAssertionAction(new Assertions(), TestObjectFactory.createProcessor());
a.check(new CheckAction.Bag(InputFactory.read(SAMPLE), new CreateReportInput()));
assertThat(this.commandLine.getErrorOutput()).contains("Can not find assertions for");
assertThat(CommandLine.getErrorOutput()).contains("Can not find assertions for");
}
@Test
@ -72,6 +72,6 @@ public class CheckAssertionActionTest {
final CheckAssertionAction a = new CheckAssertionAction(assertions, TestObjectFactory.createProcessor());
a.check(bag);
assertThat(this.commandLine.getErrorOutput()).contains("Assertion mismatch");
assertThat(CommandLine.getErrorOutput()).contains("Assertion mismatch");
}
}

View file

@ -19,7 +19,14 @@
package de.kosit.validationtool.cmd;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.util.List;
import org.apache.commons.io.IOUtils;
@ -35,55 +42,6 @@ import lombok.Setter;
*/
public class CommandLine {
private static ReplaceableOutputStream<ByteArrayOutputStream> out = new ReplaceableOutputStream<>();
private static ReplaceableOutputStream<ByteArrayOutputStream> error = new ReplaceableOutputStream<>();
static {
// Initialisierung muss vor SL4J's SimpleLogger erfolgen, sonst sind logs nicht erfasst.
// deshalb darf diese Klasse kein Log haben
System.setOut(new PrintStream(new TeeOutputStream(System.out, out)));
System.setErr(new PrintStream(new TeeOutputStream(System.err, error)));
}
public String getOutput() {
return new String(out.getOut().toByteArray());
}
public String getErrorOutput() {
return new String(error.getOut().toByteArray());
}
public List<String> getOutputLines() {
return readLines(out.getOut().toByteArray());
}
public List<String> getErrorLines() {
return readLines(error.getOut().toByteArray());
}
private List<String> readLines(byte[] bytes) {
try ( ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Reader r = new InputStreamReader(in) ) {
return IOUtils.readLines(r);
} catch (IOException e) {
throw new IllegalStateException("Can not read input");
}
}
public void activate() {
out.setOut(new ByteArrayOutputStream());
error.setOut(new ByteArrayOutputStream());
}
public void deactivate() {
out.setOut(null);
error.setOut(null);
}
/**
* Simpler Proxy für {@link OutputStream}, dessen target ausgetauscht werden kann.
*
@ -95,36 +53,105 @@ public class CommandLine {
@Setter
private O out;
public void write(int idx) throws IOException {
if (out != null) {
@Override
public void write(final int idx) throws IOException {
if (this.out != null) {
this.out.write(idx);
}
}
public void write(byte[] bts) throws IOException {
if (out != null) {
@Override
public void write(final byte[] bts) throws IOException {
if (this.out != null) {
this.out.write(bts);
}
}
public void write(byte[] bts, int st, int end) throws IOException {
if (out != null) {
@Override
public void write(final byte[] bts, final int st, final int end) throws IOException {
if (this.out != null) {
this.out.write(bts, st, end);
}
}
@Override
public void flush() throws IOException {
if (out != null) {
if (this.out != null) {
this.out.flush();
}
}
@Override
public void close() throws IOException {
if (out != null) {
if (this.out != null) {
this.out.close();
}
}
}
private static final ReplaceableOutputStream<ByteArrayOutputStream> out = new ReplaceableOutputStream<>();
private static final ReplaceableOutputStream<ByteArrayOutputStream> error = new ReplaceableOutputStream<>();
static {
// Initialisierung muss vor SL4J's SimpleLogger erfolgen, sonst sind logs nicht erfasst.
// deshalb darf diese Klasse kein Log haben
System.setOut(new PrintStream(new TeeOutputStream(System.out, out)));
System.setErr(new PrintStream(new TeeOutputStream(System.err, error)));
setStandardInput(nullInputStream());
}
public static void setStandardInput(final InputStream in) {
System.setIn(in);
}
public static InputStream nullInputStream() {
return new InputStream() {
@Override
public int read() throws IOException {
return 0;
}
};
}
public static String getOutput() {
return new String(out.getOut().toByteArray());
}
public static String getErrorOutput() {
return new String(error.getOut().toByteArray());
}
public List<String> getOutputLines() {
return readLines(out.getOut().toByteArray());
}
public List<String> getErrorLines() {
return readLines(error.getOut().toByteArray());
}
private List<String> readLines(final byte[] bytes) {
try ( final ByteArrayInputStream in = new ByteArrayInputStream(bytes);
final Reader r = new InputStreamReader(in) ) {
return IOUtils.readLines(r);
} catch (final IOException e) {
throw new IllegalStateException("Can not read input");
}
}
public static void activate() {
out.setOut(new ByteArrayOutputStream());
error.setOut(new ByteArrayOutputStream());
}
public static void deactivate() {
out.setOut(null);
error.setOut(null);
setStandardInput(nullInputStream());
}
}

View file

@ -56,7 +56,7 @@ public class CommandlineApplicationTest {
@Before
public void setup() throws IOException {
this.commandLine = new CommandLine();
this.commandLine.activate();
CommandLine.activate();
if (Files.exists(this.output)) {
FileUtils.deleteDirectory(this.output.toFile());
}
@ -71,13 +71,14 @@ public class CommandlineApplicationTest {
log.error("Error deleting file", e);
}
});
CommandLine.deactivate();
}
@Test
public void testHelp() {
final String[] args = new String[] { "-?" };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).isEmpty();
assertThat(CommandLine.getErrorOutput()).isEmpty();
checkForHelp(this.commandLine.getOutputLines());
}
@ -90,24 +91,24 @@ public class CommandlineApplicationTest {
public void testRequiredScenarioFile() {
final String[] args = new String[] { "-d", "arguments", "egal welche", "argument drin sind" };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).isNotEmpty();
assertThat(this.commandLine.getErrorOutput()).contains("Missing required option: s");
assertThat(CommandLine.getErrorOutput()).isNotEmpty();
assertThat(CommandLine.getErrorOutput()).contains("Missing required option: s");
}
@Test
public void testNotExistingScenarioFile() {
final String[] args = new String[] { "-s", Paths.get(Simple.NOT_EXISTING).toString(), Paths.get(Simple.NOT_EXISTING).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).isNotEmpty();
assertThat(this.commandLine.getErrorOutput()).contains("Not a valid path for scenario definition specified");
assertThat(CommandLine.getErrorOutput()).isNotEmpty();
assertThat(CommandLine.getErrorOutput()).contains("Not a valid path for scenario definition specified");
}
@Test
public void testIncorrectRepository() {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), Paths.get(Simple.NOT_EXISTING).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).isNotEmpty();
assertThat(this.commandLine.getErrorOutput()).contains("Can not resolve");
assertThat(CommandLine.getErrorOutput()).isNotEmpty();
assertThat(CommandLine.getErrorOutput()).contains("Can not resolve");
}
@Test
@ -115,8 +116,8 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.NOT_EXISTING).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).isNotEmpty();
assertThat(this.commandLine.getErrorOutput()).contains("No test targets found");
assertThat(CommandLine.getErrorOutput()).isNotEmpty();
assertThat(CommandLine.getErrorOutput()).contains("No test targets found");
}
@Test
@ -124,7 +125,7 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
}
@Test
@ -133,8 +134,8 @@ public class CommandlineApplicationTest {
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString(), "--report-prefix", "somePrefix",
"--report-postfix", "somePostfix" };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.commandLine.getErrorOutput()).contains("somePrefix-simple-somePostfix");
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(CommandLine.getErrorOutput()).contains("somePrefix-simple-somePostfix");
}
@Test
@ -142,7 +143,7 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-o", this.output.toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString(), Paths.get(Simple.FOO).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains("Processing 2 object(s) completed");
assertThat(CommandLine.getErrorOutput()).contains("Processing 2 object(s) completed");
}
@Test
@ -150,7 +151,7 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-o", this.output.toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.EXAMPLES).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains("Processing 8 object(s) completed");
assertThat(CommandLine.getErrorOutput()).contains("Processing 8 object(s) completed");
}
@Test
@ -159,7 +160,7 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-o", this.output.toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.output).exists();
assertThat(Files.list(this.output)).hasSize(1);
}
@ -179,7 +180,7 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-p", "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), "-o", this.output.toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.commandLine.getOutputLines()).haveAtLeastOne(new Condition<>(
s -> StringUtils.contains(s, "<?xml version=\"1.0\" " + "encoding=\"UTF-8\"?>"), "Must " + "contain xml preambel"));
}
@ -190,7 +191,7 @@ public class CommandlineApplicationTest {
this.output.toAbsolutePath().toString(), "-r", Paths.get(Simple.REPOSITORY_URI).toString(),
Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(Files.list(this.output).filter(f -> f.toString().endsWith(".html")).count()).isGreaterThan(0);
}
@ -200,8 +201,8 @@ public class CommandlineApplicationTest {
Paths.get(Simple.REPOSITORY_URI).toString(), "-o", this.output.toString(), "-c", Paths.get(ASSERTIONS).toString(),
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.commandLine.getErrorOutput()).contains("Can not find assertions for ");
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(CommandLine.getErrorOutput()).contains("Can not find assertions for ");
}
@Test
@ -209,7 +210,7 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r", "unknown", "-o", this.output.toString(),
"-d", Paths.get(ASSERTIONS).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains("at de.kosit.validationtool");
assertThat(CommandLine.getErrorOutput()).contains("at de.kosit.validationtool");
}
@Test
@ -217,7 +218,16 @@ public class CommandlineApplicationTest {
final String[] args = new String[] { "-m", "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString(), Paths.get(Simple.SIMPLE_VALID).toString() };
CommandLineApplication.mainProgram(args);
assertThat(this.commandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(this.commandLine.getErrorOutput()).contains("total");
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
assertThat(CommandLine.getErrorOutput()).contains("total");
}
@Test
public void testReadFromPipe() throws IOException {
final String[] args = new String[] { "-s", Paths.get(Simple.SCENARIOS).toString(), "-r",
Paths.get(Simple.REPOSITORY_URI).toString() };
CommandLine.setStandardInput(Files.newInputStream(Paths.get(Simple.SIMPLE_VALID)));
CommandLineApplication.mainProgram(args);
assertThat(CommandLine.getErrorOutput()).contains(RESULT_OUTPUT);
}
}

View file

@ -46,13 +46,13 @@ public class PrintReportActionTest {
@Before
public void setup() {
this.commandLine = new CommandLine();
this.commandLine.activate();
CommandLine.activate();
this.action = new PrintReportAction(TestObjectFactory.createProcessor());
}
@After
public void tearDown() {
this.commandLine.deactivate();
CommandLine.deactivate();
}
@Test
@ -62,9 +62,9 @@ public class PrintReportActionTest {
assertThat(this.action.isSkipped(b)).isFalse();
this.action.check(b);
assertThat(b.isStopped()).isFalse();
assertThat(this.commandLine.getOutput()).isNotEmpty();
assertThat(this.commandLine.getOutput()).contains("<?xml version=\"1.0\" ");
assertThat(this.commandLine.getErrorOutput()).isEmpty();
assertThat(CommandLine.getOutput()).isNotEmpty();
assertThat(CommandLine.getOutput()).contains("<?xml version=\"1.0\" ");
assertThat(CommandLine.getErrorOutput()).isEmpty();
}
}

View file

@ -1,6 +1,7 @@
package de.kosit.validationtool.daemon;
import static io.restassured.RestAssured.given;
import static org.apache.http.HttpStatus.SC_NOT_ACCEPTABLE;
import static org.apache.http.HttpStatus.SC_OK;
import java.io.IOException;
@ -41,7 +42,7 @@ public class CheckHandlerIT extends BaseIT {
@Test
public void testUnknown() throws IOException {
try ( final InputStream io = Simple.UNKNOWN.toURL().openStream() ) {
given().contentType(APPLICATION_XML).body(toContent(io)).when().post("/").then().statusCode(SC_OK);
given().contentType(APPLICATION_XML).body(toContent(io)).when().post("/").then().statusCode(SC_NOT_ACCEPTABLE);
}
}

View file

@ -38,7 +38,7 @@ public class ConfigHandlerTest {
final Configuration config = TestConfigurationFactory.createSimpleConfiguration().build();
final ConfigHandler handler = new ConfigHandler(config, new ConversionService());
handler.handle(exchange);
verify(exchange, times(1)).sendResponseHeaders(ConfigHandler.OK, 0);
verify(exchange, times(1)).sendResponseHeaders(HttpStatus.SC_OK, 0);
verify(stream, atLeast(1)).write(any());
}
@ -55,6 +55,6 @@ public class ConfigHandlerTest {
handler.handle(exchange);
verify(headers, times(1)).add(any(), any());
verify(stream, atLeast(1)).write(any());
assertThat(valueCapture.getValue()).isEqualTo(500);
assertThat(valueCapture.getValue()).isEqualTo(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}
}