Repository: camel Updated Branches: refs/heads/master b6c2e1dd2 -> 678252004
CAMEL-9225: camel-exec - Enrich exception with stdout/stderr. Thanks to Timo Bruckner for the patch. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/534b4a8e Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/534b4a8e Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/534b4a8e Branch: refs/heads/master Commit: 534b4a8ef3b89b35bd266c7c779f7fbd4b3ce03a Parents: b6c2e1d Author: Claus Ibsen <davscl...@apache.org> Authored: Tue Oct 27 09:35:17 2015 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Tue Oct 27 09:35:17 2015 +0100 ---------------------------------------------------------------------- .../camel/component/exec/ExecException.java | 30 +++++-- .../exec/impl/DefaultExecCommandExecutor.java | 53 ++++++----- .../component/exec/ExecJavaProcessTest.java | 92 +++++++++++++++++--- .../ProvokeExceptionExecCommandExecutor.java | 46 ++++++++++ 4 files changed, 176 insertions(+), 45 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/534b4a8e/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecException.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecException.java b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecException.java index d32a38b..afc762a 100644 --- a/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecException.java +++ b/components/camel-exec/src/main/java/org/apache/camel/component/exec/ExecException.java @@ -16,27 +16,43 @@ */ package org.apache.camel.component.exec; +import java.io.InputStream; import org.apache.camel.RuntimeCamelException; /** * Exception thrown when there is an execution failure. */ public class ExecException extends RuntimeCamelException { + + private final int exitValue; + private final InputStream stdout; + private final InputStream stderr; private static final long serialVersionUID = 7808703605527644487L; - public ExecException() { - } - - public ExecException(String message) { + public ExecException(String message, final InputStream stdout, final InputStream stderr, final int exitValue) { super(message); + this.exitValue = exitValue; + this.stderr = stderr; + this.stdout = stdout; } - public ExecException(String message, Throwable cause) { + public ExecException(String message, final InputStream stdout, final InputStream stderr, final int exitValue, Throwable cause) { super(message, cause); + this.exitValue = exitValue; + this.stderr = stderr; + this.stdout = stdout; + } + + public int getExitValue() { + return exitValue; + } + + public InputStream getStdout() { + return stdout; } - public ExecException(Throwable cause) { - super(cause); + public InputStream getStderr() { + return stderr; } } http://git-wip-us.apache.org/repos/asf/camel/blob/534b4a8e/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 b2ed60e..3cbce5b 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 @@ -1,18 +1,18 @@ /** * 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 + * 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 + * 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. + * 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.impl; @@ -51,6 +51,7 @@ public class DefaultExecCommandExecutor implements ExecCommandExecutor { private static final Logger LOG = LoggerFactory.getLogger(DefaultExecCommandExecutor.class); + @Override public ExecResult execute(ExecCommand command) { notNull(command, "command"); @@ -76,30 +77,34 @@ public class DefaultExecCommandExecutor implements ExecCommandExecutor { } catch (ExecuteException ee) { LOG.error("ExecException while executing command: " + command.toString() + " - " + ee.getMessage()); - throw new ExecException("Failed to execute command " + command, ee); + + InputStream stdout = out.size() == 0 ? null : new ByteArrayInputStream(out.toByteArray()); + InputStream stderr = err.size() == 0 ? null : new ByteArrayInputStream(err.toByteArray()); + + throw new ExecException("Failed to execute command " + command, stdout, stderr, ee.getExitValue(), ee); + } catch (IOException ioe) { + + InputStream stdout = out.size() == 0 ? null : new ByteArrayInputStream(out.toByteArray()); + InputStream stderr = err.size() == 0 ? null : new ByteArrayInputStream(err.toByteArray()); + + int exitValue = 0; // use 0 as exit value as the executor didn't return the value + 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(); + } + // workaround to ignore if the stream was already closes due some race condition in commons-exec String msg = ioe.getMessage(); if (msg != null && "stream closed".equals(msg.toLowerCase(Locale.ENGLISH))) { 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); + throw new ExecException("Unable to execute command " + command, stdout, stderr, exitValue, ioe); } finally { // the inputStream must be closed after the execution IOUtils.closeQuietly(command.getInput()); @@ -123,7 +128,7 @@ public class DefaultExecCommandExecutor implements ExecCommandExecutor { /** * Transforms an {@link ExecCommand} to a {@link CommandLine}. No quoting fo * the arguments is used. - * + * * @param execCommand a not-null <code>ExecCommand</code> instance. * @return a {@link CommandLine} object. */ http://git-wip-us.apache.org/repos/asf/camel/blob/534b4a8e/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJavaProcessTest.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJavaProcessTest.java b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJavaProcessTest.java index d77e661..224f38a 100644 --- a/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJavaProcessTest.java +++ b/components/camel-exec/src/test/java/org/apache/camel/component/exec/ExecJavaProcessTest.java @@ -1,18 +1,18 @@ /** * 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 + * 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 + * 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. + * 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; @@ -25,6 +25,7 @@ import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.Produce; import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.AdviceWithRouteBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.converter.IOConverter; @@ -49,7 +50,11 @@ import static org.apache.camel.component.exec.ExecutableJavaProgram.PRINT_IN_STD import static org.apache.camel.component.exec.ExecutableJavaProgram.READ_INPUT_LINES_AND_PRINT_THEM; import static org.apache.camel.component.exec.ExecutableJavaProgram.SLEEP_WITH_TIMEOUT; import static org.apache.camel.component.exec.ExecutableJavaProgram.THREADS; +import org.apache.camel.component.exec.impl.ProvokeExceptionExecCommandExecutor; +import org.apache.camel.impl.JndiRegistry; import static org.apache.commons.io.IOUtils.LINE_SEPARATOR; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; /** * Tests the functionality of the {@link ExecComponent}, executing<br> @@ -58,7 +63,7 @@ import static org.apache.commons.io.IOUtils.LINE_SEPARATOR; * is set.</b> This is a more credible assumption, than assuming that java is in * the path, because the Maven scripts build the path to java with the JAVA_HOME * environment variable. - * + * * @see {@link ExecutableJavaProgram} */ public class ExecJavaProcessTest extends CamelTestSupport { @@ -70,6 +75,18 @@ public class ExecJavaProcessTest extends CamelTestSupport { @EndpointInject(uri = "mock:output") MockEndpoint output; + + /** + * Create JndiRegistry and bind custom {@link org.apache.camel.component.exec.ExecCommandExecutor} + * @return + * @throws java.lang.Exception + */ + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry registry = super.createRegistry(); + registry.bind("executorMock", new ProvokeExceptionExecCommandExecutor()); + return registry; + } @Test public void testExecJavaProcessExitCode0() throws Exception { @@ -123,7 +140,7 @@ public class ExecJavaProcessTest extends CamelTestSupport { // the second conversion should not need a reset, this is handled // in the type converter. String out2 = e.getIn().getBody(String.class); - + output.assertIsSatisfied(); assertEquals(PRINT_IN_STDOUT, out1); assertEquals(out1, out2); @@ -348,12 +365,46 @@ public class ExecJavaProcessTest extends CamelTestSupport { assertEquals(expected, IOUtils.toString(inBody.getStdout())); } + /** + * Test for thrown {@link ExecException} and access stderr and exitValue + * of thrown Exception + */ + @Test + public void testExecJavaProcessWithThrownExecException() throws Exception { + //add CustomExecutor + context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() { + @Override + public void configure() throws Exception { + weaveByToString(".*java.*").replace().to("exec:java?commandExecutor=#executorMock"); + } + }); + + output.setExpectedMessageCount(0); + Exchange out = sendFailExchange(EXIT_WITH_VALUE_0, NO_TIMEOUT); + assertEquals(ExecException.class, out.getException().getClass()); + + //test if exitValue and stderr are accessible through thrown ExecException + ExecException ee = (ExecException) out.getException(); + assertNotNull(ee.getExitValue()); + assertNotNull(ee.getStderr()); + + output.assertIsSatisfied(); + } + protected Exchange sendExchange(final Object commandArgument, final long timeout) { - return sendExchange(commandArgument, timeout, "testBody", false); + return sendExchange(commandArgument, buildArgs(commandArgument), timeout, "testBody", false); + + } + + protected Exchange sendFailExchange(final Object commandArgument, final long timeout) { + return sendExchange(commandArgument, buildFailArgs(commandArgument), timeout, "testBody", false); } protected Exchange sendExchange(final Object commandArgument, final long timeout, final String body, final boolean useStderrOnEmptyStdout) { - final List<String> args = buildArgs(commandArgument); + return sendExchange(commandArgument, buildArgs(commandArgument), timeout, body, useStderrOnEmptyStdout); + } + + protected Exchange sendExchange(final Object commandArgument, final List<String> args, final long timeout, final String body, final boolean useStderrOnEmptyStdout) { final String javaAbsolutePath = buildJavaExecutablePath(); return producerTemplate.send(new Processor() { @@ -379,6 +430,19 @@ public class ExecJavaProcessTest extends CamelTestSupport { return args; } + /** + * Build arguments for execution which will result in error + */ + List<String> buildFailArgs(Object commandArgument) { + String classpath = System.getProperty("java.class.path"); + List<String> args = new ArrayList<String>(); + args.add("-failArg"); + args.add(classpath); + args.add(EXECUTABLE_PROGRAM_ARG); + args.add(commandArgument.toString()); + return args; + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { http://git-wip-us.apache.org/repos/asf/camel/blob/534b4a8e/components/camel-exec/src/test/java/org/apache/camel/component/exec/impl/ProvokeExceptionExecCommandExecutor.java ---------------------------------------------------------------------- diff --git a/components/camel-exec/src/test/java/org/apache/camel/component/exec/impl/ProvokeExceptionExecCommandExecutor.java b/components/camel-exec/src/test/java/org/apache/camel/component/exec/impl/ProvokeExceptionExecCommandExecutor.java new file mode 100644 index 0000000..8bdeb08 --- /dev/null +++ b/components/camel-exec/src/test/java/org/apache/camel/component/exec/impl/ProvokeExceptionExecCommandExecutor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 The Apache Software Foundation. + * + * Licensed 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.impl; + +import org.apache.camel.component.exec.ExecCommand; +import org.apache.camel.component.exec.ExecDefaultExecutor; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ShutdownHookProcessDestroyer; + +/** + * Mock of {@link ExecCommandExecutor} which provokes to throw an + * {@link org.apache.camel.component.exec.ExecException} + */ +public class ProvokeExceptionExecCommandExecutor extends DefaultExecCommandExecutor { + + @Override + protected DefaultExecutor prepareDefaultExecutor(ExecCommand execCommand) { + DefaultExecutor executor = new DefaultExecutorMock(); + executor.setExitValues(null); + executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); + + return executor; + } + + public class DefaultExecutorMock extends ExecDefaultExecutor { + + @Override + public boolean isFailure(int exitValue) { + //provoke ExecuteException + return true; + } + } +} \ No newline at end of file