diff --git a/maven-batch-executor/src/main/java/org/apache/maven/executor/batch/internal/InternalStepContext.java b/maven-batch-executor/src/main/java/org/apache/maven/executor/batch/internal/InternalStepContext.java
index aa31cb5..a68c9db 100644
--- a/maven-batch-executor/src/main/java/org/apache/maven/executor/batch/internal/InternalStepContext.java
+++ b/maven-batch-executor/src/main/java/org/apache/maven/executor/batch/internal/InternalStepContext.java
@@ -18,7 +18,6 @@
*/
package org.apache.maven.executor.batch.internal;
-import java.io.ByteArrayOutputStream;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
@@ -28,6 +27,7 @@
import org.apache.maven.executor.Executor;
import org.apache.maven.executor.ExecutorException;
import org.apache.maven.executor.ExecutorRequest;
+import org.apache.maven.executor.ExecutorResult;
import org.apache.maven.executor.ExecutorTool;
import org.apache.maven.executor.batch.steps.Environment;
import org.apache.maven.executor.batch.steps.Execution;
@@ -97,24 +97,22 @@ public Execution.Result execute(Execution.Request execution) throws ExecutorExce
execution.environmentVariables().ifPresent(ev -> ev.forEach(builder::environmentVariable));
execution.jvmSystemProperties().ifPresent(sp -> sp.forEach(builder::jvmSystemProperty));
execution.jvmArguments().ifPresent(ja -> ja.forEach(builder::jvmArgument));
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- ByteArrayOutputStream err = new ByteArrayOutputStream();
- int exitCode = executor.execute(builder.stdOut(out).stdErr(err).build());
+ ExecutorResult res = executor.execute(builder.build());
Execution.Result result = new Execution.Result() {
@Override
public int exitCode() {
- return exitCode;
+ return res.exitCode().orElseThrow();
}
@Override
public String stdOut() {
- return out.toString();
+ return res.stdOutString().orElseThrow();
}
@Override
public String stdErr() {
- return err.toString();
+ return res.stdErrString().orElseThrow();
}
};
resultList.add(result);
diff --git a/maven-batch-executor/src/test/java/org/apache/maven/executor/batch/BatchExecutorTest.java b/maven-batch-executor/src/test/java/org/apache/maven/executor/batch/BatchExecutorTest.java
index ab47f3c..8515f12 100644
--- a/maven-batch-executor/src/test/java/org/apache/maven/executor/batch/BatchExecutorTest.java
+++ b/maven-batch-executor/src/test/java/org/apache/maven/executor/batch/BatchExecutorTest.java
@@ -21,9 +21,6 @@
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
@@ -32,11 +29,13 @@
import org.apache.maven.executor.Executor;
import org.apache.maven.executor.ExecutorException;
import org.apache.maven.executor.ExecutorRequest;
+import org.apache.maven.executor.ExecutorResult;
import org.apache.maven.executor.batch.steps.ContextStep;
import org.apache.maven.executor.batch.steps.Environment;
import org.apache.maven.executor.batch.steps.ExecuteStep;
import org.apache.maven.executor.batch.steps.Execution;
import org.apache.maven.executor.batch.steps.Step;
+import org.apache.maven.executor.support.SimpleExecutionResult;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -48,16 +47,8 @@ public class BatchExecutorTest {
void smoke() {
Executor executor = new Executor() {
@Override
- public int execute(ExecutorRequest executorRequest) throws ExecutorException {
- try {
- executorRequest
- .stdOut()
- .orElse(OutputStream.nullOutputStream())
- .write("Executed!".getBytes(StandardCharsets.UTF_8));
- return 0;
- } catch (IOException e) {
- throw new ExecutorException(e);
- }
+ public ExecutorResult execute(ExecutorRequest executorRequest) throws ExecutorException {
+ return new SimpleExecutionResult(executorRequest, true, 0, "Executed", null);
}
@Override
diff --git a/maven-executor/pom.xml b/maven-executor/pom.xml
index 522817a..5a8c8da 100644
--- a/maven-executor/pom.xml
+++ b/maven-executor/pom.xml
@@ -35,7 +35,8 @@
- 0.15.8
+ 8
+ 0.15.9
@@ -53,6 +54,25 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile-java17
+
+ compile
+
+ compile
+
+ 17
+
+ ${project.basedir}/src/main/java17
+
+
+
+
+
org.apache.maven.plugins
maven-dependency-plugin
diff --git a/maven-executor/src/main/java/org/apache/maven/executor/Executor.java b/maven-executor/src/main/java/org/apache/maven/executor/Executor.java
index 906d657..5cc9b8d 100644
--- a/maven-executor/src/main/java/org/apache/maven/executor/Executor.java
+++ b/maven-executor/src/main/java/org/apache/maven/executor/Executor.java
@@ -38,10 +38,10 @@ public interface Executor extends AutoCloseable {
* process based on the information contained in the request.
*
* @param executorRequest the request containing all necessary information for the execution
- * @return an integer representing the exit code of the execution (0 typically indicates success)
+ * @return ExecutorResult carrying the result of the execution
* @throws ExecutorException if an error occurs during the execution process
*/
- int execute(ExecutorRequest executorRequest) throws ExecutorException;
+ ExecutorResult execute(ExecutorRequest executorRequest) throws ExecutorException;
/**
* Returns the Maven version that this executor points at (would use). This operation, depending on the underlying
diff --git a/maven-executor/src/main/java/org/apache/maven/executor/ExecutorHelper.java b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorHelper.java
index 3bd41c3..1d0a1e9 100644
--- a/maven-executor/src/main/java/org/apache/maven/executor/ExecutorHelper.java
+++ b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorHelper.java
@@ -84,12 +84,12 @@ enum Mode {
/**
* Executes the request with preferred mode executor.
*/
- default int execute(ExecutorRequest executorRequest) throws ExecutorException {
+ default ExecutorResult execute(ExecutorRequest executorRequest) throws ExecutorException {
return execute(getDefaultMode(), executorRequest);
}
/**
* Executes the request with passed in mode executor.
*/
- int execute(Mode mode, ExecutorRequest executorRequest) throws ExecutorException;
+ ExecutorResult execute(Mode mode, ExecutorRequest executorRequest) throws ExecutorException;
}
diff --git a/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java
index 94435fe..e5a54dd 100644
--- a/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java
+++ b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorRequest.java
@@ -22,8 +22,10 @@
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -93,9 +95,16 @@ public interface ExecutorRequest {
*/
Optional> jvmArguments();
+ /**
+ * Whether execution outputs (STDOUT and STDERR) should be captured as plain {@link String}.
+ * By default, this is {@code true}, unless client manually sets any of {@link #stdOut()} or {@link #stdErr()}
+ * streams, in which case this value is set to {@code false} and caller must handle these streams manually.
+ */
+ boolean grabOutputAsString();
+
/**
* Optional provider for STD in of the Maven. If given, this provider will be piped into std input of
- * Maven.
+ * Maven. The stream is closed once tool execution is finished.
*
* @return an Optional containing the stdin provider, or empty if not specified.
*/
@@ -105,6 +114,7 @@ public interface ExecutorRequest {
* Optional consumer for STD out of the Maven. If given, this consumer will get all output from the std out of
* Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in
* {@link #arguments()}, as if log file is set, not much will go to stdout.
+ * The stream is closed once tool execution is finished.
*
* @return an Optional containing the stdout consumer, or empty if not specified.
*/
@@ -114,6 +124,7 @@ public interface ExecutorRequest {
* Optional consumer for STD err of the Maven. If given, this consumer will get all output from the std err of
* Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in
* {@link #arguments()}, as if log file is set, not much will go to stderr.
+ * The stream is closed once tool execution is finished.
*
* @return an Optional containing the stderr consumer, or empty if not specified.
*/
@@ -126,6 +137,13 @@ public interface ExecutorRequest {
*/
boolean skipMavenRc();
+ /**
+ * The optional execution time limit. If set, and execution does not finish within the given time, it is considered
+ * failed and killed. If not set, no time limit is applied. Depending on implementation, the timeout detection may
+ * be imprecise.
+ */
+ Optional executionTimeout();
+
/**
* Returns {@link Builder} created from this instance.
*/
@@ -138,10 +156,12 @@ default Builder toBuilder() {
jvmSystemProperties().orElse(null),
environmentVariables().orElse(null),
jvmArguments().orElse(null),
+ grabOutputAsString(),
stdIn().orElse(null),
stdOut().orElse(null),
stdErr().orElse(null),
- skipMavenRc());
+ skipMavenRc(),
+ executionTimeout().orElse(null));
}
/**
@@ -157,10 +177,12 @@ static Builder mavenBuilder() {
null,
null,
null,
+ true,
null,
null,
null,
- false);
+ false,
+ null);
}
class Builder {
@@ -171,10 +193,12 @@ class Builder {
private Map jvmSystemProperties;
private Map environmentVariables;
private List jvmArguments;
+ private boolean grabOutputAsString;
private InputStream stdIn;
private OutputStream stdOut;
private OutputStream stdErr;
private boolean skipMavenRc;
+ private Duration executionTimeout;
private Builder() {}
@@ -187,10 +211,12 @@ private Builder(
Map jvmSystemProperties,
Map environmentVariables,
List jvmArguments,
+ boolean grabOutputAsString,
InputStream stdIn,
OutputStream stdOut,
OutputStream stdErr,
- boolean skipMavenRc) {
+ boolean skipMavenRc,
+ Duration executionTimeout) {
this.command = command;
this.arguments = arguments;
this.cwd = cwd;
@@ -198,10 +224,12 @@ private Builder(
this.jvmSystemProperties = jvmSystemProperties;
this.environmentVariables = environmentVariables;
this.jvmArguments = jvmArguments;
+ this.grabOutputAsString = grabOutputAsString;
this.stdIn = stdIn;
this.stdOut = stdOut;
this.stdErr = stdErr;
this.skipMavenRc = skipMavenRc;
+ this.executionTimeout = executionTimeout;
}
public Builder command(String command) {
@@ -279,17 +307,28 @@ public Builder jvmArgument(String jvmArgument) {
return this;
}
+ public Builder grabOutputAsString(boolean grabOutputAsString) {
+ this.grabOutputAsString = grabOutputAsString;
+ if (grabOutputAsString) {
+ this.stdOut = null;
+ this.stdErr = null;
+ }
+ return this;
+ }
+
public Builder stdIn(InputStream stdIn) {
this.stdIn = stdIn;
return this;
}
public Builder stdOut(OutputStream stdOut) {
+ this.grabOutputAsString = false;
this.stdOut = stdOut;
return this;
}
public Builder stdErr(OutputStream stdErr) {
+ this.grabOutputAsString = false;
this.stdErr = stdErr;
return this;
}
@@ -299,6 +338,11 @@ public Builder skipMavenRc(boolean skipMavenRc) {
return this;
}
+ public Builder executionTimeout(Duration executionTimeout) {
+ this.executionTimeout = executionTimeout;
+ return this;
+ }
+
public ExecutorRequest build() {
return new Impl(
command,
@@ -308,10 +352,12 @@ public ExecutorRequest build() {
jvmSystemProperties,
environmentVariables,
jvmArguments,
+ grabOutputAsString,
stdIn,
stdOut,
stdErr,
- skipMavenRc);
+ skipMavenRc,
+ executionTimeout);
}
private static class Impl implements ExecutorRequest {
@@ -322,10 +368,12 @@ private static class Impl implements ExecutorRequest {
private final Map jvmSystemProperties;
private final Map environmentVariables;
private final List jvmArguments;
+ private final boolean grabOutputAsString;
private final InputStream stdIn;
private final OutputStream stdOut;
private final OutputStream stdErr;
private final boolean skipMavenRc;
+ private final Duration executionTimeout;
@SuppressWarnings("ParameterNumber")
private Impl(
@@ -336,25 +384,33 @@ private Impl(
Map jvmSystemProperties,
Map environmentVariables,
List jvmArguments,
+ boolean grabOutputAsString,
InputStream stdIn,
OutputStream stdOut,
OutputStream stdErr,
- boolean skipMavenRc) {
+ boolean skipMavenRc,
+ Duration executionTimeout) {
this.command = requireNonNull(command);
- this.arguments = arguments == null ? List.of() : List.copyOf(arguments);
+ this.arguments = arguments == null
+ ? Collections.emptyList()
+ : Collections.unmodifiableList(new ArrayList<>(arguments));
this.cwd = getCanonicalPath(requireNonNull(cwd));
this.userHomeDirectory = getCanonicalPath(requireNonNull(userHomeDirectory));
this.jvmSystemProperties = jvmSystemProperties != null && !jvmSystemProperties.isEmpty()
- ? Map.copyOf(jvmSystemProperties)
+ ? Collections.unmodifiableMap(new HashMap<>(jvmSystemProperties))
: null;
this.environmentVariables = environmentVariables != null && !environmentVariables.isEmpty()
- ? Map.copyOf(environmentVariables)
+ ? Collections.unmodifiableMap(new HashMap<>(environmentVariables))
: null;
- this.jvmArguments = jvmArguments != null && !jvmArguments.isEmpty() ? List.copyOf(jvmArguments) : null;
+ this.jvmArguments = jvmArguments != null && !jvmArguments.isEmpty()
+ ? Collections.unmodifiableList(new ArrayList<>(jvmArguments))
+ : null;
+ this.grabOutputAsString = grabOutputAsString;
this.stdIn = stdIn;
this.stdOut = stdOut;
this.stdErr = stdErr;
this.skipMavenRc = skipMavenRc;
+ this.executionTimeout = executionTimeout;
}
@Override
@@ -392,6 +448,11 @@ public Optional> jvmArguments() {
return Optional.ofNullable(jvmArguments);
}
+ @Override
+ public boolean grabOutputAsString() {
+ return grabOutputAsString;
+ }
+
@Override
public Optional stdIn() {
return Optional.ofNullable(stdIn);
@@ -412,20 +473,27 @@ public boolean skipMavenRc() {
return skipMavenRc;
}
+ @Override
+ public Optional executionTimeout() {
+ return Optional.ofNullable(executionTimeout);
+ }
+
@Override
public String toString() {
- return getClass().getSimpleName() + "{" + "command='"
+ return "Impl{" + "command='"
+ command + '\'' + ", arguments="
+ arguments + ", cwd="
- + cwd + ", installationDirectory="
+ + cwd + ", userHomeDirectory="
+ userHomeDirectory + ", jvmSystemProperties="
+ jvmSystemProperties + ", environmentVariables="
+ environmentVariables + ", jvmArguments="
- + jvmArguments + ", stdinProvider="
- + stdIn + ", stdoutConsumer="
- + stdOut + ", stderrConsumer="
+ + jvmArguments + ", grabOutputAsString="
+ + grabOutputAsString + ", stdIn="
+ + stdIn + ", stdOut="
+ + stdOut + ", stdErr="
+ stdErr + ", skipMavenRc="
- + skipMavenRc + "}";
+ + skipMavenRc + ", executionTimeout="
+ + executionTimeout + '}';
}
}
}
diff --git a/maven-executor/src/main/java/org/apache/maven/executor/ExecutorResult.java b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorResult.java
new file mode 100644
index 0000000..f1fe8a6
--- /dev/null
+++ b/maven-executor/src/main/java/org/apache/maven/executor/ExecutorResult.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.maven.executor;
+
+import java.util.Optional;
+
+/**
+ * Represents an execution result.
+ */
+public interface ExecutorResult {
+ /**
+ * The {@link ExecutorRequest} this result is for.
+ */
+ ExecutorRequest getRequest();
+
+ /**
+ * The outcome of execution.
+ */
+ boolean success();
+
+ /**
+ * The exit code, if available (ie running the tool happened in a way it did produce exit code).
+ */
+ default Optional exitCode() {
+ return Optional.empty();
+ }
+
+ /**
+ * If {@link ExecutorRequest#grabOutputAsString()} was {@code true}, then the {@link String} containing
+ * STDOUT of tool. Never {@code null}, but maybe empty string. Otherwise, empty.
+ */
+ default Optional stdOutString() {
+ return Optional.empty();
+ }
+
+ /**
+ * If {@link ExecutorRequest#grabOutputAsString()} was {@code true}, then the {@link String} containing
+ * STDERR of tool. Never {@code null}, but maybe empty string. Otherwise, empty.
+ */
+ default Optional stdErrString() {
+ return Optional.empty();
+ }
+}
diff --git a/maven-executor/src/main/java/org/apache/maven/executor/embedded/EmbeddedMavenExecutor.java b/maven-executor/src/main/java/org/apache/maven/executor/embedded/EmbeddedMavenExecutor.java
index 3844868..eef1a04 100644
--- a/maven-executor/src/main/java/org/apache/maven/executor/embedded/EmbeddedMavenExecutor.java
+++ b/maven-executor/src/main/java/org/apache/maven/executor/embedded/EmbeddedMavenExecutor.java
@@ -18,6 +18,7 @@
*/
package org.apache.maven.executor.embedded;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
@@ -48,6 +49,8 @@
import org.apache.maven.executor.Executor;
import org.apache.maven.executor.ExecutorException;
import org.apache.maven.executor.ExecutorRequest;
+import org.apache.maven.executor.ExecutorResult;
+import org.apache.maven.executor.support.SimpleExecutionResult;
import static java.util.Objects.requireNonNull;
@@ -60,13 +63,15 @@ public class EmbeddedMavenExecutor implements Executor {
/**
* Maven4 supports multiple commands from same installation directory.
*/
- protected static final Map MVN4_MAIN_CLASSES = Map.of(
- "mvn",
- "org.apache.maven.cling.MavenCling",
- "mvnenc",
- "org.apache.maven.cling.MavenEncCling",
- "mvnsh",
- "org.apache.maven.cling.MavenShellCling");
+ protected static final Map MVN4_MAIN_CLASSES;
+
+ static {
+ Map mvn4MavenClasses = new HashMap<>();
+ mvn4MavenClasses.put("mvn", "org.apache.maven.cling.MavenCling");
+ mvn4MavenClasses.put("mvnenc", "org.apache.maven.cling.MavenEncCling");
+ mvn4MavenClasses.put("mvnsh", "org.apache.maven.cling.MavenShellCling");
+ MVN4_MAIN_CLASSES = Collections.unmodifiableMap(mvn4MavenClasses);
+ }
/**
* Context holds things loaded up from given Maven Installation Directory.
@@ -77,7 +82,7 @@ protected static class Context {
private final Object classWorld;
private final Set originalClassRealmIds;
private final ClassLoader tccl;
- private final Map> commands; // the commands
+ private final Map> commands; // the commands
private final Collection