CAMEL-8241: Work around bug in commons-exec to get camel-exec to work on unix with java 8.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/bbbb3942 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/bbbb3942 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/bbbb3942 Branch: refs/heads/camel-2.14.x Commit: bbbb3942e6ff42b5c0018e0770f3f55e207b527c Parents: 0231cdc Author: Claus Ibsen <davscl...@apache.org> Authored: Sun Feb 22 11:51:21 2015 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Sun Feb 22 11:55:24 2015 +0100 ---------------------------------------------------------------------- .../component/exec/ExecDefaultExecutor.java | 45 +++++++ .../camel/component/exec/ExecEndpoint.java | 1 - .../camel/component/exec/ExecProducer.java | 9 +- .../exec/impl/DefaultExecCommandExecutor.java | 29 ++++- .../camel/component/exec/ExecEndpointTest.java | 3 - .../component/exec/ExecJava8IssueTest.java | 119 +++++++++++++++++++ 6 files changed, 200 insertions(+), 6 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/bbbb3942/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecDefaultExecutor.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecDefaultExecutor.java b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecDefaultExecutor.java new file mode 100644 index 0000000..14df04e --- /dev/null +++ b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecDefaultExecutor.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.exec; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; + +public class ExecDefaultExecutor extends DefaultExecutor { + + private transient Process process; + + public ExecDefaultExecutor() { + } + + @Override + protected Process launch(CommandLine command, Map<String, String> env, File dir) throws IOException { + process = super.launch(command, env, dir); + return process; + } + + public int getExitValue() { + if (process != null && !process.isAlive()) { + return process.exitValue(); + } + return 0; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/bbbb3942/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java index 8851328..202ac70 100644 --- a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java +++ b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecEndpoint.java @@ -61,7 +61,6 @@ public class ExecEndpoint extends DefaultEndpoint { super(uri, component); this.timeout = NO_TIMEOUT; this.binding = new DefaultExecBinding(); - this.commandExecutor = new DefaultExecCommandExecutor(); } public Producer createProducer() throws Exception { http://git-wip-us.apache.org/repos/asf/camel/blob/bbbb3942/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecProducer.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecProducer.java b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecProducer.java index aa125d4..49e1457 100644 --- a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecProducer.java +++ b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecProducer.java @@ -17,6 +17,7 @@ package org.apache.camel.component.exec; import org.apache.camel.Exchange; +import org.apache.camel.component.exec.impl.DefaultExecCommandExecutor; import org.apache.camel.impl.DefaultProducer; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; @@ -42,8 +43,14 @@ public class ExecProducer extends DefaultProducer { public void process(Exchange exchange) throws Exception { ExecCommand execCommand = getBinding().readInput(exchange, endpoint); + ExecCommandExecutor executor = endpoint.getCommandExecutor(); + if (executor == null) { + executor = new DefaultExecCommandExecutor(exchange); + } + log.info("Executing {}", execCommand); - ExecResult result = endpoint.getCommandExecutor().execute(execCommand); + ExecResult result = executor.execute(execCommand); + ObjectHelper.notNull(result, "The command executor must return a not-null result"); log.info("The command {} had exit value {}", execCommand, result.getExitValue()); if (result.getExitValue() != 0) { http://git-wip-us.apache.org/repos/asf/camel/blob/bbbb3942/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecCommandExecutor.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecCommandExecutor.java b/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecCommandExecutor.java index 1de2782..33cf6a2 100644 --- a/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecCommandExecutor.java +++ b/components/camel-exec/src/main/java/org/apache/camel/component/exec/impl/DefaultExecCommandExecutor.java @@ -23,8 +23,10 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import org.apache.camel.Exchange; import org.apache.camel.component.exec.ExecCommand; import org.apache.camel.component.exec.ExecCommandExecutor; +import org.apache.camel.component.exec.ExecDefaultExecutor; import org.apache.camel.component.exec.ExecEndpoint; import org.apache.camel.component.exec.ExecException; import org.apache.camel.component.exec.ExecResult; @@ -49,6 +51,12 @@ public class DefaultExecCommandExecutor implements ExecCommandExecutor { private static final Logger LOG = LoggerFactory.getLogger(DefaultExecCommandExecutor.class); + private final Exchange exchange; + + public DefaultExecCommandExecutor(Exchange exchange) { + this.exchange = exchange; + } + public ExecResult execute(ExecCommand command) { notNull(command, "command"); @@ -76,6 +84,25 @@ public class DefaultExecCommandExecutor implements ExecCommandExecutor { LOG.error("ExecException while executing command: " + command.toString() + " - " + ee.getMessage()); throw new ExecException("Failed to execute command " + command, ee); } catch (IOException ioe) { + // workaround to ignore if the stream was already closes due some race condition in commons-exec + String msg = ioe.getMessage(); + if ("Stream closed".equals(msg)) { + LOG.debug("Ignoring Stream closed IOException", ioe); + // if the size is zero, we have no output, so construct the result + // with null (required by ExecResult) + InputStream stdout = out.size() == 0 ? null : new ByteArrayInputStream(out.toByteArray()); + InputStream stderr = err.size() == 0 ? null : new ByteArrayInputStream(err.toByteArray()); + + // use 0 as exit value as the executor didn't return the value + int exitValue = 0; + if (executor instanceof ExecDefaultExecutor) { + // get the exit value from the executor as it captures this to work around the common-exec bug + exitValue = ((ExecDefaultExecutor) executor).getExitValue(); + } + + ExecResult result = new ExecResult(command, stdout, stderr, exitValue); + return result; + } // invalid working dir LOG.error("IOException while executing command: " + command.toString() + " - " + ioe.getMessage()); throw new ExecException("Unable to execute command " + command, ioe); @@ -86,7 +113,7 @@ public class DefaultExecCommandExecutor implements ExecCommandExecutor { } protected DefaultExecutor prepareDefaultExecutor(ExecCommand execCommand) { - DefaultExecutor executor = new DefaultExecutor(); + DefaultExecutor executor = new ExecDefaultExecutor(); executor.setExitValues(null); if (execCommand.getWorkingDir() != null) { http://git-wip-us.apache.org/repos/asf/camel/blob/bbbb3942/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java index 26ec23f..ebd4cfc 100644 --- a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java +++ b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecEndpointTest.java @@ -74,7 +74,6 @@ public class ExecEndpointTest extends AbstractJUnit4SpringContextTests { assertEquals(NO_TIMEOUT, e.getTimeout()); assertEquals("test", e.getExecutable()); - assertNotNull(e.getCommandExecutor()); assertNotNull(e.getBinding()); } @@ -150,7 +149,6 @@ public class ExecEndpointTest extends AbstractJUnit4SpringContextTests { ExecEndpoint endpoint = createExecEndpoint(UnsafeUriCharactersEncoder.encode(uri)); assertEquals(cmd, endpoint.getExecutable()); assertNull(endpoint.getArgs()); - assertNotNull(endpoint.getCommandExecutor()); assertEquals(dir, endpoint.getWorkingDir()); } @@ -165,7 +163,6 @@ public class ExecEndpointTest extends AbstractJUnit4SpringContextTests { assertNull(endpoint.getArgs()); assertNull(endpoint.getWorkingDir()); - assertNotNull(endpoint.getCommandExecutor()); assertEquals(executable, endpoint.getExecutable()); } http://git-wip-us.apache.org/repos/asf/camel/blob/bbbb3942/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJava8IssueTest.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJava8IssueTest.java b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJava8IssueTest.java new file mode 100644 index 0000000..5fa78d4 --- /dev/null +++ b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJava8IssueTest.java @@ -0,0 +1,119 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.exec; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.UUID; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.commons.exec.OS; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Test to duplicate issues with Camel's exec command in Java 8 on Unix + * This issue appears to be caused by a race condition, so this test does not always fail + */ +public class ExecJava8IssueTest extends Assert { + + private File tempDir; + private final String tempDirName = name(); + private final String tempFileName = name(); + + @Before + public void setUp() { + tempDir = new File("target", tempDirName); + if (!(tempDir.mkdir())) { + fail("Couldn't create temp dir for test"); + } + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteDirectory(tempDir); + } + + @Test + public void test() throws Exception { + + if (!OS.isFamilyUnix()) { + System.err.println("The test 'CamelExecTest' does not support the following OS : " + System.getProperty("os.name")); + return; + } + + String tempFilePath = tempDir.getAbsolutePath() + "/" + tempFileName; + + final File script = File.createTempFile("script", ".sh", tempDir); + + writeScript(script); + + final String exec = "bash?args=" + script.getAbsolutePath() + " " + tempFilePath + "&outFile=" + tempFilePath; + + DefaultCamelContext context = new DefaultCamelContext(); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:source") + .to("file:" + tempDir.getAbsolutePath() + "?fileName=" + tempFileName) + .to("exec:" + exec) + .process(new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + String output = exchange.getIn().getBody(String.class); + assertEquals("hello world\n", output); + } + }); + + } + }); + + context.start(); + + ProducerTemplate pt = context.createProducerTemplate(); + String payload = "hello"; + + pt.sendBody("direct:source", payload); + } + + /** + * Creates a script which will append " world" to a file + */ + private void writeScript(File script) throws IOException { + try (FileWriter fw = new FileWriter(script); + PrintWriter pw = new PrintWriter(fw)) { + String s = "echo \" world\" >> $1"; + pw.print(s); + } + } + + /** + * Returns a random UUID + */ + private String name() { + return UUID.randomUUID().toString(); + } +} \ No newline at end of file