3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.stop;
19 |
20 | import java.util.concurrent.Future;
21 |
22 | /**
23 | * Abstraction for stopping sub processes.
24 | *
25 | * This is used in case a process runs too long (timeout is reached) or it's cancelled via {@link Future#cancel(boolean)}.
26 | */
27 | public interface ProcessStopper {
28 |
29 | /**
30 | * Stops a given sub process.
31 | * It does not wait for the process to actually stop and it has no guarantee that the process terminates.
32 | *
33 | * @param process sub process being stopped (not null).
34 | */
35 | void stop(Process process);
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/InvalidExitValueException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | /**
21 | * Process finished with a forbidden exit value.
22 | *
23 | * @author Rein Raudjärv
24 | *
25 | * @see ProcessExecutor#exitValues(Integer...)
26 | */
27 | public class InvalidExitValueException extends InvalidResultException {
28 |
29 | private static final long serialVersionUID = 1L;
30 |
31 | /**
32 | * @param message the detail message of the exception
33 | * @param result result of execution (contains also the exit value)
34 | */
35 | public InvalidExitValueException(String message, ProcessResult result) {
36 | super(message, result);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/shutdown/WriterLoopStarterBeforeExit.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test.shutdown;
19 |
20 | import org.zeroturnaround.exec.ProcessExecutor;
21 |
22 | /**
23 | * Starts {@link WriterLoop} and destroys it on JVM exit.
24 | */
25 | public class WriterLoopStarterBeforeExit {
26 |
27 | private static final long SLEEP_AFTER_START = 2000;
28 |
29 | public static void main(String[] args) throws Exception {
30 | new ProcessExecutor("java", "-cp", "target/test-classes", WriterLoop.class.getName()).destroyOnExit().start();
31 | Thread.sleep(SLEEP_AFTER_START);
32 | // Cause the launched process to be destroyed
33 | System.exit(0);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/InvalidOutputException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | import org.zeroturnaround.exec.listener.ProcessListener;
21 |
22 | /**
23 | * Process finished with an unexpected output.
24 | *
25 | * @author Rein Raudjärv
26 | * @see ProcessListener#afterFinish(Process, ProcessResult)
27 | * @since 1.8
28 | */
29 | public class InvalidOutputException extends InvalidResultException {
30 |
31 | private static final long serialVersionUID = 1L;
32 |
33 | /**
34 | * @param message the detail message of the exception
35 | * @param result result of execution (contains also the exit value)
36 | */
37 | public InvalidOutputException(String message, ProcessResult result) {
38 | super(message, result);
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorArgsWithExtraSpaceTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 | import org.zeroturnaround.exec.ProcessExecutor;
23 |
24 |
25 | /**
26 | * Tests argument splitting.
27 | *
28 | * @see ProcessExecutor
29 | * @see ArgumentsAsList
30 | */
31 | public class ProcessExecutorArgsWithExtraSpaceTest {
32 |
33 | @Test
34 | public void testReadOutputAndError() throws Exception {
35 | String output = argumentsAsList("arg1 arg2 arg3").readOutput(true).execute().outputUTF8();
36 | Assert.assertEquals("[arg1, arg2, arg3]", output);
37 | }
38 |
39 | private ProcessExecutor argumentsAsList(String args) {
40 | return new ProcessExecutor().commandSplit("java -cp target/test-classes " + ArgumentsAsList.class.getName() + " " + args);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/SetFailExecuteStreamHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 | import java.io.OutputStream;
23 |
24 | import org.zeroturnaround.exec.stream.ExecuteStreamHandler;
25 |
26 |
27 | public class SetFailExecuteStreamHandler implements ExecuteStreamHandler {
28 |
29 | public void setProcessInputStream(OutputStream os) throws IOException {
30 | throw new IOException();
31 | }
32 |
33 | public void setProcessErrorStream(InputStream is) throws IOException {
34 | throw new IOException();
35 | }
36 |
37 | public void setProcessOutputStream(InputStream is) throws IOException {
38 | throw new IOException();
39 | }
40 |
41 | public void start() throws IOException {
42 | // do nothing
43 | }
44 |
45 | public void stop() {
46 | // do nothing
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/listener/DestroyerListenerAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.listener;
19 |
20 | import org.zeroturnaround.exec.ProcessExecutor;
21 |
22 | /**
23 | * Process event handler that wraps a process destroyer.
24 | *
25 | * @author Rein Raudjärv
26 | *
27 | * @see ProcessDestroyer
28 | */
29 | public class DestroyerListenerAdapter extends ProcessListener {
30 |
31 | private final ProcessDestroyer destroyer;
32 |
33 | public DestroyerListenerAdapter(ProcessDestroyer destroyer) {
34 | if (destroyer == null)
35 | throw new IllegalArgumentException("Process destroyer must be provided.");
36 | this.destroyer = destroyer;
37 | }
38 |
39 | @Override
40 | public void afterStart(Process process, ProcessExecutor executor) {
41 | destroyer.add(process);
42 | }
43 |
44 | @Override
45 | public void afterStop(Process process) {
46 | destroyer.remove(process);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/CallerLoggerUtilTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 | import org.zeroturnaround.exec.stream.CallerLoggerUtil;
23 |
24 |
25 | public class CallerLoggerUtilTest {
26 |
27 | @Test
28 | public void testFullName() throws Exception {
29 | String fullName = "my.full.Logger";
30 | Assert.assertEquals(fullName, CallerLoggerUtil.getName(fullName));
31 | }
32 |
33 | @Test
34 | public void testShortName() throws Exception {
35 | String shortName = "MyLogger";
36 | String fullName = getClass().getName() + "." + shortName;
37 | Assert.assertEquals(fullName, CallerLoggerUtil.getName(shortName));
38 | }
39 |
40 | @Test
41 | public void testMyClassName() throws Exception {
42 | String fullName = getClass().getName();
43 | Assert.assertEquals(fullName, CallerLoggerUtil.getName(null));
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/shutdown/WriterLoopStarterAfterExit.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test.shutdown;
19 |
20 | import org.zeroturnaround.exec.ProcessExecutor;
21 |
22 | /**
23 | * Starts {@link WriterLoop} inside shutdown hook and destroys it.
24 | */
25 | public class WriterLoopStarterAfterExit implements Runnable {
26 |
27 | private static final long SLEEP_AFTER_START = 2000;
28 |
29 | public static void main(String[] args) throws Exception {
30 | Runtime.getRuntime().addShutdownHook(new Thread(new WriterLoopStarterAfterExit()));
31 | // Launch the process and also destroy it
32 | System.exit(0);
33 | }
34 |
35 | public void run() {
36 | try {
37 | new ProcessExecutor("java", "-cp", "target/test-classes", WriterLoop.class.getName()).destroyOnExit().start();
38 | Thread.sleep(SLEEP_AFTER_START);
39 | }
40 | catch (Exception e) {
41 | e.printStackTrace();
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/EmptyArgTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 | import org.zeroturnaround.exec.ProcessExecutor;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Arrays;
26 | import java.util.List;
27 |
28 | /**
29 | * Tests passing empty arguments.
30 | *
31 | * @see ProcessExecutor
32 | * @see ArgumentsAsList
33 | */
34 | public class EmptyArgTest {
35 |
36 | @Test
37 | public void testReadOutputAndError() throws Exception {
38 | String output = argumentsAsList("arg1", "", "arg3", "").readOutput(true).execute().outputUTF8();
39 | Assert.assertEquals("[arg1, , arg3, ]", output);
40 | }
41 |
42 | private ProcessExecutor argumentsAsList(String... args) {
43 | List command = new ArrayList();
44 | command.addAll(Arrays.asList("java", "-cp", "target/test-classes", ArgumentsAsList.class.getName()));
45 | command.addAll(Arrays.asList(args));
46 | return new ProcessExecutor(command);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/shutdown/WriterLoop.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test.shutdown;
19 |
20 | import java.io.File;
21 | import java.io.FileWriter;
22 | import java.io.PrintWriter;
23 |
24 | /**
25 | * Program which regularly writes into a file.
26 | * By checking whether the file gets updates we know whether this program is still running or it's finished.
27 | */
28 | class WriterLoop {
29 |
30 | private static final File FILE = new File("writeLoop.data");
31 |
32 | private static final long INTERVAL = 1000;
33 | private static final long COUNT = 10;
34 |
35 | public static File getFile() {
36 | return FILE;
37 | }
38 |
39 | public static void main(String[] args) throws Exception {
40 | PrintWriter out = new PrintWriter(new FileWriter(FILE), true);
41 | try {
42 | out.println("Started");
43 | for (int i = 0; i < COUNT; i++) {
44 | out.println(i);
45 | Thread.sleep(INTERVAL);
46 | }
47 | out.println("Finished");
48 | }
49 | finally {
50 | out.close();
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/ProcessOutputTest.java:
--------------------------------------------------------------------------------
1 | package org.zeroturnaround.exec;
2 |
3 | import java.util.Arrays;
4 |
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | public class ProcessOutputTest {
9 |
10 | @Test(expected = NullPointerException.class)
11 | public void testNull() {
12 | ProcessOutput.getLinesFrom(null);
13 | }
14 |
15 | @Test
16 | public void testSimple() {
17 | Assert.assertEquals(Arrays.asList("foo"), ProcessOutput.getLinesFrom("foo"));
18 | }
19 |
20 | @Test
21 | public void testNewLine() {
22 | Assert.assertEquals(Arrays.asList("foo", "bar"), ProcessOutput.getLinesFrom("foo\nbar"));
23 | }
24 |
25 | @Test
26 | public void testNewLineWithMultipleLines() {
27 | Assert.assertEquals(Arrays.asList("foo1", "bar1", "foo2", "bar2"), ProcessOutput.getLinesFrom("foo1\nbar1\nfoo2\nbar2"));
28 | }
29 |
30 | @Test
31 | public void testCarriageReturn() {
32 | Assert.assertEquals(Arrays.asList("foo", "bar"), ProcessOutput.getLinesFrom("foo\rbar"));
33 | }
34 |
35 | @Test
36 | public void testCarriageReturnWithMultipleLines() {
37 | Assert.assertEquals(Arrays.asList("foo1", "bar1", "foo2", "bar2"), ProcessOutput.getLinesFrom("foo1\rbar1\rfoo2\rbar2"));
38 | }
39 |
40 | @Test
41 | public void testCarriageReturnAndNewLine() {
42 | Assert.assertEquals(Arrays.asList("foo", "bar"), ProcessOutput.getLinesFrom("foo\r\nbar"));
43 | }
44 |
45 | @Test
46 | public void testCarriageReturnAndNewLineWithMultipleLines() {
47 | Assert.assertEquals(Arrays.asList("foo1", "bar1", "foo2", "bar2"), ProcessOutput.getLinesFrom("foo1\r\nbar1\r\nfoo2\r\nbar2"));
48 | }
49 |
50 | @Test
51 | public void testTwoNewLines() {
52 | Assert.assertEquals(Arrays.asList("foo", "bar"), ProcessOutput.getLinesFrom("foo\n\nbar"));
53 | }
54 |
55 | @Test
56 | public void testNewLineAtTheEnd() {
57 | Assert.assertEquals(Arrays.asList("foo"), ProcessOutput.getLinesFrom("foo\n"));
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/StartedProcess.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | import java.util.concurrent.Future;
21 |
22 | /**
23 | * Represents a process that has started. It may or may not have finished.
24 | *
25 | * @author Rein Raudjärv
26 | */
27 | public class StartedProcess {
28 |
29 | /**
30 | * The sub process started.
31 | */
32 | private final Process process;
33 |
34 | /**
35 | * The asynchronous result of the started process.
36 | */
37 | private final Future future;
38 |
39 | public StartedProcess(Process process, Future future) {
40 | this.process = process;
41 | this.future = future;
42 | }
43 |
44 | /**
45 | * @return the started process.
46 | */
47 | public Process getProcess() {
48 | return process;
49 | }
50 |
51 | /**
52 | * @return asynchronous result of the started process.
53 | */
54 | public Future getFuture() {
55 | return future;
56 | }
57 |
58 | /**
59 | * @return the started process.
60 | * @deprecated use {@link #getProcess()} instead.
61 | */
62 | public Process process() {
63 | return getProcess();
64 | }
65 |
66 | /**
67 | * @return asynchronous result of the started process.
68 | * @deprecated use {@link #getFuture()} instead.
69 | */
70 | public Future future() {
71 | return getFuture();
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/InputRedirectTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.ByteArrayInputStream;
21 |
22 | import org.apache.commons.lang3.SystemUtils;
23 | import org.junit.Assume;
24 | import org.junit.Test;
25 | import org.slf4j.Logger;
26 | import org.slf4j.LoggerFactory;
27 | import org.zeroturnaround.exec.ProcessExecutor;
28 |
29 | /**
30 | * Reported in https://github.com/zeroturnaround/zt-exec/issues/30
31 | */
32 | public class InputRedirectTest {
33 |
34 | private static final Logger log = LoggerFactory.getLogger(InputRedirectTest.class);
35 |
36 | @Test
37 | public void testRedirectInput() throws Exception {
38 | String binTrue;
39 | if (SystemUtils.IS_OS_LINUX) {
40 | binTrue = "/bin/true";
41 | }
42 | else if (SystemUtils.IS_OS_MAC_OSX) {
43 | binTrue = "/usr/bin/true";
44 | }
45 | else {
46 | Assume.assumeTrue("Unsupported OS " + SystemUtils.OS_NAME, false);
47 | return; // Skip this test
48 | }
49 |
50 | // We need to put something in the buffer
51 | ByteArrayInputStream bais = new ByteArrayInputStream("foo".getBytes());
52 | ProcessExecutor exec = new ProcessExecutor().command(binTrue);
53 | // Test that we don't get IOException: Stream closed
54 | int exit = exec.redirectInput(bais).readOutput(true).execute().getExitValue();
55 | log.debug("Exit: {}", exit);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/InvalidResultException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | /**
21 | * Process finished with an unexpected result.
22 | *
23 | * @author Rein Raudjärv
24 | * @since 1.8
25 | */
26 | public class InvalidResultException extends RuntimeException {
27 |
28 | private static final long serialVersionUID = 1L;
29 |
30 | /**
31 | * Actual exit value and process output.
32 | */
33 | private final ProcessResult result;
34 |
35 | /**
36 | * @param message the detail message of the exception
37 | * @param result result of execution (contains also the exit value)
38 | */
39 | public InvalidResultException(String message, ProcessResult result) {
40 | super(message);
41 | this.result = result;
42 | }
43 |
44 | /**
45 | * @return actual process result.
46 | */
47 | public ProcessResult getResult() {
48 | return result;
49 | }
50 |
51 | /**
52 | * @return the exit value of the finished process.
53 | */
54 | public int getExitValue() {
55 | return result.getExitValue();
56 | }
57 |
58 | /**
59 | * @return actual process result.
60 | * @deprecated use {@link #getResult()}
61 | */
62 | public ProcessResult result() {
63 | return getResult();
64 | }
65 |
66 | /**
67 | * @return the exit value of the finished process.
68 | * @deprecated use {@link #getExitValue()}
69 | */
70 | public int exitValue() {
71 | return getExitValue();
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/MessageLoggers.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | import org.slf4j.Logger;
21 | import org.zeroturnaround.exec.stream.slf4j.Level;
22 |
23 | /**
24 | * Contains {@link MessageLogger} instances for various log levels.
25 | */
26 | public class MessageLoggers {
27 |
28 | public static final MessageLogger NOP = new MessageLogger() {
29 | public void message(Logger log, String format, Object... arguments) {
30 | // do nothing
31 | }
32 | };
33 |
34 | public static final MessageLogger TRACE = new MessageLogger() {
35 | public void message(Logger log, String format, Object... arguments) {
36 | log.trace(format, arguments);
37 | }
38 | };
39 |
40 | public static final MessageLogger DEBUG = new MessageLogger() {
41 | public void message(Logger log, String format, Object... arguments) {
42 | log.debug(format, arguments);
43 | }
44 | };
45 |
46 | public static final MessageLogger INFO = new MessageLogger() {
47 | public void message(Logger log, String format, Object... arguments) {
48 | log.info(format, arguments);
49 | }
50 | };
51 |
52 | public static final MessageLogger get(Level level) {
53 | switch (level) {
54 | case TRACE: return TRACE;
55 | case DEBUG: return DEBUG;
56 | case INFO: return INFO;
57 | default:
58 | throw new IllegalArgumentException("Invalid level " + level);
59 | }
60 | }
61 |
62 | private MessageLoggers() {
63 | // hide
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorStreamCloseTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.ByteArrayOutputStream;
21 |
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 | import org.zeroturnaround.exec.ProcessExecutor;
25 |
26 |
27 | /**
28 | * Tests that redirect target stream are not closed.
29 | *
30 | * @author Rein Raudjärv
31 | *
32 | * @see ProcessExecutor
33 | * @see HelloWorld
34 | */
35 | public class ProcessExecutorStreamCloseTest {
36 |
37 | @Test
38 | public void testRedirectOutputNotClosed() throws Exception {
39 | ByteArrayOutputStream out = new ByteArrayOutputStream();
40 | RememberCloseOutputStream close = new RememberCloseOutputStream(out);
41 | helloWorld().redirectOutput(close).redirectErrorStream(false).execute();
42 | Assert.assertEquals("Hello ", new String(out.toByteArray()));
43 | Assert.assertFalse(close.isClosed());
44 | }
45 |
46 | @Test
47 | public void testRedirectErrorNotClosed() throws Exception {
48 | ByteArrayOutputStream out = new ByteArrayOutputStream();
49 | RememberCloseOutputStream close = new RememberCloseOutputStream(out);
50 | helloWorld().redirectError(close).execute();
51 | Assert.assertEquals("world!", new String(out.toByteArray()));
52 | Assert.assertFalse(close.isClosed());
53 | }
54 |
55 | private ProcessExecutor helloWorld() {
56 | return new ProcessExecutor("java", "-cp", "target/test-classes", HelloWorld.class.getName());
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/ProcessAttributes.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | import java.io.File;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.Set;
24 |
25 | /**
26 | * Immutable set of attributes used to start a process.
27 | */
28 | class ProcessAttributes {
29 |
30 | /**
31 | * The external program and its arguments.
32 | */
33 | private final List command;
34 |
35 | /**
36 | * Working directory, null in case of current working directory.
37 | */
38 | private final File directory;
39 |
40 | /**
41 | * Environment variables which are added (removed in case of null values) to the started process.
42 | */
43 | private final Map environment;
44 |
45 | /**
46 | * Set of accepted exit codes or null if all exit codes are allowed.
47 | */
48 | private final Set allowedExitValues;
49 |
50 | public ProcessAttributes(List command, File directory, Map environment, Set allowedExitValues) {
51 | this.command = command;
52 | this.directory = directory;
53 | this.environment = environment;
54 | this.allowedExitValues = allowedExitValues;
55 | }
56 |
57 | public List getCommand() {
58 | return command;
59 | }
60 |
61 | public File getDirectory() {
62 | return directory;
63 | }
64 |
65 | public Map getEnvironment() {
66 | return environment;
67 | }
68 |
69 | public Set getAllowedExitValues() {
70 | return allowedExitValues;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/InvalidExitUtil.java:
--------------------------------------------------------------------------------
1 | package org.zeroturnaround.exec;
2 |
3 | import java.util.Set;
4 | import java.util.concurrent.TimeoutException;
5 |
6 | /**
7 | * Helper for checking the exit code of the finished process.
8 | */
9 | class InvalidExitUtil {
10 |
11 | /**
12 | * In case {@link InvalidExitValueException} or {@link TimeoutException} is thrown and we have read the process output
13 | * we include the output up to this length in the error message. If the output is longer we truncate it.
14 | */
15 | private static final int MAX_OUTPUT_SIZE_IN_ERROR_MESSAGE = 5000;
16 |
17 | /**
18 | * Check the process exit value.
19 | */
20 | public static void checkExit(ProcessAttributes attributes, ProcessResult result) {
21 | Set allowedExitValues = attributes.getAllowedExitValues();
22 | if (allowedExitValues != null && !allowedExitValues.contains(result.getExitValue())) {
23 | StringBuilder sb = new StringBuilder();
24 | sb.append("Unexpected exit value: ").append(result.getExitValue());
25 | sb.append(", allowed exit values: ").append(allowedExitValues);
26 | addExceptionMessageSuffix(attributes, sb, result.hasOutput() ? result.getOutput() : null);
27 | throw new InvalidExitValueException(sb.toString(), result);
28 | }
29 | }
30 |
31 | public static void addExceptionMessageSuffix(ProcessAttributes attributes, StringBuilder sb, ProcessOutput output) {
32 | sb.append(", executed command ").append(attributes.getCommand());
33 | if (attributes.getDirectory() != null) {
34 | sb.append(" in directory ").append(attributes.getDirectory());
35 | }
36 | if (!attributes.getEnvironment().isEmpty()) {
37 | sb.append(" with environment ").append(attributes.getEnvironment());
38 | }
39 | if (output != null) {
40 | int length = output.getBytes().length;
41 | String out = output.getString();
42 | if (out.length() <= MAX_OUTPUT_SIZE_IN_ERROR_MESSAGE) {
43 | sb.append(", output was ").append(length).append(" bytes:\n").append(out.trim());
44 | }
45 | else {
46 | sb.append(", output was ").append(length).append(" bytes (truncated):\n");
47 | int halfLimit = MAX_OUTPUT_SIZE_IN_ERROR_MESSAGE / 2;
48 | sb.append(out.substring(0, halfLimit)).append("\n...\n").append(out.substring(out.length() - halfLimit).trim());
49 | }
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Changelog.txt:
--------------------------------------------------------------------------------
1 | ********************************************
2 | ZT Exec Changelog
3 | ********************************************
4 |
5 | 1.12 (2nd of September 2020)
6 | * Removed rebel.xml from the release archive
7 |
8 | 1.11 (5th of July 2019)
9 | * Improved usage - eliminated cases of log and throw
10 | * Removed dependency on Apache's commons-io library
11 | * Use ping instead of sleep on Windows to test timeout exceptions
12 | * Some bug fixes
13 |
14 | 1.10 (1st August 2017)
15 | * Added support for flushing output after each write
16 | * Added support for hooks for thread executor creation
17 | * Added support of preserving MDC context of the caller thread
18 | * Changed LogOutpuStream to handle line breaks like ProcessOutput
19 |
20 | 1.9 (12th April 2016)
21 | * Fixed using empty arguments on Windows
22 | * Fixed closing stdin on Java 8
23 | * Fixed redirecting PipedInputStream.
24 | * Added ProcessInitException to expose error code
25 | * Added ProcessExecutor.checkExitValue() for unit testing
26 | * Added getters to ProcessExecutor
27 |
28 | 1.8 (17th March 2015)
29 | * Fixed blocking JVM shutdown
30 | * Improved TimeoutException message
31 | * Improved Slf4jStream
32 | * Added ProcessExecutor.closeTimeout()
33 | * Added ProcessOutput.getLines()
34 | * Added ProcessOutput.command(Iterable)
35 | * Added ProcessListener.afterFinish() and InvalidOutputException support
36 | * Added Level class
37 |
38 | 1.7 (30th June 2014)
39 | * Fixed ProcessExecutor.commandSplit()
40 | * Added ProcessExecutor.stopper(ProcessStopper) to customize stopping in case of timeout or cancellation
41 | * Added ProcessExecutor.setMessageLogger() to customize log message level
42 | * Added ProcessExecutor.executeNoTimeout() to avoid catching TimeoutException
43 | * Added ProcessExecutor.environment(String, String) for convenience
44 | * Improved logging and error handling
45 | * Added get-prefixes to StartedProcess methods (old ones are deprecated)
46 |
47 | 1.6 (30th January 2014)
48 | * Improved Slf4j logging support
49 | * Added ProcessExecutor.redirectInput()
50 | * Improved adding/removing destroyers and listeners
51 | * InvalidExitValueException now includes process output if it was read
52 | * Added get-prefixes to some methods (old ones are deprecated)
53 |
54 | 1.5 (14th October 2013)
55 | * By default any exit code is now allowed (use exitValueNormal() to only allow zero)
56 | * Fixed an issue with starting processes on JVM shutdown
57 | * Fixed an issue with supporting Java 1.5
58 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessInitExceptionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.IOException;
21 |
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 | import org.zeroturnaround.exec.ProcessInitException;
25 |
26 | public class ProcessInitExceptionTest {
27 |
28 | @Test
29 | public void testNull() throws Exception {
30 | Assert.assertNull(ProcessInitException.newInstance(null, new IOException()));
31 | }
32 |
33 | @Test
34 | public void testEmpty() throws Exception {
35 | Assert.assertNull(ProcessInitException.newInstance(null, new IOException("")));
36 | }
37 |
38 | @Test
39 | public void testSimple() throws Exception {
40 | ProcessInitException e = ProcessInitException.newInstance(
41 | "Could not run test.", new IOException("java.io.IOException: Cannot run program \"ls\": java.io.IOException: error=12, Cannot allocate memory"));
42 | Assert.assertNotNull(e);
43 | Assert.assertEquals("Could not run test. Error=12, Cannot allocate memory", e.getMessage());
44 | Assert.assertEquals(12, e.getErrorCode());
45 | }
46 |
47 | @Test
48 | public void testBeforeCode() throws Exception {
49 | ProcessInitException e = ProcessInitException.newInstance(
50 | "Could not run test.", new IOException("java.io.IOException: Cannot run program \"sleep\": java.io.IOException: CreateProcess error=2, The system cannot find the file specified"));
51 | Assert.assertNotNull(e);
52 | Assert.assertEquals("Could not run test. Error=2, The system cannot find the file specified", e.getMessage());
53 | Assert.assertEquals(2, e.getErrorCode());
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/InputStreamPumperTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.ByteArrayInputStream;
21 | import java.io.ByteArrayOutputStream;
22 |
23 | import org.junit.Assert;
24 | import org.junit.Test;
25 | import org.zeroturnaround.exec.ProcessExecutor;
26 | import org.zeroturnaround.exec.stream.PumpStreamHandler;
27 |
28 | /**
29 | * Tests that test redirected input for the process to be run.
30 | */
31 | public class InputStreamPumperTest {
32 |
33 | @Test
34 | public void testPumpFromInputToOutput() throws Exception {
35 | String str = "Tere Minu Uus vihik";
36 | ByteArrayInputStream bais = new ByteArrayInputStream(str.getBytes());
37 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
38 | PumpStreamHandler handler = new PumpStreamHandler(baos, System.err, bais);
39 |
40 | ProcessExecutor exec = new ProcessExecutor("java", "-cp", "target/test-classes",
41 | PrintInputToOutput.class.getName()).readOutput(true);
42 | exec.streams(handler);
43 |
44 | String result = exec.execute().outputUTF8();
45 | Assert.assertEquals(str, result);
46 | }
47 |
48 | @Test
49 | public void testPumpFromInputToOutputWithInput() throws Exception {
50 | String str = "Tere Minu Uus vihik";
51 | ByteArrayInputStream bais = new ByteArrayInputStream(str.getBytes());
52 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
53 |
54 | ProcessExecutor exec = new ProcessExecutor("java", "-cp", "target/test-classes",
55 | PrintInputToOutput.class.getName()).readOutput(true).redirectInput(bais);
56 |
57 | String result = exec.execute().outputUTF8();
58 | Assert.assertEquals(str, result);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorExitValueTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 | import java.util.concurrent.TimeUnit;
23 |
24 | import org.junit.Test;
25 | import org.zeroturnaround.exec.InvalidExitValueException;
26 | import org.zeroturnaround.exec.ProcessExecutor;
27 |
28 |
29 | public class ProcessExecutorExitValueTest {
30 |
31 | @Test(expected=InvalidExitValueException.class)
32 | public void testJavaVersionExitValueCheck() throws Exception {
33 | new ProcessExecutor().command("java", "-version").exitValues(3).execute();
34 | }
35 |
36 | @Test(expected=InvalidExitValueException.class)
37 | public void testJavaVersionExitValueCheckTimeout() throws Exception {
38 | new ProcessExecutor().command("java", "-version").exitValues(3).timeout(60, TimeUnit.SECONDS).execute();
39 | }
40 |
41 | public void testNonZeroExitValueByDefault() throws Exception {
42 | new ProcessExecutor(exitLikeABoss(17)).execute();
43 | }
44 |
45 | @Test
46 | public void testCustomExitValueValid() throws Exception {
47 | new ProcessExecutor(exitLikeABoss(17)).exitValues(17).execute();
48 | }
49 |
50 | @Test(expected=InvalidExitValueException.class)
51 | public void testCustomExitValueInvalid() throws Exception {
52 | new ProcessExecutor(exitLikeABoss(17)).exitValues(15).execute();
53 | }
54 |
55 | private static List exitLikeABoss(int exitValue) {
56 | List result = new ArrayList();
57 | result.add("java");
58 | result.add("-cp");
59 | result.add("target/test-classes");
60 | result.add(ExitLikeABoss.class.getName());
61 | result.add(String.valueOf(exitValue));
62 | return result;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessListenerSuccessTest.java:
--------------------------------------------------------------------------------
1 | package org.zeroturnaround.exec.test;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 | import org.zeroturnaround.exec.ProcessExecutor;
6 | import org.zeroturnaround.exec.ProcessResult;
7 | import org.zeroturnaround.exec.listener.ProcessListener;
8 |
9 | public class ProcessListenerSuccessTest {
10 |
11 | @Test
12 | public void testJavaVersion() throws Exception {
13 | ProcessListenerImpl listener = new ProcessListenerImpl();
14 | ProcessResult result = new ProcessExecutor("java", "-version").addListener(listener).execute();
15 | int exit = result.getExitValue();
16 | Assert.assertEquals(0, exit);
17 | Assert.assertNotNull(listener.executor);
18 | Assert.assertNotNull(listener.process);
19 | Assert.assertNotNull(listener.result);
20 | Assert.assertEquals(result, listener.result);
21 | }
22 |
23 | static class ProcessListenerImpl extends ProcessListener {
24 |
25 | ProcessExecutor executor;
26 |
27 | Process process;
28 |
29 | ProcessResult result;
30 |
31 | @Override
32 | public void beforeStart(ProcessExecutor executor) {
33 | Assert.assertNotNull(executor);
34 |
35 | Assert.assertNull(this.executor);
36 | Assert.assertNull(this.process);
37 | Assert.assertNull(this.result);
38 |
39 | this.executor = executor;
40 | }
41 |
42 | @Override
43 | public void afterStart(Process process, ProcessExecutor executor) {
44 | Assert.assertNotNull(process);
45 | Assert.assertNotNull(executor);
46 |
47 | Assert.assertNotNull(this.executor);
48 | Assert.assertNull(this.process);
49 | Assert.assertNull(this.result);
50 |
51 | Assert.assertEquals(this.executor, executor);
52 | this.process = process;
53 | }
54 |
55 | @Override
56 | public void afterFinish(Process process, ProcessResult result) {
57 | Assert.assertNotNull(process);
58 | Assert.assertNotNull(result);
59 |
60 | Assert.assertNotNull(this.executor);
61 | Assert.assertNotNull(this.process);
62 | Assert.assertNull(this.result);
63 |
64 | Assert.assertEquals(this.process, process);
65 | this.result = result;
66 | }
67 |
68 | @Override
69 | public void afterStop(Process process) {
70 | Assert.assertNotNull(process);
71 |
72 | Assert.assertNotNull(this.executor);
73 | Assert.assertNotNull(this.process);
74 | Assert.assertNotNull(this.result);
75 |
76 | Assert.assertEquals(this.process, process);
77 | }
78 |
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/shutdown/ProcessExecutorShutdownHookTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test.shutdown;
19 |
20 | import java.io.File;
21 |
22 | import org.apache.commons.io.FileUtils;
23 | import org.apache.commons.lang3.SystemUtils;
24 | import org.junit.Assert;
25 | import org.junit.Test;
26 | import org.zeroturnaround.exec.ProcessExecutor;
27 |
28 |
29 | /**
30 | * Tests destroying processes on JVM exit.
31 | */
32 | public class ProcessExecutorShutdownHookTest {
33 |
34 | private static final long SLEEP_FOR_RECHECKING_FILE = 2000;
35 |
36 | @Test
37 | public void testDestroyOnExit() throws Exception {
38 | testDestroyOnExit(WriterLoopStarterBeforeExit.class, true);
39 | }
40 |
41 | @Test
42 | public void testDestroyOnExitInShutdownHook() throws Exception {
43 | testDestroyOnExit(WriterLoopStarterAfterExit.class, false);
44 | }
45 |
46 | private void testDestroyOnExit(Class> starter, boolean fileIsAlwaysCreated) throws Exception {
47 | File file = WriterLoop.getFile();
48 | if (file.exists())
49 | FileUtils.forceDelete(file);
50 | new ProcessExecutor("java", "-cp", SystemUtils.JAVA_CLASS_PATH, starter.getName()).redirectOutputAsInfo().execute();
51 | // After WriterLoopStarter has finished we expect that WriterLoop is also finished - no-one is updating the file
52 | if (fileIsAlwaysCreated || file.exists()) {
53 | checkFileStaysTheSame(file);
54 | FileUtils.forceDelete(file);
55 | }
56 | }
57 |
58 | private static void checkFileStaysTheSame(File file) throws InterruptedException {
59 | Assert.assertTrue(file.exists());
60 | long length = file.length();
61 | Thread.sleep(SLEEP_FOR_RECHECKING_FILE);
62 | Assert.assertEquals("File '" + file + "' was still updated.", length, file.length());
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorLoggerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.OutputStream;
21 |
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 | import org.zeroturnaround.exec.ProcessExecutor;
25 | import org.zeroturnaround.exec.stream.PumpStreamHandler;
26 | import org.zeroturnaround.exec.stream.slf4j.Slf4jInfoOutputStream;
27 | import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
28 |
29 |
30 | public class ProcessExecutorLoggerTest {
31 |
32 | @Test
33 | public void testFullName() throws Exception {
34 | String fullName = "my.full.Logger";
35 | testSlf4jLoggerName(fullName, Slf4jStream.of(fullName));
36 | }
37 |
38 | @Test
39 | public void testShortName() throws Exception {
40 | String shortName = "MyLogger";
41 | String fullName = getClass().getName() + "." + shortName;
42 | testSlf4jLoggerName(fullName, Slf4jStream.of(shortName));
43 | }
44 |
45 | @Test
46 | public void testClassNameWithShortName() throws Exception {
47 | String shortName = "MyLogger";
48 | String fullName = getClass().getName() + "." + shortName;
49 | testSlf4jLoggerName(fullName, Slf4jStream.of(getClass(), shortName));
50 | }
51 |
52 | @Test
53 | public void testMyClassName() throws Exception {
54 | String fullName = getClass().getName();
55 | testSlf4jLoggerName(fullName, Slf4jStream.ofCaller());
56 | }
57 |
58 | private void testSlf4jLoggerName(String fullName, Slf4jStream stream) {
59 | ProcessExecutor executor = new ProcessExecutor();
60 | executor.redirectOutput(stream.asInfo());
61 | PumpStreamHandler pumps = executor.pumps();
62 | OutputStream out = pumps.getOut();
63 | Assert.assertTrue("Slf4jInfoOutputStream expected", out instanceof Slf4jInfoOutputStream);
64 | Assert.assertEquals(fullName, ((Slf4jInfoOutputStream) out).getLogger().getName());
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/stream/CallerLoggerUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.stream;
19 |
20 | /**
21 | * Constructs name for the caller logger.
22 | *
23 | * @author Rein Raudjärv
24 | */
25 | public abstract class CallerLoggerUtil {
26 |
27 | /**
28 | * Returns full name for the caller class' logger.
29 | *
30 | * @param name name of the logger. In case of full name (it contains dots) same value is just returned.
31 | * In case of short names (no dots) the given name is prefixed by caller's class name and a dot.
32 | * In case of null the caller's class name is just returned.
33 | * @return full name for the caller class' logger.
34 | */
35 | public static String getName(String name) {
36 | return getName(name, 1);
37 | }
38 |
39 | /**
40 | * Returns full name for the caller class' logger.
41 | *
42 | * @param name name of the logger. In case of full name (it contains dots) same value is just returned.
43 | * In case of short names (no dots) the given name is prefixed by caller's class name and a dot.
44 | * In case of null the caller's class name is just returned.
45 | * @param level no of call stack levels to get the caller (0 means the caller of this method).
46 | * @return full name for the caller class' logger.
47 | */
48 | public static String getName(String name, int level) {
49 | level++;
50 | String fullName;
51 | if (name == null)
52 | fullName = getCallerClassName(level);
53 | else if (name.contains("."))
54 | fullName = name;
55 | else
56 | fullName = getCallerClassName(level) + "." + name;
57 | return fullName;
58 | }
59 |
60 | /**
61 | * @return caller class name of the given level.
62 | */
63 | private static String getCallerClassName(int level) {
64 | return Thread.currentThread().getStackTrace()[level + 2].getClassName();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/listener/ProcessListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.listener;
19 |
20 | import org.zeroturnaround.exec.ProcessExecutor;
21 | import org.zeroturnaround.exec.ProcessResult;
22 |
23 | /**
24 | * Event handler for process events.
25 | *
26 | * This is a class instead of interface in order to add new methods without updating all implementations.
27 | *
28 | * @author Rein Raudjärv
29 | * @see ProcessExecutor#addListener(ProcessListener)
30 | */
31 | public abstract class ProcessListener {
32 |
33 | /**
34 | * Invoked before a process is started.
35 | *
36 | * @param executor executor used for starting a process.
37 | * Any changes made here apply to the starting process.
38 | * Once the process has started it is not affected by the {@link ProcessExecutor} any more.
39 | */
40 | public void beforeStart(ProcessExecutor executor) {
41 | // do nothing
42 | }
43 |
44 | /**
45 | * Invoked after a process has started.
46 | *
47 | * @param process the process started.
48 | * @param executor executor used for starting the process.
49 | * Modifying the {@link ProcessExecutor} only affects the following processes
50 | * not the one just started.
51 | */
52 | public void afterStart(Process process, ProcessExecutor executor) {
53 | // do nothing
54 | }
55 |
56 | /**
57 | * Invoked after a process has finished successfully.
58 | *
59 | * @param process process just finished.
60 | * @param result result of the finished process.
61 | * @since 1.8
62 | */
63 | public void afterFinish(Process process, ProcessResult result) {
64 | // do nothing
65 | }
66 |
67 | /**
68 | * Invoked after a process has exited (whether finished or cancelled).
69 | *
70 | * @param process process just stopped.
71 | */
72 | public void afterStop(Process process) {
73 | // do nothing
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorInputStreamTest.java:
--------------------------------------------------------------------------------
1 | package org.zeroturnaround.exec.test;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.PipedInputStream;
6 | import java.io.PipedOutputStream;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | import org.junit.Assert;
10 | import org.junit.Test;
11 | import org.zeroturnaround.exec.ProcessExecutor;
12 | import org.zeroturnaround.exec.StartedProcess;
13 |
14 | /**
15 | *
16 | */
17 | public class ProcessExecutorInputStreamTest {
18 |
19 | @Test
20 | public void testWithInputAndRedirectOutput() throws Exception {
21 | String str = "Tere Minu Uus vihik";
22 | ByteArrayInputStream bais = new ByteArrayInputStream(str.getBytes());
23 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
24 |
25 | ProcessExecutor exec = new ProcessExecutor("java", "-cp", "target/test-classes", PrintInputToOutput.class.getName());
26 | exec.redirectInput(bais).redirectOutput(baos);
27 |
28 | exec.execute();
29 | Assert.assertEquals(str, baos.toString());
30 | }
31 |
32 | @Test
33 | public void testRedirectPipedInputStream() throws Exception {
34 | // Setup InputStream that will block on a read()
35 | PipedOutputStream pos = new PipedOutputStream();
36 | PipedInputStream pis = new PipedInputStream(pos);
37 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
38 |
39 | ProcessExecutor exec = new ProcessExecutor("java", "-cp", "target/test-classes", PrintArguments.class.getName());
40 | exec.redirectInput(pis);
41 | StartedProcess startedProcess = exec.start();
42 | // Assert that we don't get a TimeoutException
43 | startedProcess.getFuture().get(5, TimeUnit.SECONDS);
44 | }
45 |
46 | @Test
47 | public void testDataIsFlushedToProcessWithANonEndingInputStream() throws Exception {
48 | String str = "Tere Minu Uus vihik " + System.nanoTime();
49 |
50 | // Setup InputStream that will block on a read()
51 | PipedOutputStream pos = new PipedOutputStream();
52 | PipedInputStream pis = new PipedInputStream(pos);
53 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
54 |
55 | ProcessExecutor exec = new ProcessExecutor("java", "-cp", "target/test-classes", PrintInputToOutput.class.getName());
56 | exec.redirectInput(pis).redirectOutput(baos);
57 | StartedProcess startedProcess = exec.start();
58 | pos.write(str.getBytes());
59 | pos.write("\n\n\n".getBytes()); // PrintInputToOutput processes at most 3 lines
60 |
61 | // Assert that we don't get a TimeoutException
62 | startedProcess.getFuture().get(5, TimeUnit.SECONDS);
63 | Assert.assertEquals(str, baos.toString());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/get_release_commands.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 |
4 | if [[ "$1" = "-h" ]] || [[ "$1" = "--help" ]]; then
5 | echo "Usage: './get_release_commands.sh VERSION_TO_RELEASE [NEW_DEV_VERSION]'"
6 | echo "Prints out commands to run to release zt-exec to Maven Central."
7 | echo "VERSION_TO_RELEASE must be a number (integer or floating). NEW_DEV_VERSION is optional"
8 | exit 0
9 | fi
10 |
11 | if ! [[ "$1" =~ ^[0-9]*\.?[0-9]*$ ]] || [[ "$1z" = "z" ]]; then
12 | echo "Not a valid version number given, use -h to get usage options"
13 | exit 1
14 | fi
15 |
16 | version=$1
17 |
18 | version_after_dot=${version##*.}
19 | version_str_length=${#version}-${#version_after_dot}
20 |
21 | incr_last_digit=$((${version_after_dot}+1))
22 |
23 | version_without_last_digit=${version: 0:${version_str_length}}
24 | new_version="${version_without_last_digit}${incr_last_digit}-SNAPSHOT"
25 |
26 | if [[ "$2z" = "z" ]]; then
27 | echo "No new development version given using ${new_version}"
28 | else
29 | new_version=$2
30 | echo "New development version will be ${new_version}"
31 | fi
32 |
33 | echo "To release zt-exec version ${version} run these commands:"
34 | echo ""
35 | echo "1) set release version"
36 | echo "mvn versions:set -DnewVersion=${version}"
37 |
38 | echo ""
39 | echo "2) commit & tag"
40 | echo "git add pom.xml; "
41 | echo "git commit -m \"Prepare release zt-exec-${version}\""
42 | echo "git tag zt-exec-${version}"
43 |
44 | echo ""
45 | echo "3) build release"
46 | echo "mvn clean install"
47 |
48 | echo ""
49 | echo "4) generate javadoc archive"
50 | echo "mvn javadoc:jar"
51 |
52 | echo ""
53 | echo "5) generate sources archive"
54 | echo "mvn source:jar"
55 |
56 | echo ""
57 | echo "6) deploy and sign releases archive"
58 | echo "mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=pom.xml -Dfile=target/zt-exec-$1.jar"
59 |
60 | echo ""
61 | echo "7) deploy and sign sources archive"
62 | echo "mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=pom.xml -Dfile=target/zt-exec-$1-sources.jar -Dclassifier=sources"
63 |
64 | echo ""
65 | echo "8) deploy and sign javadoc archive"
66 | echo "mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=pom.xml -Dfile=target/zt-exec-$1-javadoc.jar -Dclassifier=javadoc"
67 |
68 | echo ""
69 | echo "9) set new development version"
70 | echo "mvn versions:set -DnewVersion=${new_version}"
71 | echo "git add pom.xml; "
72 | echo "git commit -m \"prepare for next development iteration\""
73 |
74 | echo ""
75 | echo "10) push to GitHub"
76 | echo "git push"
77 | echo "git push --tags"
78 |
79 | echo ""
80 | echo "11) clean local repo"
81 | echo "git clean -f"
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/stream/TeeOutputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 Ketan Padegaonkar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.zeroturnaround.exec.stream;
18 |
19 | import java.io.IOException;
20 | import java.io.OutputStream;
21 |
22 | /**
23 | * Splits an OutputStream into two. Named after the unix 'tee'
24 | * command. It allows a stream to be branched off so there
25 | * are now two streams.
26 | */
27 | public class TeeOutputStream extends OutputStream {
28 | private final OutputStream left;
29 | private final OutputStream right;
30 |
31 | public TeeOutputStream(OutputStream left, OutputStream right) {
32 | this.left = left;
33 | this.right = right;
34 | }
35 |
36 | /**
37 | * Write a byte array to both output streams.
38 | *
39 | * @param b the data.
40 | * @param off the start offset in the data.
41 | * @param len the number of bytes to write.
42 | * @throws IOException on error.
43 | */
44 | public void write(byte[] b, int off, int len) throws IOException {
45 | left.write(b, off, len);
46 | right.write(b, off, len);
47 | }
48 |
49 |
50 | /**
51 | * Write a byte to both output streams.
52 | *
53 | * @param b the byte to write.
54 | * @throws IOException on error.
55 | */
56 | public void write(int b) throws IOException {
57 | left.write(b);
58 | right.write(b);
59 | }
60 |
61 |
62 | /**
63 | * Write a byte array to both output streams.
64 | *
65 | * @param b an array of bytes.
66 | * @throws IOException on error.
67 | */
68 | public void write(byte[] b) throws IOException {
69 | left.write(b);
70 | right.write(b);
71 | }
72 |
73 | /**
74 | * Closes both output streams
75 | *
76 | * @throws IOException on error.
77 | */
78 | @Override
79 | public void close() throws IOException {
80 | try {
81 | left.close();
82 | } finally {
83 | right.close();
84 | }
85 | }
86 |
87 |
88 | /**
89 | * Flush both output streams.
90 | *
91 | * @throws IOException on error
92 | */
93 | @Override
94 | public void flush() throws IOException {
95 | left.flush();
96 | right.flush();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/ProcessInitException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | import java.io.IOException;
21 |
22 | /**
23 | * Creating a process failed providing an error code.
24 | *
25 | *
26 | * Wraps an {@link IOException} like:
27 | *
28 | * java.io.IOException: Cannot run program "ls": java.io.IOException: error=12, Cannot allocate memory
29 | * java.io.IOException: Cannot run program "ls": error=316, Unknown error: 316
30 | *
31 | */
32 | public class ProcessInitException extends IOException {
33 |
34 | private static final String BEFORE_CODE = " error=";
35 | private static final String AFTER_CODE = ", ";
36 | private static final String NEW_INFIX = " Error=";
37 |
38 | private final int errorCode;
39 |
40 | public ProcessInitException(String message, Throwable cause, int errorCode) {
41 | super(message, cause);
42 | this.errorCode = errorCode;
43 | }
44 |
45 | /**
46 | * @return error code raised when a process failed to start.
47 | */
48 | public int getErrorCode() {
49 | return errorCode;
50 | }
51 |
52 | /**
53 | * Try to wrap a given {@link IOException} into a {@link ProcessInitException}.
54 | *
55 | * @param prefix prefix to be added in the message.
56 | * @param e existing exception possibly containing an error code in its message.
57 | * @return new exception containing the prefix, error code and its description in the message plus the error code value as a field,
58 | * null if we were unable to find an error code from the original message.
59 | */
60 | public static ProcessInitException newInstance(String prefix, IOException e) {
61 | String m = e.getMessage();
62 | if (m == null) {
63 | return null;
64 | }
65 | int i = m.lastIndexOf(BEFORE_CODE);
66 | if (i == -1) {
67 | return null;
68 | }
69 | int j = m.indexOf(AFTER_CODE, i);
70 | if (j == -1) {
71 | return null;
72 | }
73 | int code;
74 | try {
75 | code = Integer.parseInt(m.substring(i + BEFORE_CODE.length(), j));
76 | }
77 | catch (NumberFormatException n) {
78 | return null;
79 | }
80 | return new ProcessInitException(prefix + NEW_INFIX + m.substring(i + BEFORE_CODE.length()), e, code);
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/LogOutputStreamTest.java:
--------------------------------------------------------------------------------
1 | package org.zeroturnaround.exec.test;
2 |
3 | import java.io.IOException;
4 | import java.io.UnsupportedEncodingException;
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | import org.junit.Assert;
10 | import org.junit.Test;
11 | import org.zeroturnaround.exec.stream.LineConsumer;
12 | import org.zeroturnaround.exec.stream.LogOutputStream;
13 |
14 | public class LogOutputStreamTest {
15 |
16 | private void testLogOutputStream(String multiLineString, String... expectedLines) throws UnsupportedEncodingException, IOException {
17 | final List processedLines = new ArrayList();
18 | LogOutputStream logOutputStream = new LogOutputStream() {
19 |
20 | @Override
21 | protected void processLine(String line) {
22 | processedLines.add(line);
23 | }
24 | };
25 | try {
26 | logOutputStream.write(multiLineString.getBytes("UTF-8"));
27 | } finally {
28 | logOutputStream.close();
29 | }
30 | Assert.assertEquals(Arrays.asList(expectedLines), processedLines);
31 | }
32 |
33 | @Test
34 | public void testSimple() throws UnsupportedEncodingException, IOException {
35 | testLogOutputStream("foo", "foo");
36 | }
37 |
38 | @Test
39 | public void testNewLine() throws UnsupportedEncodingException, IOException {
40 | testLogOutputStream("foo\nbar", "foo", "bar");
41 | }
42 |
43 | @Test
44 | public void testNewLineWithMultipleLines() throws UnsupportedEncodingException, IOException {
45 | testLogOutputStream("foo1\nbar1\nfoo2\nbar2", "foo1", "bar1", "foo2", "bar2");
46 | }
47 |
48 | @Test
49 | public void testCarriageReturn() throws UnsupportedEncodingException, IOException {
50 | testLogOutputStream("foo\rbar", "foo", "bar");
51 | }
52 |
53 | @Test
54 | public void testCarriageReturnWithMultipleLines() throws UnsupportedEncodingException, IOException {
55 | testLogOutputStream("foo1\rbar1\rfoo2\rbar2", "foo1", "bar1", "foo2", "bar2");
56 | }
57 |
58 | @Test
59 | public void testCarriageReturnAndNewLine() throws UnsupportedEncodingException, IOException {
60 | testLogOutputStream("foo\r\nbar", "foo", "bar");
61 | }
62 |
63 | @Test
64 | public void testCarriageReturnAndNewLineWithMultipleLines() throws UnsupportedEncodingException, IOException {
65 | testLogOutputStream("foo1\r\nbar1\r\nfoo2\r\nbar2", "foo1", "bar1", "foo2", "bar2");
66 | }
67 |
68 | @Test
69 | public void testTwoNewLines() throws UnsupportedEncodingException, IOException {
70 | testLogOutputStream("foo\n\nbar", "foo", "bar");
71 | }
72 |
73 | @Test
74 | public void testNewLineAtTheEnd() throws UnsupportedEncodingException, IOException {
75 | testLogOutputStream("foo\n", "foo");
76 | }
77 |
78 | @Test
79 | public void lambda() throws IOException {
80 | final List lines = new ArrayList();
81 | LogOutputStream out = LogOutputStream.create(new LineConsumer() {
82 | @Override
83 | public void accept(String line) {
84 | lines.add(line);
85 | }
86 | });
87 | out.write("foo\nbar\n".getBytes());
88 | Assert.assertEquals(Arrays.asList("foo", "bar"), lines);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorBigOutputTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.ByteArrayOutputStream;
21 | import java.util.concurrent.TimeUnit;
22 |
23 | import org.junit.Assert;
24 | import org.junit.Test;
25 | import org.zeroturnaround.exec.ProcessExecutor;
26 |
27 |
28 | /**
29 | * Tests reading large output that doesn't fit into a buffer between this process and sub process.
30 | *
31 | * @author Rein Raudjärv
32 | *
33 | * @see ProcessExecutor
34 | * @see BigOutput
35 | */
36 | public class ProcessExecutorBigOutputTest {
37 |
38 | @Test
39 | public void testDevNull() throws Exception {
40 | bigOutput().execute();
41 | }
42 |
43 | @Test
44 | public void testDevNullSeparate() throws Exception {
45 | bigOutput().redirectErrorStream(false).execute();
46 | }
47 |
48 | @Test
49 | public void testReadOutputAndError() throws Exception {
50 | String output = bigOutput().readOutput(true).execute().outputUTF8();
51 | Assert.assertEquals(repeat("+-"), output);
52 | }
53 |
54 | @Test
55 | public void testReadOutputOnly() throws Exception {
56 | String output = bigOutput().readOutput(true).redirectErrorStream(false).execute().outputUTF8();
57 | Assert.assertEquals(repeat("+"), output);
58 | }
59 |
60 | @Test
61 | public void testRedirectOutputOnly() throws Exception {
62 | ByteArrayOutputStream out = new ByteArrayOutputStream();
63 | bigOutput().redirectOutput(out).redirectErrorStream(false).execute();
64 | Assert.assertEquals(repeat("+"), new String(out.toByteArray()));
65 | }
66 |
67 | @Test
68 | public void testRedirectErrorOnly() throws Exception {
69 | ByteArrayOutputStream err = new ByteArrayOutputStream();
70 | bigOutput().redirectError(err).redirectErrorStream(false).execute();
71 | Assert.assertEquals(repeat("-"), new String(err.toByteArray()));
72 | }
73 |
74 | private ProcessExecutor bigOutput() {
75 | // Use timeout in case we get stuck
76 | return new ProcessExecutor("java", "-cp", "target/test-classes", BigOutput.class.getName()).timeout(10, TimeUnit.SECONDS);
77 | }
78 |
79 | private static String repeat(String s) {
80 | StringBuffer sb = new StringBuffer(BigOutput.LENGTH * 2);
81 | for (int i = 0; i < BigOutput.LENGTH; i++)
82 | sb.append(s);
83 | return sb.toString();
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/listener/ProcessDestroyer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * NOTICE: This file originates from the Apache Commons Exec package.
19 | * It has been modified to fit our needs.
20 | *
21 | * The following is the original header of the file in Apache Commons Exec:
22 | *
23 | * Licensed to the Apache Software Foundation (ASF) under one or more
24 | * contributor license agreements. See the NOTICE file distributed with
25 | * this work for additional information regarding copyright ownership.
26 | * The ASF licenses this file to You under the Apache License, Version 2.0
27 | * (the "License"); you may not use this file except in compliance with
28 | * the License. You may obtain a copy of the License at
29 | *
30 | * http://www.apache.org/licenses/LICENSE-2.0
31 | *
32 | * Unless required by applicable law or agreed to in writing, software
33 | * distributed under the License is distributed on an "AS IS" BASIS,
34 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 | * See the License for the specific language governing permissions and
36 | * limitations under the License.
37 | */
38 | package org.zeroturnaround.exec.listener;
39 |
40 | /**
41 | * Destroys all registered {@link java.lang.Process} after a certain event,
42 | * typically when the VM exits
43 | * @see ShutdownHookProcessDestroyer
44 | */
45 | public interface ProcessDestroyer {
46 |
47 | /**
48 | * Returns true if the specified
49 | * {@link java.lang.Process} was
50 | * successfully added to the list of processes to be destroy.
51 | *
52 | * @param process
53 | * the process to add
54 | * @return true if the specified
55 | * {@link java.lang.Process} was
56 | * successfully added
57 | */
58 | boolean add(Process process);
59 |
60 | /**
61 | * Returns true if the specified
62 | * {@link java.lang.Process} was
63 | * successfully removed from the list of processes to be destroy.
64 | *
65 | * @param process
66 | * the process to remove
67 | * @return true if the specified
68 | * {@link java.lang.Process} was
69 | * successfully removed
70 | */
71 | boolean remove(Process process);
72 |
73 | /**
74 | * Returns the number of registered processes.
75 | *
76 | * @return the number of register process
77 | */
78 | int size();
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorCommandLineTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.ByteArrayInputStream;
21 | import java.io.IOException;
22 | import java.util.ArrayList;
23 | import java.util.Arrays;
24 | import java.util.List;
25 | import java.util.concurrent.TimeoutException;
26 |
27 | import org.apache.commons.io.IOUtils;
28 | import org.junit.Assert;
29 | import org.junit.Test;
30 | import org.zeroturnaround.exec.ProcessExecutor;
31 |
32 |
33 | /**
34 | * Tests passing command line arguments to a Java process.
35 | */
36 | public class ProcessExecutorCommandLineTest {
37 |
38 | @Test
39 | public void testOneArg() throws Exception {
40 | testArguments("foo");
41 | }
42 |
43 | @Test
44 | public void testTwoArgs() throws Exception {
45 | testArguments("foo", "bar");
46 | }
47 |
48 | @Test
49 | public void testSpaces() throws Exception {
50 | testArguments("foo foo", "bar bar");
51 | }
52 |
53 | @Test
54 | public void testQuotes() throws Exception {
55 | String[] args = new String[]{"\"a\"", "b \"c\" d", "f \"e\"", "\"g\" h"};
56 | List expected = Arrays.asList("\"a\"", "b \"c\" d", "f \"e\"", "\"g\" h");
57 | if (System.getProperty("os.name").startsWith("Windows"))
58 | expected = Arrays.asList("a", "b c d", "f e", "g h");
59 | testArguments(expected, args);
60 | }
61 |
62 | @Test
63 | public void testSlashes() throws Exception {
64 | testArguments("/o\\", "\\/.*");
65 | }
66 |
67 | private void testArguments(String... args) throws IOException, InterruptedException, TimeoutException {
68 | byte[] bytes = printArguments(args).execute().output();
69 | List expected = Arrays.asList(args);
70 | List actual = IOUtils.readLines(new ByteArrayInputStream(bytes));
71 | Assert.assertEquals(expected, actual);
72 | }
73 |
74 | private void testArguments(List expected, String... args) throws IOException, InterruptedException, TimeoutException {
75 | byte[] bytes = printArguments(args).execute().output();
76 | List actual = IOUtils.readLines(new ByteArrayInputStream(bytes));
77 | Assert.assertEquals(expected, actual);
78 | }
79 |
80 | private ProcessExecutor printArguments(String... args) {
81 | List command = new ArrayList();
82 | command.addAll(Arrays.asList("java", "-cp", "target/test-classes", PrintArguments.class.getName()));
83 | command.addAll(Arrays.asList(args));
84 | return new ProcessExecutor(command).readOutput(true);
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/stream/TeeOutputStreamTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 Ketan Padegaonkar
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.zeroturnaround.exec.stream;
18 |
19 | import org.junit.Assert;
20 | import org.junit.Test;
21 | import org.zeroturnaround.exec.test.RememberCloseOutputStream;
22 |
23 | import java.io.ByteArrayOutputStream;
24 | import java.io.IOException;
25 | import java.io.OutputStream;
26 |
27 | public class TeeOutputStreamTest {
28 |
29 | public static class ExceptionOnCloseByteArrayOutputStream extends RememberCloseOutputStream {
30 |
31 | public ExceptionOnCloseByteArrayOutputStream(OutputStream out) {
32 | super(out);
33 | }
34 |
35 | @Override
36 | public void close() throws IOException {
37 | super.close();
38 | throw new IOException();
39 | }
40 | }
41 |
42 | @Test
43 | public void shouldCopyContentsToBothStreams() throws IOException {
44 | ByteArrayOutputStream left = new ByteArrayOutputStream();
45 | ByteArrayOutputStream right = new ByteArrayOutputStream();
46 | TeeOutputStream teeOutputStream = new TeeOutputStream(left, right);
47 |
48 | teeOutputStream.write(10);
49 | teeOutputStream.write(new byte[]{1, 2, 3});
50 | teeOutputStream.write(new byte[]{10, 11, 12, 13, 14, 15, 15, 16}, 2, 3);
51 |
52 | Assert.assertArrayEquals(new byte[]{10, 1, 2, 3, 12, 13, 14}, left.toByteArray());
53 | Assert.assertArrayEquals(new byte[]{10, 1, 2, 3, 12, 13, 14}, right.toByteArray());
54 | }
55 |
56 | @Test
57 | public void shouldCloseBothStreamsWhenClosingTee() throws IOException {
58 | RememberCloseOutputStream left = new RememberCloseOutputStream(NullOutputStream.NULL_OUTPUT_STREAM);
59 | RememberCloseOutputStream right = new RememberCloseOutputStream(NullOutputStream.NULL_OUTPUT_STREAM);
60 | TeeOutputStream teeOutputStream = new TeeOutputStream(left, right);
61 |
62 | teeOutputStream.close();
63 |
64 | Assert.assertTrue(left.isClosed());
65 | Assert.assertTrue(right.isClosed());
66 | }
67 |
68 | @Test
69 | public void shouldCloseSecondStreamWhenClosingFirstFails() {
70 | ExceptionOnCloseByteArrayOutputStream left = new ExceptionOnCloseByteArrayOutputStream(NullOutputStream.NULL_OUTPUT_STREAM);
71 | RememberCloseOutputStream right = new RememberCloseOutputStream(NullOutputStream.NULL_OUTPUT_STREAM);
72 | TeeOutputStream teeOutputStream = new TeeOutputStream(left, right);
73 | try {
74 | teeOutputStream.close();
75 | Assert.fail("Was expecting an exception!");
76 | } catch (IOException expected) {
77 |
78 | }
79 |
80 | Assert.assertTrue(left.isClosed());
81 | Assert.assertTrue(right.isClosed());
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/ProcessOutput.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | import java.io.UnsupportedEncodingException;
21 | import java.util.ArrayList;
22 | import java.util.List;
23 | import java.util.StringTokenizer;
24 |
25 | /**
26 | * Standard output of a finished process.
27 | *
28 | * @author Rein Raudjärv
29 | * @see ProcessExecutor
30 | */
31 | public class ProcessOutput {
32 |
33 | /**
34 | * Process output (not null).
35 | */
36 | private final byte[] data;
37 |
38 | public ProcessOutput(byte[] data) {
39 | this.data = data;
40 | }
41 |
42 | /**
43 | * @return binary output of the finished process.
44 | */
45 | public byte[] getBytes() {
46 | return data;
47 | }
48 |
49 | /**
50 | * @return output of the finished process converted to a String using platform's default encoding.
51 | */
52 | public String getString() {
53 | return new String(getBytes());
54 | }
55 |
56 | /**
57 | * @return output of the finished process converted to UTF-8 String.
58 | */
59 | public String getUTF8() {
60 | return getString("UTF-8");
61 | }
62 |
63 | /**
64 | * @return output of the finished process converted to a String.
65 | *
66 | * @param charset The name of a supported char set.
67 | */
68 | public String getString(String charset) {
69 | try {
70 | return new String(getBytes(), charset);
71 | }
72 | catch (UnsupportedEncodingException e) {
73 | throw new IllegalStateException(e.getMessage());
74 | }
75 | }
76 |
77 | /**
78 | * @return output lines of the finished process converted using platform's default encoding.
79 | */
80 | public List getLines() {
81 | return getLinesFrom(getString());
82 | }
83 |
84 | /**
85 | * @return output lines of the finished process converted using UTF-8.
86 | */
87 | public List getLinesAsUTF8() {
88 | return getLinesFrom(getUTF8());
89 | }
90 |
91 | /**
92 | * @return output lines of the finished process converted using a given char set.
93 | *
94 | * @param charset The name of a supported char set.
95 | */
96 | public List getLines(String charset) {
97 | return getLinesFrom(getString(charset));
98 | }
99 |
100 | static List getLinesFrom(String output) {
101 | // Split using both Windows and UNIX line separators
102 | List result = new ArrayList();
103 | StringTokenizer st = new StringTokenizer(output, "\n\r");
104 | while (st.hasMoreTokens()) {
105 | result.add(st.nextToken());
106 | }
107 | return result;
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | fail-fast: false
10 | matrix:
11 | java: [ 6, 8, 11, 17 ]
12 | name: Build with Java ${{ matrix.java }}
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v3
16 |
17 | - name: Set up JDK 6
18 | if: ${{ matrix.java == '6'}}
19 | # We use v1 as newer versions do not seem to support Java 6
20 | # Inspired by https://github.com/junit-team/junit4/blob/main/.github/workflows/main.yml
21 | uses: actions/setup-java@v1
22 | with:
23 | java-version: ${{ matrix.java }}
24 | mvn-toolchain-id: ${{ matrix.java }}
25 | - name: Set up toolchains.xml for Java 6
26 | # We need to manually set up toolchains.xml as setup-java@v1 does not seem to support toolchains.xml
27 | if: ${{ matrix.java == '6'}}
28 | run: |
29 | echo "" >> $HOME/.m2/toolchains.xml
30 | echo "" >> $HOME/.m2/toolchains.xml
31 | echo " " >> $HOME/.m2/toolchains.xml
32 | echo " jdk" >> $HOME/.m2/toolchains.xml
33 | echo " " >> $HOME/.m2/toolchains.xml
34 | echo " 6" >> $HOME/.m2/toolchains.xml
35 | echo " " >> $HOME/.m2/toolchains.xml
36 | echo " " >> $HOME/.m2/toolchains.xml
37 | echo " ${JAVA_HOME_6_0_119_X64}" >> $HOME/.m2/toolchains.xml
38 | echo " " >> $HOME/.m2/toolchains.xml
39 | echo " " >> $HOME/.m2/toolchains.xml
40 | echo "" >> $HOME/.m2/toolchains.xml
41 | cat $HOME/.m2/toolchains.xml
42 | - name: Set up JDK
43 | if: ${{ matrix.java != '6'}}
44 | uses: actions/setup-java@v3
45 | with:
46 | java-version: ${{ matrix.java }}
47 | mvn-toolchain-id: ${{ matrix.java }}
48 | distribution: temurin
49 | cache: maven
50 | - name: Set up JDK 17
51 | # Set up JDK 17 as the last step before building so it becomes default and Maven would use it
52 | uses: actions/setup-java@v3
53 | with:
54 | java-version: 17
55 | mvn-toolchain-id: 17
56 | distribution: temurin
57 | cache: maven
58 |
59 | - name: Build on Java 6, run tests with Java 17
60 | # Java 6 is special case, we need to activate a profile on it
61 | if: ${{ matrix.java == '6'}}
62 | run: |
63 | chmod +x mvnw
64 | ./mvnw -ntp -B -P java6 -DtestJdk=17 test
65 |
66 | - name: Build on Java 8, run tests with Java 17
67 | # Java 8 is also a special case, we need to activate a profile on it
68 | if: ${{ matrix.java == '8'}}
69 | run: |
70 | chmod +x mvnw
71 | ./mvnw -ntp -B -P java8 -DtestJdk=17 test
72 |
73 | - name: Build and test on Java ${{ matrix.java }}
74 | # More modern JDKs support specifying a target JDK release for the compiler
75 | if: ${{ matrix.java != '6' && matrix.java != '8' }}
76 | run: |
77 | chmod +x mvnw
78 | ./mvnw -ntp -B -DmainJdk=${{ matrix.java }} verify package
79 | mkdir upload && cp target/*.jar upload
80 |
81 | - uses: actions/upload-artifact@v3
82 | with:
83 | name: Upload artifacts
84 | path: upload
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/listener/CompositeProcessListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.listener;
19 |
20 | import java.util.Iterator;
21 | import java.util.List;
22 | import java.util.concurrent.CopyOnWriteArrayList;
23 |
24 | import org.zeroturnaround.exec.ProcessExecutor;
25 | import org.zeroturnaround.exec.ProcessResult;
26 |
27 |
28 | /**
29 | * Composite process event handler.
30 | *
31 | * @author Rein Raudjärv
32 | */
33 | public class CompositeProcessListener extends ProcessListener implements Cloneable {
34 |
35 | private final List children = new CopyOnWriteArrayList();
36 |
37 | public CompositeProcessListener() {
38 | // no children
39 | }
40 |
41 | public CompositeProcessListener(List children) {
42 | this.children.addAll(children);
43 | }
44 |
45 | /**
46 | * Add new listener.
47 | *
48 | * @param listener listener to be added.
49 | */
50 | public void add(ProcessListener listener) {
51 | children.add(listener);
52 | }
53 |
54 | /**
55 | * Remove existing listener.
56 | *
57 | * @param listener listener to be removed.
58 | */
59 | public void remove(ProcessListener listener) {
60 | children.remove(listener);
61 | }
62 |
63 | /**
64 | * Remove existing listeners of given type or its sub-types.
65 | *
66 | * @param type listener type.
67 | */
68 | public void removeAll(Class extends ProcessListener> type) {
69 | for (Iterator it = children.iterator(); it.hasNext();) {
70 | if (type.isInstance(it.next()))
71 | it.remove();
72 | }
73 | }
74 |
75 | /**
76 | * Remove all existing listeners.
77 | */
78 | public void clear() {
79 | children.clear();
80 | }
81 |
82 | public CompositeProcessListener clone() {
83 | return new CompositeProcessListener(children);
84 | }
85 |
86 | @Override
87 | public void beforeStart(ProcessExecutor executor) {
88 | for (ProcessListener child : children) {
89 | child.beforeStart(executor);
90 | }
91 | }
92 |
93 | @Override
94 | public void afterStart(Process process, ProcessExecutor executor) {
95 | for (ProcessListener child : children) {
96 | child.afterStart(process, executor);
97 | }
98 | }
99 |
100 | @Override
101 | public void afterFinish(Process process, ProcessResult result) {
102 | for (ProcessListener child : children) {
103 | child.afterFinish(process, result);
104 | }
105 | }
106 |
107 | @Override
108 | public void afterStop(Process process) {
109 | for (ProcessListener child : children) {
110 | child.afterStop(process);
111 | }
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/stream/ExecuteStreamHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * NOTICE: This file originates from the Apache Commons Exec package.
19 | * It has been modified to fit our needs.
20 | *
21 | * The following is the original header of the file in Apache Commons Exec:
22 | *
23 | * Licensed to the Apache Software Foundation (ASF) under one or more
24 | * contributor license agreements. See the NOTICE file distributed with
25 | * this work for additional information regarding copyright ownership.
26 | * The ASF licenses this file to You under the Apache License, Version 2.0
27 | * (the "License"); you may not use this file except in compliance with
28 | * the License. You may obtain a copy of the License at
29 | *
30 | * http://www.apache.org/licenses/LICENSE-2.0
31 | *
32 | * Unless required by applicable law or agreed to in writing, software
33 | * distributed under the License is distributed on an "AS IS" BASIS,
34 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 | * See the License for the specific language governing permissions and
36 | * limitations under the License.
37 | */
38 | package org.zeroturnaround.exec.stream;
39 |
40 | import java.io.IOException;
41 | import java.io.InputStream;
42 | import java.io.OutputStream;
43 |
44 | /**
45 | * Used by Execute to handle input and output stream of
46 | * subprocesses.
47 | */
48 | public interface ExecuteStreamHandler {
49 |
50 | /**
51 | * Install a handler for the input stream of the subprocess.
52 | *
53 | * @param os
54 | * output stream to write to the standard input stream of the
55 | * subprocess
56 | * @throws IOException throws a IO exception in case of IO issues of the underlying stream
57 | */
58 | void setProcessInputStream(OutputStream os) throws IOException;
59 |
60 | /**
61 | * Install a handler for the error stream of the subprocess.
62 | *
63 | * @param is
64 | * input stream to read from the error stream from the subprocess
65 | * @throws IOException throws a IO exception in case of IO issues of the underlying stream
66 | */
67 | void setProcessErrorStream(InputStream is) throws IOException;
68 |
69 | /**
70 | * Install a handler for the output stream of the subprocess.
71 | *
72 | * @param is
73 | * input stream to read from the error stream from the subprocess
74 | * @throws IOException throws a IO exception in case of IO issues of the underlying stream
75 | */
76 | void setProcessOutputStream(InputStream is) throws IOException;
77 |
78 | /**
79 | * Start handling of the streams.
80 | *
81 | * @throws IOException throws a IO exception in case of IO issues of the underlying stream
82 | */
83 | void start() throws IOException;
84 |
85 | /**
86 | * Stop handling of the streams - will not be restarted.
87 | * Will wait for pump threads to complete.
88 | */
89 | void stop();
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/stream/InputStreamPumper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * NOTICE: This file originates from the Apache Commons Exec package.
19 | * It has been modified to fit our needs.
20 | *
21 | * The following is the original header of the file in Apache Commons Exec:
22 | *
23 | * Licensed to the Apache Software Foundation (ASF) under one or more
24 | * contributor license agreements. See the NOTICE file distributed with
25 | * this work for additional information regarding copyright ownership.
26 | * The ASF licenses this file to You under the Apache License, Version 2.0
27 | * (the "License"); you may not use this file except in compliance with
28 | * the License. You may obtain a copy of the License at
29 | *
30 | * http://www.apache.org/licenses/LICENSE-2.0
31 | *
32 | * Unless required by applicable law or agreed to in writing, software
33 | * distributed under the License is distributed on an "AS IS" BASIS,
34 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 | * See the License for the specific language governing permissions and
36 | * limitations under the License.
37 | */
38 | package org.zeroturnaround.exec.stream;
39 |
40 | import java.io.InputStream;
41 | import java.io.OutputStream;
42 |
43 | import org.slf4j.Logger;
44 | import org.slf4j.LoggerFactory;
45 |
46 | /**
47 | * Copies all data from an System.input stream to an output stream of the executed process.
48 | *
49 | * @author mkleint
50 | */
51 | public class InputStreamPumper implements Runnable {
52 |
53 | private static final Logger log = LoggerFactory.getLogger(InputStreamPumper.class);
54 |
55 | public static final int SLEEPING_TIME = 100;
56 |
57 | /** the input stream to pump from */
58 | private final InputStream is;
59 |
60 | /** the output stream to pmp into */
61 | private final OutputStream os;
62 |
63 | /** flag to stop the stream pumping */
64 | private volatile boolean stop;
65 |
66 | /**
67 | * Create a new stream pumper.
68 | *
69 | * @param is input stream to read data from
70 | * @param os output stream to write data to.
71 | */
72 | public InputStreamPumper(final InputStream is, final OutputStream os) {
73 | this.is = is;
74 | this.os = os;
75 | this.stop = false;
76 | }
77 |
78 | /**
79 | * Copies data from the input stream to the output stream. Terminates as
80 | * soon as the input stream is closed or an error occurs.
81 | */
82 | public void run() {
83 | try {
84 | while (!stop) {
85 | while (is.available() > 0 && !stop) {
86 | os.write(is.read());
87 | }
88 | os.flush();
89 | Thread.sleep(SLEEPING_TIME);
90 | }
91 | }
92 | catch (Exception e) {
93 | log.error("Got exception while reading/writing the stream", e);
94 | }
95 | }
96 |
97 | public void stopProcessing() {
98 | stop = true;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/close/StandardProcessCloser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.close;
19 |
20 | import java.io.IOException;
21 |
22 | import org.slf4j.Logger;
23 | import org.slf4j.LoggerFactory;
24 | import org.zeroturnaround.exec.stream.ExecuteStreamHandler;
25 |
26 | /**
27 | * Stops {@link ExecuteStreamHandler} from pumping the streams and closes them.
28 | */
29 | public class StandardProcessCloser implements ProcessCloser {
30 |
31 | private static final Logger log = LoggerFactory.getLogger(StandardProcessCloser.class);
32 |
33 | protected final ExecuteStreamHandler streams;
34 |
35 | public StandardProcessCloser(ExecuteStreamHandler streams) {
36 | this.streams = streams;
37 | }
38 |
39 | public void close(Process process) throws IOException, InterruptedException {
40 | if (streams != null) {
41 | streams.stop();
42 | }
43 | closeStreams(process);
44 | }
45 |
46 | /**
47 | * Close the streams belonging to the given Process.
48 | */
49 | private void closeStreams(final Process process) throws IOException {
50 | IOException caught = null;
51 |
52 | try {
53 | process.getOutputStream().close();
54 | }
55 | catch (IOException e) {
56 | if (e.getMessage().equals("Stream closed")) {
57 | /**
58 | * OutputStream's contract for the close() method: If the stream is already closed then invoking this method has no effect.
59 | *
60 | * When a UNIXProcess exits ProcessPipeOutputStream automatically closes its target FileOutputStream and replaces it with NullOutputStream.
61 | * However the ProcessPipeOutputStream doesn't close itself at that moment.
62 | * As ProcessPipeOutputStream extends BufferedOutputStream extends FilterOutputStream closing it flushes the buffer first.
63 | * In Java 7 closing FilterOutputStream ignores any exception thrown by the target OutputStream. Since Java 8 these exceptions are now thrown.
64 | *
65 | * So since Java 8 after UNIXProcess detects the exit and there's something in the output buffer closing this stream throws IOException
66 | * with message "Stream closed" from NullOutputStream.
67 | */
68 | log.trace("Failed to close process output stream:", e);
69 | }
70 | else {
71 | caught = add(caught, e);
72 | }
73 | }
74 |
75 | try {
76 | process.getInputStream().close();
77 | }
78 | catch (IOException e) {
79 | caught = add(caught, e);
80 | }
81 |
82 | try {
83 | process.getErrorStream().close();
84 | }
85 | catch (IOException e) {
86 | caught = add(caught, e);
87 | }
88 |
89 | if (caught != null) {
90 | throw caught;
91 | }
92 | }
93 |
94 | private static IOException add(IOException exception, IOException newException) {
95 | if (exception == null) {
96 | return newException;
97 | }
98 | ExceptionUtil.addSuppressed(exception, newException);
99 | return exception;
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/close/TimeoutProcessCloser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.close;
19 |
20 | import java.io.IOException;
21 | import java.util.concurrent.Callable;
22 | import java.util.concurrent.ExecutionException;
23 | import java.util.concurrent.ExecutorService;
24 | import java.util.concurrent.Executors;
25 | import java.util.concurrent.Future;
26 | import java.util.concurrent.TimeUnit;
27 | import java.util.concurrent.TimeoutException;
28 |
29 | import org.slf4j.Logger;
30 | import org.slf4j.LoggerFactory;
31 | import org.zeroturnaround.exec.stream.ExecuteStreamHandler;
32 | import org.zeroturnaround.exec.stream.PumpStreamHandler;
33 |
34 | /**
35 | * Same as {@link StandardProcessCloser} but only waits fixed period for the closing.
36 | * On timeout a warning is logged but no error is thrown.
37 | *
38 | * This is used on Windows where sometimes sub process' streams do not close properly.
39 | */
40 | public class TimeoutProcessCloser extends StandardProcessCloser {
41 |
42 | private static final Logger log = LoggerFactory.getLogger(TimeoutProcessCloser.class);
43 |
44 | private final long timeout;
45 |
46 | private final TimeUnit unit;
47 |
48 | /**
49 | * Creates new instance of {@link TimeoutProcessCloser}.
50 | *
51 | * @param streams helper for pumping the streams.
52 | * @param timeout how long should we wait for the closing.
53 | * @param unit unit of the timeout value.
54 | */
55 | public TimeoutProcessCloser(ExecuteStreamHandler streams, long timeout, TimeUnit unit) {
56 | super(streams);
57 | this.timeout = timeout;
58 | this.unit = unit;
59 | }
60 |
61 | public void close(final Process process) throws IOException, InterruptedException {
62 | ExecutorService service = Executors.newSingleThreadScheduledExecutor();
63 | Future future = service.submit(new Callable() {
64 | public Void call() throws Exception {
65 | doClose(process);
66 | return null;
67 | }
68 | });
69 | // Previously submitted tasks are executed but no new tasks will be accepted.
70 | service.shutdown();
71 |
72 | try {
73 | future.get(timeout, unit);
74 | }
75 | catch (ExecutionException e) {
76 | throw new IllegalStateException("Could not close streams of " + process, e.getCause());
77 | }
78 | catch (TimeoutException e) {
79 | log.warn("Could not close streams of {} in {} {}", process, timeout, getUnitsAsString(timeout, unit));
80 | }
81 | finally {
82 | // Ensure that any data received so far is flushed from buffers
83 | if (streams instanceof PumpStreamHandler) {
84 | ((PumpStreamHandler) streams).flush();
85 | }
86 | }
87 | }
88 |
89 | protected void doClose(final Process process) throws IOException, InterruptedException {
90 | super.close(process);
91 | }
92 |
93 | private static String getUnitsAsString(long d, TimeUnit unit) {
94 | String result = unit.toString().toLowerCase();
95 | if (d == 1) {
96 | result = result.substring(0, result.length() - 1);
97 | }
98 | return result;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/ProcessResult.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | /**
21 | * Exit value and output of a finished process.
22 | *
23 | * @author Rein Raudjärv
24 | * @see ProcessExecutor
25 | */
26 | public class ProcessResult {
27 |
28 | /**
29 | * Exit value of the finished process.
30 | */
31 | private final int exitValue;
32 |
33 | /**
34 | * Process output or null if it wasn't read.
35 | */
36 | private final ProcessOutput output;
37 |
38 | public ProcessResult(int exitCode, ProcessOutput output) {
39 | this.exitValue = exitCode;
40 | this.output = output;
41 | }
42 |
43 | /**
44 | * @return the exit value of the finished process.
45 | */
46 | public int getExitValue() {
47 | return exitValue;
48 | }
49 |
50 | /**
51 | * @return the exit value of the finished process.
52 | * @deprecated use {@link #getExitValue()}
53 | */
54 | public int exitValue() {
55 | return getExitValue();
56 | }
57 |
58 | /**
59 | * @return true if the process output was read.
60 | */
61 | public boolean hasOutput() {
62 | return output != null;
63 | }
64 |
65 | /**
66 | * @return output of the finished process.
67 | * You have to invoke {@link ProcessExecutor#readOutput(boolean)} to set the process output to be read.
68 | *
69 | * @throws IllegalStateException if the output was not read.
70 | */
71 | public ProcessOutput getOutput() {
72 | if (output == null)
73 | throw new IllegalStateException("Process output was not read. To enable output reading please call ProcessExecutor.readOutput(true) before starting the process.");
74 | return output;
75 | }
76 |
77 | /**
78 | * @return binary output of the finished process.
79 | * You have to invoke {@link ProcessExecutor#readOutput(boolean)} to set the process output to be read.
80 | *
81 | * @throws IllegalStateException if the output was not read.
82 | */
83 | public byte[] output() {
84 | return getOutput().getBytes();
85 | }
86 |
87 | /**
88 | * @return output of the finished process converted to a String using platform's default encoding.
89 | * You have to invoke {@link ProcessExecutor#readOutput(boolean)} to set the process output to be read.
90 | *
91 | * @throws IllegalStateException if the output was not read.
92 | */
93 | public String outputString() {
94 | return getOutput().getString();
95 | }
96 |
97 | /**
98 | * @return output of the finished process converted to UTF-8 String.
99 | * You have to invoke {@link ProcessExecutor#readOutput(boolean)} to set the process output to be read.
100 | *
101 | * @throws IllegalStateException if the output was not read.
102 | */
103 | public String outputUTF8() {
104 | return getOutput().getUTF8();
105 | }
106 |
107 | /**
108 | * @return output of the finished process converted to a String.
109 | * You have to invoke {@link ProcessExecutor#readOutput(boolean)} to set the process output to be read.
110 | *
111 | * @param charset The name of a supported char set.
112 | * @throws IllegalStateException if the output was not read or the char set was not supported.
113 | */
114 | public String outputString(String charset) {
115 | return getOutput().getString(charset);
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/stream/slf4j/Slf4jStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.stream.slf4j;
19 |
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import org.zeroturnaround.exec.stream.CallerLoggerUtil;
23 |
24 | /**
25 | * Creates output streams that write to {@link Logger}s.
26 | *
27 | * @author Rein Raudjärv
28 | */
29 | public class Slf4jStream {
30 |
31 | private final Logger log;
32 |
33 | private Slf4jStream(Logger log) {
34 | this.log = log;
35 | }
36 |
37 | /**
38 | * @param log logger which an output stream redirects to.
39 | * @return Slf4jStream with the given logger.
40 | */
41 | public static Slf4jStream of(Logger log) {
42 | return new Slf4jStream(log);
43 | }
44 |
45 | /**
46 | * @param klass class which is used to get the logger's name.
47 | * @return Slf4jStream with a logger named after the given class.
48 | */
49 | public static Slf4jStream of(Class> klass) {
50 | return of(LoggerFactory.getLogger(klass));
51 | }
52 |
53 | /**
54 | * Constructs a logger from a class name and an additional name,
55 | * appended to the end. So the final logger name will be:
56 | * klass.getName() + "." + name
57 | *
58 | * @param klass class which is used to get the logger's name.
59 | * @param name logger's name, appended to the class name.
60 | * @return Slf4jStream with a logger named after the given class.
61 | *
62 | * @since 1.8
63 | */
64 | public static Slf4jStream of(Class> klass, String name) {
65 | if (name == null) {
66 | return of(klass);
67 | } else {
68 | return of(LoggerFactory.getLogger(klass.getName() + "." + name));
69 | }
70 | }
71 |
72 | /**
73 | * @param name logger's name (full or short).
74 | * In case of short name (no dots) the given name is prefixed by caller's class name and a dot.
75 | * @return Slf4jStream with the given logger.
76 | */
77 | public static Slf4jStream of(String name) {
78 | return of(LoggerFactory.getLogger(CallerLoggerUtil.getName(name, 1)));
79 | }
80 |
81 | /**
82 | * @return Slf4jStream with the logger of caller of this method.
83 | */
84 | public static Slf4jStream ofCaller() {
85 | return of(LoggerFactory.getLogger(CallerLoggerUtil.getName(null, 1)));
86 | }
87 |
88 | /**
89 | * @param level the desired logging level
90 | * @return output stream that writes with a given level.
91 | */
92 | public Slf4jOutputStream as(Level level) {
93 | switch (level) {
94 | case TRACE: return asTrace();
95 | case DEBUG: return asDebug();
96 | case INFO: return asInfo();
97 | case WARN: return asWarn();
98 | case ERROR: return asError();
99 | }
100 | throw new IllegalArgumentException("Invalid level " + level);
101 | }
102 |
103 | /**
104 | * @return output stream that writes trace level.
105 | */
106 | public Slf4jOutputStream asTrace() {
107 | return new Slf4jTraceOutputStream(log);
108 | }
109 |
110 | /**
111 | * @return output stream that writes debug level.
112 | */
113 | public Slf4jOutputStream asDebug() {
114 | return new Slf4jDebugOutputStream(log);
115 | }
116 |
117 | /**
118 | * @return output stream that writes info level.
119 | */
120 | public Slf4jOutputStream asInfo() {
121 | return new Slf4jInfoOutputStream(log);
122 | }
123 |
124 | /**
125 | * @return output stream that writes warn level.
126 | */
127 | public Slf4jOutputStream asWarn() {
128 | return new Slf4jWarnOutputStream(log);
129 | }
130 |
131 | /**
132 | * @return output stream that writes error level.
133 | */
134 | public Slf4jOutputStream asError() {
135 | return new Slf4jErrorOutputStream(log);
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorTimeoutTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import org.apache.commons.lang3.SystemUtils;
21 | import org.hamcrest.CoreMatchers;
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 | import org.zeroturnaround.exec.ProcessExecutor;
25 |
26 | import java.util.ArrayList;
27 | import java.util.List;
28 | import java.util.concurrent.TimeUnit;
29 | import java.util.concurrent.TimeoutException;
30 |
31 | public class ProcessExecutorTimeoutTest {
32 |
33 | @Test
34 | public void testExecuteTimeout() throws Exception {
35 | try {
36 | // Use timeout in case we get stuck
37 | List args = getWriterLoopCommand();
38 | new ProcessExecutor().command(args).timeout(1, TimeUnit.SECONDS).execute();
39 | Assert.fail("TimeoutException expected.");
40 | }
41 | catch (TimeoutException e) {
42 | Assert.assertThat(e.getMessage(), CoreMatchers.containsString("1 second"));
43 | Assert.assertThat(e.getMessage(), CoreMatchers.containsString(Loop.class.getName()));
44 | }
45 | }
46 |
47 | @Test
48 | public void testStartTimeout() throws Exception {
49 | try {
50 | // Use timeout in case we get stuck
51 | List args = getWriterLoopCommand();
52 | new ProcessExecutor().command(args).start().getFuture().get(1, TimeUnit.SECONDS);
53 | Assert.fail("TimeoutException expected.");
54 | }
55 | catch (TimeoutException e) {
56 | Assert.assertNull(e.getMessage());
57 | }
58 | }
59 |
60 | private List getWriterLoopCommand() {
61 | List args = new ArrayList() {
62 | {
63 | add("java");
64 | add("-cp");
65 | add("target/test-classes");
66 | add(Loop.class.getName());
67 | }
68 | };
69 | return args;
70 | }
71 |
72 | /*
73 | * This is a test copied from https://github.com/zeroturnaround/zt-exec/issues/56
74 | */
75 | @Test
76 | public void testExecuteTimeoutIssue56_1() throws Exception {
77 | try {
78 | List commands = new ArrayList();
79 | if (SystemUtils.IS_OS_WINDOWS) {
80 | // native sleep command is not available on Windows platform
81 | // mock using standard ping to localhost instead
82 | // (Windows ping does 4 requests which takes about 3 seconds)
83 | commands.add("ping");
84 | commands.add("127.0.0.1");
85 | }
86 | else {
87 | commands.add("sleep");
88 | commands.add("3");
89 | }
90 | new ProcessExecutor()
91 | .command(commands)
92 | .timeout(1, TimeUnit.SECONDS)
93 | .execute();
94 | Assert.fail("TimeoutException expected.");
95 | }
96 | catch (TimeoutException e) {
97 | Assert.assertThat(e.getMessage(), CoreMatchers.containsString("1 second"));
98 | }
99 | }
100 |
101 | /*
102 | * This is a test copied from https://github.com/zeroturnaround/zt-exec/issues/56
103 | */
104 | @Test
105 | public void testStartTimeoutIssue56_2() throws Exception {
106 | try {
107 | List commands = new ArrayList();
108 | if (SystemUtils.IS_OS_WINDOWS) {
109 | // native sleep command is not available on Windows platform
110 | // mock using standard ping to localhost instead
111 | // (Windows ping does 4 requests which takes about 3 seconds)
112 | commands.add("ping");
113 | commands.add("127.0.0.1");
114 | }
115 | else {
116 | commands.add("sleep");
117 | commands.add("3");
118 | }
119 | new ProcessExecutor()
120 | .command(commands)
121 | .start()
122 | .getFuture()
123 | .get(1, TimeUnit.SECONDS);
124 | Assert.fail("TimeoutException expected.");
125 | }
126 | catch (TimeoutException e) {
127 | Assert.assertNull(e.getMessage());
128 | }
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/ReadmeExamples.java:
--------------------------------------------------------------------------------
1 | package org.zeroturnaround.exec;
2 |
3 | import java.io.OutputStream;
4 | import java.util.Map;
5 | import java.util.concurrent.Future;
6 | import java.util.concurrent.TimeUnit;
7 | import java.util.concurrent.TimeoutException;
8 |
9 | import org.slf4j.LoggerFactory;
10 | import org.zeroturnaround.exec.stream.LogOutputStream;
11 | import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
12 |
13 | /**
14 | * Examples of the readme.
15 | */
16 | class ReadmeExamples {
17 |
18 | void justExecute() throws Exception {
19 | new ProcessExecutor().command("java", "-version").execute();
20 | }
21 |
22 | int getExitCode() throws Exception {
23 | int exit = new ProcessExecutor().command("java", "-version")
24 | .execute().getExitValue();
25 | return exit;
26 | }
27 |
28 | String getOutput() throws Exception {
29 | String output = new ProcessExecutor().command("java", "-version")
30 | .readOutput(true).execute()
31 | .outputUTF8();
32 | return output;
33 | }
34 |
35 | void pumpOutputToLogger() throws Exception {
36 | new ProcessExecutor().command("java", "-version")
37 | .redirectOutput(Slf4jStream.of(LoggerFactory.getLogger(getClass().getName() + ".MyProcess")).asInfo()).execute();
38 | }
39 |
40 | void pumpOutputToLoggerShorter() throws Exception {
41 | new ProcessExecutor().command("java", "-version")
42 | .redirectOutput(Slf4jStream.of("MyProcess").asInfo()).execute();
43 | }
44 |
45 | void pumpOutputToLoggerOfCaller() throws Exception {
46 | new ProcessExecutor().command("java", "-version")
47 | .redirectOutput(Slf4jStream.ofCaller().asInfo()).execute();
48 | }
49 |
50 | String pumpOutputToLoggerAndGetOutput() throws Exception {
51 | String output = new ProcessExecutor().command("java", "-version")
52 | .redirectOutput(Slf4jStream.of(getClass()).asInfo())
53 | .readOutput(true).execute().outputUTF8();
54 | return output;
55 | }
56 |
57 | String pumpErrorToLoggerAndGetOutput() throws Exception {
58 | String output = new ProcessExecutor().command("java", "-version")
59 | .redirectError(Slf4jStream.of(getClass()).asInfo())
60 | .readOutput(true).execute()
61 | .outputUTF8();
62 | return output;
63 | }
64 |
65 | void executeWithTimeout() throws Exception {
66 | try {
67 | new ProcessExecutor().command("java", "-version")
68 | .timeout(60, TimeUnit.SECONDS).execute();
69 | }
70 | catch (TimeoutException e) {
71 | // process is automatically destroyed
72 | }
73 | }
74 |
75 | void pumpOutputToStream(OutputStream out) throws Exception {
76 | new ProcessExecutor().command("java", "-version")
77 | .redirectOutput(out).execute();
78 | }
79 |
80 | void pumpOutputToLogStream(OutputStream out) throws Exception {
81 | new ProcessExecutor().command("java", "-version")
82 | .redirectOutput(new LogOutputStream() {
83 | @Override
84 | protected void processLine(String line) {
85 | // ...
86 | }
87 | })
88 | .execute();
89 | }
90 |
91 | void destroyProcessOnJvmExit() throws Exception {
92 | new ProcessExecutor().command("java", "-version").destroyOnExit().execute();
93 | }
94 |
95 | void executeWithEnvironmentVariable() throws Exception {
96 | new ProcessExecutor().command("java", "-version")
97 | .environment("foo", "bar")
98 | .execute();
99 | }
100 |
101 | void executeWithEnvironment(Map env) throws Exception {
102 | new ProcessExecutor().command("java", "-version")
103 | .environment(env)
104 | .execute();
105 | }
106 |
107 | void checkExitCode() throws Exception {
108 | try {
109 | new ProcessExecutor().command("java", "-version")
110 | .exitValues(3).execute();
111 | }
112 | catch (InvalidExitValueException e) {
113 | System.out.println("Process exited with " + e.getExitValue());
114 | }
115 | }
116 |
117 | void checkExitCodeAndGetOutput() throws Exception {
118 | String output;
119 | boolean success = false;
120 | try {
121 | output = new ProcessExecutor().command("java", "-version")
122 | .readOutput(true).exitValues(3)
123 | .execute().outputUTF8();
124 | success = true;
125 | }
126 | catch (InvalidExitValueException e) {
127 | System.out.println("Process exited with " + e.getExitValue());
128 | output = e.getResult().outputUTF8();
129 | }
130 | }
131 |
132 | void startInBackground() throws Exception {
133 | Future future = new ProcessExecutor()
134 | .command("java", "-version")
135 | .start().getFuture();
136 | //do some stuff
137 | future.get(60, TimeUnit.SECONDS);
138 | }
139 |
140 | String startInBackgroundAndGetOutput() throws Exception {
141 | Future future = new ProcessExecutor()
142 | .command("java", "-version")
143 | .readOutput(true)
144 | .start().getFuture();
145 | //do some stuff
146 | String output = future.get(60, TimeUnit.SECONDS).outputUTF8();
147 | return output;
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/WaitForProcess.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec;
19 |
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.IOException;
22 | import java.util.concurrent.Callable;
23 |
24 | import org.slf4j.Logger;
25 | import org.slf4j.LoggerFactory;
26 | import org.zeroturnaround.exec.close.ProcessCloser;
27 | import org.zeroturnaround.exec.listener.ProcessListener;
28 | import org.zeroturnaround.exec.stop.ProcessStopper;
29 |
30 |
31 | /**
32 | * Handles the executed process.
33 | *
34 | * @author Rein Raudjärv
35 | */
36 | class WaitForProcess implements Callable {
37 |
38 | private static final Logger log = LoggerFactory.getLogger(WaitForProcess.class);
39 |
40 | private final Process process;
41 |
42 | /**
43 | * Set of main attributes used to start the process.
44 | */
45 | private final ProcessAttributes attributes;
46 |
47 | /**
48 | * Helper for stopping the process in case of interruption.
49 | */
50 | private final ProcessStopper stopper;
51 |
52 | /**
53 | * Helper for closing the process' standard streams.
54 | */
55 | private final ProcessCloser closer;
56 |
57 | /**
58 | * Buffer where the process output is redirected to or null if it's not used.
59 | */
60 | private final ByteArrayOutputStream out;
61 |
62 | /**
63 | * Process event listener (not null).
64 | */
65 | private final ProcessListener listener;
66 |
67 | /**
68 | * Helper for logging messages about starting and waiting for the processes.
69 | */
70 | private final MessageLogger messageLogger;
71 |
72 | /**
73 | * Thread which executes this operation.
74 | */
75 | private volatile Thread workerThread;
76 |
77 | public WaitForProcess(Process process, ProcessAttributes attributes, ProcessStopper stopper, ProcessCloser closer, ByteArrayOutputStream out, ProcessListener listener, MessageLogger messageLogger) {
78 | this.process = process;
79 | this.attributes = attributes;
80 | this.stopper = stopper;
81 | this.closer = closer;
82 | this.out = out;
83 | this.listener = listener;
84 | this.messageLogger = messageLogger;
85 | }
86 |
87 | /**
88 | * @return the sub process.
89 | */
90 | public Process getProcess() {
91 | return process;
92 | }
93 |
94 | public ProcessResult call() throws IOException, InterruptedException {
95 | try {
96 | workerThread = Thread.currentThread();
97 | int exit;
98 | boolean finished = false;
99 | try {
100 | exit = process.waitFor();
101 | finished = true;
102 | messageLogger.message(log, "{} stopped with exit code {}", this, exit);
103 | }
104 | finally {
105 | if (!finished) {
106 | messageLogger.message(log, "Stopping {}...", this);
107 | stopper.stop(process);
108 | }
109 |
110 | closer.close(process);
111 | }
112 | ProcessOutput output = getCurrentOutput();
113 | ProcessResult result = new ProcessResult(exit, output);
114 | InvalidExitUtil.checkExit(attributes, result);
115 | listener.afterFinish(process, result);
116 | return result;
117 | }
118 | finally {
119 | // Invoke listeners - regardless process finished or got cancelled
120 | listener.afterStop(process);
121 | workerThread = null;
122 | }
123 | }
124 |
125 | private ProcessOutput getCurrentOutput() {
126 | return out == null ? null : new ProcessOutput(out.toByteArray());
127 | }
128 |
129 | /**
130 | * Adds a suffix for an error message including:
131 | *
132 | * - executed command
133 | * - working directory (unless it's inherited from parent)
134 | * - environment (unless it's the same with the parent)
135 | * - output read so far (unless it's not read)
136 | *
137 | * @param sb where the suffix is appended to.
138 | */
139 | public void addExceptionMessageSuffix(StringBuilder sb) {
140 | InvalidExitUtil.addExceptionMessageSuffix(attributes, sb, getCurrentOutput());
141 | }
142 |
143 | /**
144 | * @return current stacktrace of the worker thread, null if this operation is currently not running.
145 | */
146 | public StackTraceElement[] getStackTrace() {
147 | Thread t = workerThread;
148 | return t == null ? null : t.getStackTrace();
149 | }
150 |
151 | @Override
152 | public String toString() {
153 | return process.toString();
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorHelloWorldTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.ByteArrayOutputStream;
21 |
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 | import org.zeroturnaround.exec.ProcessExecutor;
25 |
26 |
27 | /**
28 | * Tests redirecting stream.
29 | *
30 | * @author Rein Raudjärv
31 | *
32 | * @see ProcessExecutor
33 | * @see HelloWorld
34 | */
35 | public class ProcessExecutorHelloWorldTest {
36 |
37 | @Test
38 | public void testReadOutputAndError() throws Exception {
39 | String output = helloWorld().readOutput(true).execute().outputUTF8();
40 | Assert.assertEquals("Hello world!", output);
41 | }
42 |
43 | @Test
44 | public void testReadOutputOnly() throws Exception {
45 | String output = helloWorld().readOutput(true).redirectErrorStream(false).execute().outputUTF8();
46 | Assert.assertEquals("Hello ", output);
47 | }
48 |
49 | @Test
50 | public void testRedirectOutputAndError() throws Exception {
51 | ByteArrayOutputStream out = new ByteArrayOutputStream();
52 | helloWorld().redirectOutput(out).execute();
53 | Assert.assertEquals("Hello world!", new String(out.toByteArray()));
54 | }
55 |
56 | @Test
57 | public void testRedirectOutputAndErrorMerged() throws Exception {
58 | ByteArrayOutputStream out = new ByteArrayOutputStream();
59 | ByteArrayOutputStream err = new ByteArrayOutputStream();
60 | helloWorld().redirectOutput(out).redirectError(err).execute();
61 | Assert.assertEquals("Hello ", new String(out.toByteArray()));
62 | Assert.assertEquals("world!", new String(err.toByteArray()));
63 | }
64 |
65 | @Test
66 | public void testRedirectOutputAndErrorAndReadOutput() throws Exception {
67 | ByteArrayOutputStream out = new ByteArrayOutputStream();
68 | String output = helloWorld().redirectOutput(out).readOutput(true).execute().outputUTF8();
69 | Assert.assertEquals("Hello world!", output);
70 | Assert.assertEquals("Hello world!", new String(out.toByteArray()));
71 | }
72 |
73 | @Test
74 | public void testRedirectOutputOnly() throws Exception {
75 | ByteArrayOutputStream out = new ByteArrayOutputStream();
76 | helloWorld().redirectOutput(out).redirectErrorStream(false).execute();
77 | Assert.assertEquals("Hello ", new String(out.toByteArray()));
78 | }
79 |
80 | @Test
81 | public void testRedirectOutputOnlyAndReadOutput() throws Exception {
82 | ByteArrayOutputStream out = new ByteArrayOutputStream();
83 | String output = helloWorld().redirectOutput(out).redirectErrorStream(false).readOutput(true).execute().outputUTF8();
84 | Assert.assertEquals("Hello ", output);
85 | Assert.assertEquals("Hello ", new String(out.toByteArray()));
86 | }
87 |
88 | @Test
89 | public void testRedirectErrorOnly() throws Exception {
90 | ByteArrayOutputStream err = new ByteArrayOutputStream();
91 | helloWorld().redirectError(err).redirectErrorStream(false).execute();
92 | Assert.assertEquals("world!", new String(err.toByteArray()));
93 | }
94 |
95 | @Test
96 | public void testRedirectErrorOnlyAndReadOutput() throws Exception {
97 | ByteArrayOutputStream err = new ByteArrayOutputStream();
98 | String output = helloWorld().redirectError(err).redirectErrorStream(false).readOutput(true).execute().outputUTF8();
99 | Assert.assertEquals("Hello ", output);
100 | Assert.assertEquals("world!", new String(err.toByteArray()));
101 | }
102 |
103 | @Test
104 | public void testRedirectOutputAndErrorSeparate() throws Exception {
105 | ByteArrayOutputStream out = new ByteArrayOutputStream();
106 | ByteArrayOutputStream err = new ByteArrayOutputStream();
107 | helloWorld().redirectOutput(out).redirectError(err).redirectErrorStream(false).execute();
108 | Assert.assertEquals("Hello ", new String(out.toByteArray()));
109 | Assert.assertEquals("world!", new String(err.toByteArray()));
110 | }
111 |
112 | @Test
113 | public void testRedirectOutputAndErrorSeparateAndReadOutput() throws Exception {
114 | ByteArrayOutputStream out = new ByteArrayOutputStream();
115 | ByteArrayOutputStream err = new ByteArrayOutputStream();
116 | String output = helloWorld().redirectOutput(out).redirectError(err).redirectErrorStream(false).readOutput(true).execute().outputUTF8();
117 | Assert.assertEquals("Hello ", output);
118 | Assert.assertEquals("Hello ", new String(out.toByteArray()));
119 | Assert.assertEquals("world!", new String(err.toByteArray()));
120 | }
121 |
122 | private ProcessExecutor helloWorld() {
123 | return new ProcessExecutor("java", "-cp", "target/test-classes", HelloWorld.class.getName());
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/stream/LogOutputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.stream;
19 |
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.IOException;
22 | import java.io.OutputStream;
23 | import java.io.UnsupportedEncodingException;
24 |
25 | /**
26 | * Base class to connect a logging system to the output and/or
27 | * error stream of then external process. The implementation
28 | * parses the incoming data to construct a line and passes
29 | * the complete line to an user-defined implementation.
30 | */
31 | public abstract class LogOutputStream extends OutputStream {
32 |
33 | /** Initial buffer size. */
34 | private static final int INITIAL_SIZE = 132;
35 |
36 | /** Carriage return */
37 | private static final int CR = 0x0d;
38 |
39 | /** Linefeed */
40 | private static final int LF = 0x0a;
41 |
42 | /** the internal buffer */
43 | private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(
44 | INITIAL_SIZE);
45 |
46 | byte lastReceivedByte;
47 |
48 | private String outputCharset;
49 |
50 | public LogOutputStream setOutputCharset(final String outputCharset) {
51 | this.outputCharset = outputCharset;
52 | return this;
53 | }
54 |
55 | /**
56 | * Write the data to the buffer and flush the buffer, if a line separator is
57 | * detected.
58 | *
59 | * @param cc data to log (byte).
60 | * @see java.io.OutputStream#write(int)
61 | */
62 | public void write(final int cc) throws IOException {
63 | final byte c = (byte) cc;
64 | if ((c == '\n') || (c == '\r')) {
65 | // new line is started in case of
66 | // - CR (regardless of previous character)
67 | // - LF if previous character was not CR and not LF
68 | if (c == '\r' || (c == '\n' && (lastReceivedByte != '\r' && lastReceivedByte != '\n'))) {
69 | processBuffer();
70 | }
71 | } else {
72 | buffer.write(cc);
73 | }
74 | lastReceivedByte = c;
75 | }
76 |
77 | /**
78 | * Flush this log stream.
79 | *
80 | * @see java.io.OutputStream#flush()
81 | */
82 | public void flush() {
83 | if (buffer.size() > 0) {
84 | processBuffer();
85 | }
86 | }
87 |
88 | /**
89 | * Writes all remaining data from the buffer.
90 | *
91 | * @see java.io.OutputStream#close()
92 | */
93 | public void close() throws IOException {
94 | if (buffer.size() > 0) {
95 | processBuffer();
96 | }
97 | super.close();
98 | }
99 |
100 | /**
101 | * Write a block of characters to the output stream
102 | *
103 | * @param b the array containing the data
104 | * @param off the offset into the array where data starts
105 | * @param len the length of block
106 | * @throws java.io.IOException if the data cannot be written into the stream.
107 | * @see java.io.OutputStream#write(byte[], int, int)
108 | */
109 | public void write(final byte[] b, final int off, final int len)
110 | throws IOException {
111 | // find the line breaks and pass other chars through in blocks
112 | int offset = off;
113 | int blockStartOffset = offset;
114 | int remaining = len;
115 | while (remaining > 0) {
116 | while (remaining > 0 && b[offset] != LF && b[offset] != CR) {
117 | offset++;
118 | remaining--;
119 | }
120 | // either end of buffer or a line separator char
121 | int blockLength = offset - blockStartOffset;
122 | if (blockLength > 0) {
123 | buffer.write(b, blockStartOffset, blockLength);
124 | lastReceivedByte = 0;
125 | }
126 | while (remaining > 0 && (b[offset] == LF || b[offset] == CR)) {
127 | write(b[offset]);
128 | offset++;
129 | remaining--;
130 | }
131 | blockStartOffset = offset;
132 | }
133 | }
134 |
135 | /**
136 | * Converts the buffer to a string and sends it to processLine.
137 | */
138 | protected void processBuffer() {
139 | final String line;
140 | if (outputCharset == null) {
141 | line = buffer.toString();
142 | } else {
143 | try {
144 | line = buffer.toString(outputCharset);
145 | } catch (UnsupportedEncodingException e) {
146 | throw new IllegalArgumentException(e);
147 | }
148 | }
149 | processLine(line);
150 | buffer.reset();
151 | }
152 |
153 | /**
154 | * Logs a line to the log system of the user.
155 | *
156 | * @param line
157 | * the line to log.
158 | */
159 | protected abstract void processLine(String line);
160 |
161 | /**
162 | * Factory method to create a LogOutputStream that passes each line to the specified consumer.
163 | * Mostly useful with Java 8+, so the consumer can be passed as a lambda expression.
164 | *
165 | * @param consumer the consumer to consume the log lines
166 | * @return the created LogOutputStream, passing each line to the specified consumer.
167 | */
168 | public static LogOutputStream create(final LineConsumer consumer) {
169 | if (consumer == null) {
170 | throw new IllegalArgumentException("Line consumer must be provided.");
171 | }
172 | return new LogOutputStream() {
173 | @Override
174 | protected void processLine(String line) {
175 | consumer.accept(line);
176 | }
177 | };
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/test/java/org/zeroturnaround/exec/test/ProcessExecutorMainTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package org.zeroturnaround.exec.test;
19 |
20 | import java.io.File;
21 | import java.io.IOException;
22 | import java.util.ArrayList;
23 | import java.util.Arrays;
24 | import java.util.List;
25 |
26 | import org.apache.commons.lang3.StringUtils;
27 | import org.junit.Assert;
28 | import org.junit.Test;
29 | import org.zeroturnaround.exec.ProcessExecutor;
30 | import org.zeroturnaround.exec.ProcessResult;
31 | import org.zeroturnaround.exec.stream.ExecuteStreamHandler;
32 | import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
33 |
34 |
35 | public class ProcessExecutorMainTest {
36 |
37 | @Test(expected=IllegalStateException.class)
38 | public void testNoCommand() throws Exception {
39 | new ProcessExecutor().execute();
40 | }
41 |
42 | @Test(expected=IOException.class)
43 | public void testNoSuchFile() throws Exception {
44 | new ProcessExecutor().command("unknown command").execute();
45 | }
46 |
47 | @Test
48 | public void testJavaVersion() throws Exception {
49 | int exit = new ProcessExecutor().command("java", "-version").execute().getExitValue();
50 | Assert.assertEquals(0, exit);
51 | }
52 |
53 | @Test
54 | public void testJavaVersionCommandSplit() throws Exception {
55 | int exit = new ProcessExecutor().commandSplit("java -version").execute().getExitValue();
56 | Assert.assertEquals(0, exit);
57 | }
58 |
59 | @Test
60 | public void testJavaVersionIterable() throws Exception {
61 | Iterable iterable = Arrays.asList("java", "-version");
62 | int exit = new ProcessExecutor().command(iterable).execute().getExitValue();
63 | Assert.assertEquals(0, exit);
64 | }
65 |
66 | @Test
67 | public void testJavaVersionFuture() throws Exception {
68 | int exit = new ProcessExecutor().command("java", "-version").start().getFuture().get().getExitValue();
69 | Assert.assertEquals(0, exit);
70 | }
71 |
72 | @Test
73 | public void testJavaVersionOutput() throws Exception {
74 | ProcessResult result = new ProcessExecutor().command("java", "-version").readOutput(true).execute();
75 | String str = result.outputUTF8();
76 | Assert.assertFalse(StringUtils.isEmpty(str));
77 | }
78 |
79 | @Test
80 | public void testJavaVersionOutputTwice() throws Exception {
81 | ProcessExecutor executor = new ProcessExecutor().command("java", "-version").readOutput(true);
82 | ProcessResult result = executor.execute();
83 | String str = result.outputUTF8();
84 | Assert.assertFalse(StringUtils.isEmpty(str));
85 | Assert.assertEquals(str, executor.execute().outputUTF8());
86 | }
87 |
88 | @Test
89 | public void testJavaVersionOutputFuture() throws Exception {
90 | ProcessResult result = new ProcessExecutor().command("java", "-version").readOutput(true).start().getFuture().get();
91 | String str = result.outputUTF8();
92 | Assert.assertFalse(StringUtils.isEmpty(str));
93 | }
94 |
95 | @Test
96 | public void testJavaVersionLogInfo() throws Exception {
97 | // Just expect no errors - don't check the log file itself
98 | new ProcessExecutor().command("java", "-version").redirectOutput(Slf4jStream.of("testJavaVersionLogInfo").asInfo()).execute();
99 | }
100 |
101 | @Test
102 | public void testJavaVersionLogInfoAndOutput() throws Exception {
103 | // Just expect no errors - don't check the log file itself
104 | ProcessResult result = new ProcessExecutor().command("java", "-version").redirectOutput(Slf4jStream.of("testJavaVersionLogInfoAndOutput").asInfo()).readOutput(true).execute();
105 | String str = result.outputUTF8();
106 | Assert.assertFalse(StringUtils.isEmpty(str));
107 | }
108 |
109 | @Test
110 | public void testJavaVersionLogInfoAndOutputFuture() throws Exception {
111 | // Just expect no errors - don't check the log file itself
112 | ProcessResult result = new ProcessExecutor().command("java", "-version").redirectOutput(Slf4jStream.of("testJavaVersionLogInfoAndOutputFuture").asInfo()).readOutput(true).start().getFuture().get();
113 | String str = result.outputUTF8();
114 | Assert.assertFalse(StringUtils.isEmpty(str));
115 | }
116 |
117 | @Test
118 | public void testJavaVersionNoStreams() throws Exception {
119 | // Just expect no errors
120 | new ProcessExecutor().command("java", "-version").streams(null).execute();
121 | }
122 |
123 | @Test
124 | public void testProcessDestroyerEvents() throws Exception {
125 | MockProcessDestroyer mock = new MockProcessDestroyer();
126 | new ProcessExecutor().command("java", "-version").destroyer(mock).execute();
127 | Assert.assertNotNull(mock.added);
128 | Assert.assertEquals(mock.added, mock.removed);
129 | }
130 |
131 | @Test
132 | public void testProcessDestroyerEventsOnStreamsFail() throws Exception {
133 | MockProcessDestroyer mock = new MockProcessDestroyer();
134 | ExecuteStreamHandler streams = new SetFailExecuteStreamHandler();
135 | try {
136 | new ProcessExecutor().command("java", "-version").streams(streams).destroyer(mock).execute();
137 | Assert.fail("IOException expected");
138 | }
139 | catch (IOException e) {
140 | // Good
141 | }
142 | Assert.assertNull(mock.added);
143 | Assert.assertNull(mock.removed);
144 | }
145 |
146 | @Test
147 | public void testProcessExecutorListInit() throws Exception {
148 | // Use timeout in case we get stuck
149 | List args = new ArrayList() {
150 | {
151 | add("java");
152 | add("-cp");
153 | add("target/test-classes");
154 | add(HelloWorld.class.getName());
155 | }
156 | };
157 | ProcessExecutor exec = new ProcessExecutor(args);
158 | ProcessResult result = exec.readOutput(true).execute();
159 | Assert.assertEquals("Hello world!", result.outputUTF8());
160 | }
161 |
162 | @Test
163 | public void testProcessExecutorCommand() throws Exception {
164 | // Use timeout in case we get stuck
165 | List args = new ArrayList() {
166 | {
167 | add("java");
168 | add("-cp");
169 | add("target/test-classes");
170 | add(HelloWorld.class.getName());
171 | }
172 | };
173 | ProcessExecutor exec = new ProcessExecutor();
174 | exec.command(args);
175 | ProcessResult result = exec.readOutput(true).execute();
176 | Assert.assertEquals("Hello world!", result.outputUTF8());
177 | }
178 |
179 | @Test
180 | public void testProcessExecutorSetDirectory() throws Exception {
181 | // Use timeout in case we get stuck
182 | List args = new ArrayList() {
183 | {
184 | add("java");
185 | add("-cp");
186 | add("test-classes");
187 | add(HelloWorld.class.getName());
188 | }
189 | };
190 | ProcessExecutor exec = new ProcessExecutor().directory(new File("target"));
191 | exec.command(args);
192 | ProcessResult result = exec.readOutput(true).execute();
193 | Assert.assertEquals("Hello world!", result.outputUTF8());
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/stream/StreamPumper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * NOTICE: This file originates from the Apache Commons Exec package.
19 | * It has been modified to fit our needs.
20 | *
21 | * The following is the original header of the file in Apache Commons Exec:
22 | *
23 | * Licensed to the Apache Software Foundation (ASF) under one or more
24 | * contributor license agreements. See the NOTICE file distributed with
25 | * this work for additional information regarding copyright ownership.
26 | * The ASF licenses this file to You under the Apache License, Version 2.0
27 | * (the "License"); you may not use this file except in compliance with
28 | * the License. You may obtain a copy of the License at
29 | *
30 | * http://www.apache.org/licenses/LICENSE-2.0
31 | *
32 | * Unless required by applicable law or agreed to in writing, software
33 | * distributed under the License is distributed on an "AS IS" BASIS,
34 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 | * See the License for the specific language governing permissions and
36 | * limitations under the License.
37 | */
38 | package org.zeroturnaround.exec.stream;
39 |
40 | import java.io.IOException;
41 | import java.io.InputStream;
42 | import java.io.OutputStream;
43 |
44 | import org.slf4j.Logger;
45 | import org.slf4j.LoggerFactory;
46 |
47 | /**
48 | * Copies all data from an input stream to an output stream.
49 | */
50 | public class StreamPumper implements Runnable {
51 |
52 | private static final Logger log = LoggerFactory.getLogger(StreamPumper.class);
53 |
54 | /** the default size of the internal buffer for copying the streams */
55 | private static final int DEFAULT_SIZE = 1024;
56 |
57 | /** the input stream to pump from */
58 | private final InputStream is;
59 |
60 | /** the output stream to pmp into */
61 | private final OutputStream os;
62 |
63 | /** the size of the internal buffer for copying the streams */
64 | private final int size;
65 |
66 | /** was the end of the stream reached */
67 | private boolean finished;
68 |
69 | /** close the output stream when exhausted */
70 | private final boolean closeWhenExhausted;
71 |
72 | /** flush the output stream after each write */
73 | private final boolean flushImmediately;
74 |
75 | /**
76 | * Create a new stream pumper.
77 | *
78 | * @param is input stream to read data from
79 | * @param os output stream to write data to.
80 | * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
81 | * @param flushImmediately flush the output stream whenever data was written to it
82 | */
83 | public StreamPumper(final InputStream is, final OutputStream os,
84 | final boolean closeWhenExhausted, boolean flushImmediately) {
85 | this.is = is;
86 | this.os = os;
87 | this.size = DEFAULT_SIZE;
88 | this.closeWhenExhausted = closeWhenExhausted;
89 | this.flushImmediately = flushImmediately;
90 | }
91 |
92 | /**
93 | * Create a new stream pumper.
94 | *
95 | * @param is input stream to read data from
96 | * @param os output stream to write data to.
97 | * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
98 | * @param size the size of the internal buffer for copying the streams
99 | * @param flushImmediately flush the output stream whenever data was written to it
100 | */
101 | public StreamPumper(final InputStream is, final OutputStream os,
102 | final boolean closeWhenExhausted, final int size, boolean flushImmediately) {
103 | this.is = is;
104 | this.os = os;
105 | this.size = (size > 0 ? size : DEFAULT_SIZE);
106 | this.closeWhenExhausted = closeWhenExhausted;
107 | this.flushImmediately = flushImmediately;
108 | }
109 |
110 | /**
111 | * Create a new stream pumper.
112 | *
113 | * @param is input stream to read data from
114 | * @param os output stream to write data to.
115 | * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
116 | */
117 | public StreamPumper(final InputStream is, final OutputStream os,
118 | final boolean closeWhenExhausted) {
119 | this.is = is;
120 | this.os = os;
121 | this.size = DEFAULT_SIZE;
122 | this.closeWhenExhausted = closeWhenExhausted;
123 | this.flushImmediately = false;
124 | }
125 |
126 | /**
127 | * Create a new stream pumper.
128 | *
129 | * @param is input stream to read data from
130 | * @param os output stream to write data to.
131 | * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
132 | * @param size the size of the internal buffer for copying the streams
133 | */
134 | public StreamPumper(final InputStream is, final OutputStream os,
135 | final boolean closeWhenExhausted, final int size) {
136 | this.is = is;
137 | this.os = os;
138 | this.size = (size > 0 ? size : DEFAULT_SIZE);
139 | this.closeWhenExhausted = closeWhenExhausted;
140 | this.flushImmediately = false;
141 | }
142 |
143 | /**
144 | * Create a new stream pumper.
145 | *
146 | * @param is input stream to read data from
147 | * @param os output stream to write data to.
148 | */
149 | public StreamPumper(final InputStream is, final OutputStream os) {
150 | this(is, os, false);
151 | }
152 |
153 | /**
154 | * Copies data from the input stream to the output stream. Terminates as
155 | * soon as the input stream is closed or an error occurs.
156 | */
157 | public void run() {
158 | log.trace("{} started.", this);
159 | synchronized (this) {
160 | // Just in case this object is reused in the future
161 | finished = false;
162 | }
163 |
164 | final byte[] buf = new byte[this.size];
165 |
166 | int length;
167 | try {
168 | while ((length = is.read(buf)) > 0) {
169 | os.write(buf, 0, length);
170 | if(flushImmediately) {
171 | os.flush();
172 | }
173 | }
174 | } catch (Exception e) {
175 | // nothing to do - happens quite often with watchdog
176 | } finally {
177 | log.trace("{} finished.", this);
178 | if (closeWhenExhausted) {
179 | try {
180 | os.close();
181 | } catch (IOException e) {
182 | log.error("Got exception while closing exhausted output stream", e);
183 | }
184 | }
185 | synchronized (this) {
186 | finished = true;
187 | notifyAll();
188 | }
189 | }
190 | }
191 |
192 | /**
193 | * Tells whether the end of the stream has been reached.
194 | *
195 | * @return true is the stream has been exhausted.
196 | */
197 | public synchronized boolean isFinished() {
198 | return finished;
199 | }
200 |
201 | /**
202 | * This method blocks until the stream pumper finishes.
203 | *
204 | * @see #isFinished()
205 | * @throws InterruptedException throws when the waiting is interrupted
206 | */
207 | public synchronized void waitFor() throws InterruptedException {
208 | while (!isFinished()) {
209 | wait();
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ZT Process Executor
2 | ================
3 |
4 | ### Continuous Integration
5 | 
6 |
7 | ### Quick Overview
8 |
9 | The project was created to merge similar functionality of projects at [ZeroTurnaround](http://zeroturnaround.com/) into a single codebase.
10 | It's designed to be powerful but still simple to use. By using a single class **ProcessExecutor**
11 | the user gets the functionality from both **java.lang.ProcessBuilder** and [Apache Commons Exec](http://commons.apache.org/proper/commons-exec/).
12 |
13 | ### Dependencies
14 |
15 | * Minimal required Java version is 6 (tested also with 8, 11 and 17).
16 | * The only 3rd-party dependency is [SLF4J](https://www.slf4j.org/).
17 |
18 | ### Installation
19 | [](https://maven-badges.herokuapp.com/maven-central/org.zeroturnaround/zt-exec)
20 |
21 | The project artifacts are available in [Maven Central Repository](https://search.maven.org/artifact/org.zeroturnaround/zt-exec/).
22 |
23 | To include it in your maven project then you have to specify the dependency.
24 |
25 | ```xml
26 | ...
27 |
28 | org.zeroturnaround
29 | zt-exec
30 | 1.12
31 |
32 | ...
33 | ```
34 |
35 | ## Motivation
36 |
37 | There are many approaches to take when running external processes from Java. There are the **JRE** options such as the **Runtime.exec()** and **ProcessBuilder**. Also there is the [Apache Commons Exec](http://commons.apache.org/proper/commons-exec/). Nevertheless we created yet another process library (**YAPL**).
38 |
39 | Some of the reasons for this crazy endeavour
40 |
41 | * Improved handling of streams
42 | * Reading/writing to streams
43 | * Redirecting stderr to stdout
44 | * Improved handling of timeouts
45 | * Improved checking of exit codes
46 | * Improved API
47 | * One liners for quite complex usecases
48 | * One liners to get process output into a String
49 | * Access to the **Process** object available
50 | * Support for async processes ( **Future** )
51 | * Improved logging with [SLF4J API](http://www.slf4j.org/)
52 | * Support for multiple processes
53 |
54 | **Can zt-exec also kill processes?**
55 |
56 | No. There is [zt-process-killer](https://github.com/zeroturnaround/zt-process-killer) for that.
57 |
58 | ## Examples
59 |
60 | * Output is pumped to NullOutputStream
61 |
62 | ```java
63 | new ProcessExecutor().command("java", "-version").execute();
64 | ```
65 |
66 |
67 |
68 | * Returning the exit code
69 | * Output is pumped to NullOutputStream
70 |
71 | ```java
72 | int exit = new ProcessExecutor().command("java", "-version")
73 | .execute().getExitValue();
74 | ```
75 |
76 |
77 |
78 | * Return output as UTF8 String
79 |
80 | ```java
81 | String output = new ProcessExecutor().command("java", "-version")
82 | .readOutput(true).execute()
83 | .outputUTF8();
84 | ```
85 |
86 |
87 |
88 | * Pumping the output to a logger
89 |
90 | ```java
91 | new ProcessExecutor().command("java", "-version")
92 | .redirectOutput(Slf4jStream.of(LoggerFactory.getLogger(getClass().getName() + ".MyProcess")).asInfo()).execute();
93 | ```
94 |
95 |
96 |
97 | * Pumping the output to a logger (short form for previous)
98 |
99 | ```java
100 | new ProcessExecutor().command("java", "-version")
101 | .redirectOutput(Slf4jStream.of("MyProcess").asInfo()).execute();
102 | ```
103 |
104 |
105 |
106 | * Pumping the output to the logger of the caller class
107 |
108 | ```java
109 | new ProcessExecutor().command("java", "-version")
110 | .redirectOutput(Slf4jStream.ofCaller().asInfo()).execute();
111 | ```
112 |
113 |
114 |
115 | * Pumping the output to a logger
116 | * Returning output as UTF8 String
117 |
118 | ```java
119 | String output = new ProcessExecutor().command("java", "-version")
120 | .redirectOutput(Slf4jStream.of(getClass()).asInfo())
121 | .readOutput(true).execute().outputUTF8();
122 | ```
123 |
124 |
125 |
126 | * Pumping the stderr to a logger
127 | * Returning the output as UTF8 String
128 |
129 | ```java
130 | String output = new ProcessExecutor().command("java", "-version")
131 | .redirectError(Slf4jStream.of(getClass()).asInfo())
132 | .readOutput(true).execute()
133 | .outputUTF8();
134 | ```
135 |
136 |
137 |
138 | * Running with a timeout of **60** seconds
139 | * Output pumped to NullOutputStream
140 |
141 | ```java
142 | try {
143 | new ProcessExecutor().command("java", "-version")
144 | .timeout(60, TimeUnit.SECONDS).execute();
145 | }
146 | catch (TimeoutException e) {
147 | // process is automatically destroyed
148 | }
149 | ```
150 |
151 |
152 |
153 | * Pumping output to another OutputStream
154 |
155 | ```java
156 | OutputStream out = ...;
157 | new ProcessExecutor().command("java", "-version")
158 | .redirectOutput(out).execute();
159 | ```
160 |
161 |
162 |
163 | * Handling output line-by-line while process is running (Java 8+)
164 |
165 | ```java
166 | new ProcessExecutor().command("java", "-version")
167 | .redirectOutput(line -> ...)
168 | .execute();
169 | ```
170 |
171 |
172 |
173 | * Handling output line-by-line while process is running (prior to Java 8)
174 |
175 | ```java
176 | new ProcessExecutor().command("java", "-version")
177 | .redirectOutput(new LogOutputStream() {
178 | @Override
179 | protected void processLine(String line) {
180 | ...
181 | }
182 | })
183 | .execute();
184 | ```
185 |
186 |
187 |
188 | * Destroy the running process when VM exits
189 | * Output pumped to NullOutputStream
190 |
191 | ```java
192 | new ProcessExecutor().command("java", "-version").destroyOnExit().execute();
193 | ```
194 |
195 |
196 |
197 | * Run process with a specific environment variable
198 | * Output pumped to NullOutputStream
199 |
200 | ```java
201 | new ProcessExecutor().command("java", "-version")
202 | .environment("foo", "bar").execute();
203 | ```
204 |
205 |
206 |
207 | * Run process with a specific environment
208 | * Output pumped to NullOutputStream
209 |
210 | ```java
211 | Map env = ...
212 | new ProcessExecutor().command("java", "-version")
213 | .environment(env).execute();
214 | ```
215 |
216 |
217 |
218 | * Throw exception when wrong exit code
219 | * Output is pumped to NullOutputStream
220 |
221 | ```java
222 | try {
223 | new ProcessExecutor().command("java", "-version")
224 | .exitValues(3).execute();
225 | }
226 | catch (InvalidExitValueException e) {
227 | System.out.println("Process exited with " + e.getExitValue());
228 | }
229 | ```
230 |
231 |
232 |
233 | * Throw exception when wrong exit code
234 | * Return output as UTF8 String
235 |
236 | ```java
237 | String output;
238 | boolean success = false;
239 | try {
240 | output = new ProcessExecutor().command("java", "-version")
241 | .readOutput(true).exitValues(3)
242 | .execute().outputUTF8();
243 | success = true;
244 | }
245 | catch (InvalidExitValueException e) {
246 | System.out.println("Process exited with " + e.getExitValue());
247 | output = e.getResult().outputUTF8();
248 | }
249 | ```
250 |
251 |
252 |
253 | * Starting process in the background
254 | * Output is pumped to NullOutputStream
255 |
256 | ```java
257 | Future future = new ProcessExecutor()
258 | .command("java", "-version")
259 | .start().getFuture();
260 | // do some stuff
261 | future.get(60, TimeUnit.SECONDS);
262 | ```
263 |
264 |
265 |
266 | * Start process in the background
267 | * Return output as UTF8 String
268 |
269 | ```java
270 | Future future = new ProcessExecutor()
271 | .command("java", "-version")
272 | .readOutput(true)
273 | .start().getFuture();
274 | // do some stuff
275 | String output = future.get(60, TimeUnit.SECONDS).outputUTF8();
276 | ```
277 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
30 | @REM e.g. to debug Maven itself, use
31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
33 | @REM ----------------------------------------------------------------------------
34 |
35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
36 | @echo off
37 | @REM set title of command window
38 | title %0
39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
41 |
42 | @REM set %HOME% to equivalent of $HOME
43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
44 |
45 | @REM Execute a user defined script before this one
46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
50 | :skipRcPre
51 |
52 | @setlocal
53 |
54 | set ERROR_CODE=0
55 |
56 | @REM To isolate internal variables from possible post scripts, we use another setlocal
57 | @setlocal
58 |
59 | @REM ==== START VALIDATION ====
60 | if not "%JAVA_HOME%" == "" goto OkJHome
61 |
62 | echo.
63 | echo Error: JAVA_HOME not found in your environment. >&2
64 | echo Please set the JAVA_HOME variable in your environment to match the >&2
65 | echo location of your Java installation. >&2
66 | echo.
67 | goto error
68 |
69 | :OkJHome
70 | if exist "%JAVA_HOME%\bin\java.exe" goto init
71 |
72 | echo.
73 | echo Error: JAVA_HOME is set to an invalid directory. >&2
74 | echo JAVA_HOME = "%JAVA_HOME%" >&2
75 | echo Please set the JAVA_HOME variable in your environment to match the >&2
76 | echo location of your Java installation. >&2
77 | echo.
78 | goto error
79 |
80 | @REM ==== END VALIDATION ====
81 |
82 | :init
83 |
84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
85 | @REM Fallback to current working directory if not found.
86 |
87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
89 |
90 | set EXEC_DIR=%CD%
91 | set WDIR=%EXEC_DIR%
92 | :findBaseDir
93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
94 | cd ..
95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
96 | set WDIR=%CD%
97 | goto findBaseDir
98 |
99 | :baseDirFound
100 | set MAVEN_PROJECTBASEDIR=%WDIR%
101 | cd "%EXEC_DIR%"
102 | goto endDetectBaseDir
103 |
104 | :baseDirNotFound
105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
106 | cd "%EXEC_DIR%"
107 |
108 | :endDetectBaseDir
109 |
110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
111 |
112 | @setlocal EnableExtensions EnableDelayedExpansion
113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
115 |
116 | :endReadAdditionalConfig
117 |
118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
123 |
124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
126 | )
127 |
128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
130 | if exist %WRAPPER_JAR% (
131 | if "%MVNW_VERBOSE%" == "true" (
132 | echo Found %WRAPPER_JAR%
133 | )
134 | ) else (
135 | if not "%MVNW_REPOURL%" == "" (
136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
137 | )
138 | if "%MVNW_VERBOSE%" == "true" (
139 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
140 | echo Downloading from: %WRAPPER_URL%
141 | )
142 |
143 | powershell -Command "&{"^
144 | "$webclient = new-object System.Net.WebClient;"^
145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
147 | "}"^
148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
149 | "}"
150 | if "%MVNW_VERBOSE%" == "true" (
151 | echo Finished downloading %WRAPPER_JAR%
152 | )
153 | )
154 | @REM End of extension
155 |
156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
157 | SET WRAPPER_SHA_256_SUM=""
158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
160 | )
161 | IF NOT %WRAPPER_SHA_256_SUM%=="" (
162 | powershell -Command "&{"^
163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
168 | " exit 1;"^
169 | "}"^
170 | "}"
171 | if ERRORLEVEL 1 goto error
172 | )
173 |
174 | @REM Provide a "standardized" way to retrieve the CLI args that will
175 | @REM work with both Windows and non-Windows executions.
176 | set MAVEN_CMD_LINE_ARGS=%*
177 |
178 | %MAVEN_JAVA_EXE% ^
179 | %JVM_CONFIG_MAVEN_PROPS% ^
180 | %MAVEN_OPTS% ^
181 | %MAVEN_DEBUG_OPTS% ^
182 | -classpath %WRAPPER_JAR% ^
183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
185 | if ERRORLEVEL 1 goto error
186 | goto end
187 |
188 | :error
189 | set ERROR_CODE=1
190 |
191 | :end
192 | @endlocal & set ERROR_CODE=%ERROR_CODE%
193 |
194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
198 | :skipRcPost
199 |
200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause
202 |
203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
204 |
205 | cmd /C exit /B %ERROR_CODE%
206 |
--------------------------------------------------------------------------------
/src/main/java/org/zeroturnaround/exec/listener/ShutdownHookProcessDestroyer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 ZeroTurnaround
3 | * Contains fragments of code from Apache Commons Exec, rights owned
4 | * by Apache Software Foundation (ASF).
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | * NOTICE: This file originates from the Apache Commons Exec package.
19 | * It has been modified to fit our needs.
20 | *
21 | * The following is the original header of the file in Apache Commons Exec:
22 | *
23 | * Licensed to the Apache Software Foundation (ASF) under one or more
24 | * contributor license agreements. See the NOTICE file distributed with
25 | * this work for additional information regarding copyright ownership.
26 | * The ASF licenses this file to You under the Apache License, Version 2.0
27 | * (the "License"); you may not use this file except in compliance with
28 | * the License. You may obtain a copy of the License at
29 | *
30 | * http://www.apache.org/licenses/LICENSE-2.0
31 | *
32 | * Unless required by applicable law or agreed to in writing, software
33 | * distributed under the License is distributed on an "AS IS" BASIS,
34 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 | * See the License for the specific language governing permissions and
36 | * limitations under the License.
37 | */
38 | package org.zeroturnaround.exec.listener;
39 |
40 | import java.util.Enumeration;
41 | import java.util.Vector;
42 |
43 | import org.slf4j.Logger;
44 | import org.slf4j.LoggerFactory;
45 |
46 | /**
47 | * Destroys all registered Processes when the VM exits.
48 | *
49 | * This class is copied from Commons Exec.
50 | */
51 | public class ShutdownHookProcessDestroyer implements ProcessDestroyer, Runnable {
52 |
53 | private static final Logger log = LoggerFactory.getLogger(ShutdownHookProcessDestroyer.class);
54 |
55 | /**
56 | * Singleton instance of the {@link ShutdownHookProcessDestroyer}.
57 | */
58 | public static final ProcessDestroyer INSTANCE = new ShutdownHookProcessDestroyer();
59 |
60 | /** the list of currently running processes */
61 | private final Vector processes = new Vector();
62 |
63 | /** The thread registered at the JVM to execute the shutdown handler */
64 | private ProcessDestroyerImpl destroyProcessThread = null;
65 |
66 | /** Whether or not this ProcessDestroyer has been registered as a shutdown hook */
67 | private boolean added = false;
68 |
69 | /** Whether the shut down hook routine was already run **/
70 | private volatile boolean shutDownHookExecuted = false;
71 |
72 | /**
73 | * Whether or not this ProcessDestroyer is currently running as shutdown hook
74 | */
75 | private volatile boolean running = false;
76 |
77 | private class ProcessDestroyerImpl extends Thread {
78 |
79 | private boolean shouldDestroy = true;
80 |
81 | public ProcessDestroyerImpl() {
82 | super("ProcessDestroyer Shutdown Hook");
83 | }
84 |
85 | public void run() {
86 | if (shouldDestroy) {
87 | ShutdownHookProcessDestroyer.this.run();
88 | }
89 | }
90 |
91 | public void setShouldDestroy(final boolean shouldDestroy) {
92 | this.shouldDestroy = shouldDestroy;
93 | }
94 | }
95 |
96 | /**
97 | * Constructs a ProcessDestroyer and obtains
98 | * Runtime.addShutdownHook() and
99 | * Runtime.removeShutdownHook() through reflection. The
100 | * ProcessDestroyer manages a list of processes to be destroyed when the VM
101 | * exits. If a process is added when the list is empty, this
102 | * ProcessDestroyer is registered as a shutdown hook. If
103 | * removing a process results in an empty list, the
104 | * ProcessDestroyer is removed as a shutdown hook.
105 | */
106 | public ShutdownHookProcessDestroyer() {
107 | }
108 |
109 | /**
110 | * Registers this ProcessDestroyer as a shutdown hook, uses
111 | * reflection to ensure pre-JDK 1.3 compatibility.
112 | */
113 | private void addShutdownHook() {
114 | if (!running) {
115 | destroyProcessThread = new ProcessDestroyerImpl();
116 | Runtime.getRuntime().addShutdownHook(destroyProcessThread);
117 | added = true;
118 | }
119 | }
120 |
121 | /**
122 | * Removes this ProcessDestroyer as a shutdown hook, uses
123 | * reflection to ensure pre-JDK 1.3 compatibility
124 | */
125 | private void removeShutdownHook() {
126 | if (added && !running) {
127 | boolean removed = Runtime.getRuntime().removeShutdownHook(
128 | destroyProcessThread);
129 | if (!removed) {
130 | log.error("Could not remove shutdown hook");
131 | }
132 | /*
133 | * start the hook thread, a unstarted thread may not be eligible for
134 | * garbage collection Cf.: http://developer.java.sun.com/developer/
135 | * bugParade/bugs/4533087.html
136 | */
137 |
138 | destroyProcessThread.setShouldDestroy(false);
139 | destroyProcessThread.start();
140 | // this should return quickly, since it basically is a NO-OP.
141 | try {
142 | destroyProcessThread.join(20000);
143 | }
144 | catch (InterruptedException ie) {
145 | // the thread didn't die in time
146 | // it should not kill any processes unexpectedly
147 | }
148 | destroyProcessThread = null;
149 | added = false;
150 | }
151 | }
152 |
153 | /**
154 | * Returns whether or not the ProcessDestroyer is registered as as shutdown
155 | * hook
156 | *
157 | * @return true if this is currently added as shutdown hook
158 | */
159 | public boolean isAddedAsShutdownHook() {
160 | return added;
161 | }
162 |
163 | /**
164 | * Returns true if the specified Process was
165 | * successfully added to the list of processes to destroy upon VM exit.
166 | *
167 | * @param process
168 | * the process to add
169 | * @return true if the specified Process was
170 | * successfully added
171 | */
172 | public boolean add(final Process process) {
173 | synchronized (processes) {
174 | // if this list is empty, register the shutdown hook
175 | if (processes.size() == 0) {
176 | try {
177 | if(shutDownHookExecuted) {
178 | throw new IllegalStateException();
179 | }
180 | addShutdownHook();
181 | }
182 | // kill the process now if the JVM is currently shutting down
183 | catch (IllegalStateException e) {
184 | destroy(process);
185 | }
186 | }
187 | processes.addElement(process);
188 | return processes.contains(process);
189 | }
190 | }
191 |
192 | /**
193 | * Returns true if the specified Process was
194 | * successfully removed from the list of processes to destroy upon VM exit.
195 | *
196 | * @param process
197 | * the process to remove
198 | * @return true if the specified Process was
199 | * successfully removed
200 | */
201 | public boolean remove(final Process process) {
202 | synchronized (processes) {
203 | boolean processRemoved = processes.removeElement(process);
204 | if (processRemoved && processes.size() == 0) {
205 | try {
206 | removeShutdownHook();
207 | } catch (IllegalStateException e) {
208 | /* if the JVM is shutting down, the hook cannot be removed */
209 | shutDownHookExecuted = true;
210 | }
211 | }
212 | return processRemoved;
213 | }
214 | }
215 |
216 | /**
217 | * Returns the number of registered processes.
218 | *
219 | * @return the number of register process
220 | */
221 | public int size() {
222 | return processes.size();
223 | }
224 |
225 | /**
226 | * Invoked by the VM when it is exiting.
227 | */
228 | public void run() {
229 | /* check if running the routine is still necessary */
230 | if(shutDownHookExecuted) {
231 | return;
232 | }
233 | synchronized (processes) {
234 | running = true;
235 | Enumeration e = processes.elements();
236 | while (e.hasMoreElements()) {
237 | destroy((Process) e.nextElement());
238 | }
239 | processes.clear();
240 | shutDownHookExecuted = true;
241 | }
242 | }
243 |
244 | private void destroy(Process process) {
245 | try {
246 | process.destroy();
247 | }
248 | catch (Throwable t) {
249 | log.error("Unable to terminate process during process shutdown");
250 | }
251 | }
252 | }
253 |
--------------------------------------------------------------------------------