class PlainAsyncCmdExecutor implements AsyncCmdExecutor {

    @Override
    public SingleProcessExecution<String> launchCommand(final String command,
                                                        final Map<String, String> environmentVariables,
                                                        final File workDir) {
        final Process process = cmdExecutor.launchCommand(command, environmentVariables, workDir);
        final CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            final StringBuilder output = new StringBuilder();
            final StringBuilder errors = new StringBuilder();
            try {
                final Thread stdReader = new Thread(() -> readOutputStream(command, output,
                        new InputStreamReader(process.getInputStream())));
                final Thread errReader = new Thread(() -> readOutputStream(command, errors,
                        new InputStreamReader(process.getErrorStream())));
                stdReader.start();
                errReader.start();
                final int exitCode = process.waitFor();
                stdReader.join();
                errReader.join();
                if (exitCode == 0) {
                    return output.toString();
                } else {
                    final String errorMessage = String.format("Command '%s' failed with the following stderr: %s",
                            command, errors.toString());
                    log.error(errorMessage);
                    throw new CmdExecutionException(errorMessage);
                }
            } catch (InterruptedException e) {
                throw new CmdExecutionException(String.format("Command '%s' execution was interrupted", command));
            }
        }, executor);
        return new SingleProcessExecution<>(process, future);
    }

}