From f8540ad2aa480f2cc39c287d926359e0095cb272 Mon Sep 17 00:00:00 2001 From: Karsten Ohme Date: Tue, 7 Apr 2020 05:41:46 +0200 Subject: [PATCH] added async support for exec:java supporting start check with script expression --- pom.xml | 15 ++- .../org/codehaus/mojo/exec/ExecJavaMojo.java | 125 +++++++++++++----- .../codehaus/mojo/exec/ExecJavaMojoTest.java | 16 ++- .../codehaus/mojo/exec/TestServerMain.java | 88 ++++++++++++ src/test/projects/project16/pom.xml | 79 +++++++++++ 5 files changed, 290 insertions(+), 33 deletions(-) create mode 100644 src/test/java/org/codehaus/mojo/exec/TestServerMain.java create mode 100644 src/test/projects/project16/pom.xml diff --git a/pom.xml b/pom.xml index 5ffe9626d..8f0d591eb 100644 --- a/pom.xml +++ b/pom.xml @@ -184,6 +184,12 @@ 1.9.0 test + + org.codehaus.groovy + groovy-jsr223 + 2.5.10 + test + @@ -275,7 +281,7 @@ org.codehaus.groovy groovy - 1.8.4 + 2.5.10 org.codehaus.gmaven.runtime @@ -329,6 +335,13 @@ + + + org.codehaus.groovy + groovy + 2.5.10 + + org.codehaus.mojo diff --git a/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java b/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java index fe77c1ecd..54f4083b1 100644 --- a/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java +++ b/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java @@ -17,7 +17,6 @@ import java.util.Set; import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; @@ -28,9 +27,12 @@ import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult; -import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate; import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + /** * Executes the supplied java class in the current VM with the enclosing project's dependencies as classpath. * @@ -173,6 +175,33 @@ public class ExecJavaMojo @Deprecated private long killAfter; + /** + * If set to true the Java class executes asynchronously and build execution continues in parallel. + */ + @Parameter( property = "exec.async", defaultValue = "false" ) + private boolean async; + + /** + * Sets the script code to check if the Java class is successfully started. + */ + @Parameter( property = "exec.asyncStartCheck") + private String asyncStartCheck; + + /** + * Sets the script language to evaluate the {@link #asyncStartCheck}. + *

+ * It is possible to add new plugin dependencies of script libraries supporting JSR-223. + *

+ */ + @Parameter( property = "exec.asyncStartCheckScriptLanguage", defaultValue = "JavaScript") + private String asyncStartCheckScriptLanguage; + + /** + * Sets the poll interval in ms to evaluate the {@link #asyncStartCheck}. + */ + @Parameter( property = "exec.asyncStartCheckPollInterval", defaultValue = "1000") + private int asyncStartCheckPollInterval; + private Properties originalSystemProperties; /** @@ -224,7 +253,7 @@ public void execute() getLog().debug( msg ); } - IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( mainClass /* name */ ); + final IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( mainClass /* name */ ); Thread bootstrapThread = new Thread( threadGroup, new Runnable() { public void run() @@ -274,45 +303,79 @@ public void run() setSystemProperties(); bootstrapThread.start(); - joinNonDaemonThreads( threadGroup ); - // It's plausible that spontaneously a non-daemon thread might be created as we try and shut down, - // but it's too late since the termination condition (only daemon threads) has been triggered. - if ( keepAlive ) - { - getLog().warn( "Warning: keepAlive is now deprecated and obsolete. Do you need it? Please comment on MEXEC-6." ); - waitFor( 0 ); + if (!async) { + terminate(threadGroup); } + else { + if (asyncStartCheck != null) { + // wait until condition to know that java thread has started + ScriptEngineManager factory = new ScriptEngineManager(); + // create a JavaScript engine + ScriptEngine engine = factory.getEngineByName(asyncStartCheckScriptLanguage); + if (engine == null) { + throw new MojoExecutionException("Couldn't find engine for script language "+asyncStartCheckScriptLanguage); + } + // evaluate JavaScript code from String + while (true) { + try { + Thread.sleep(asyncStartCheckPollInterval); + } catch (InterruptedException e) { + throw new MojoExecutionException("Couldn't not sleep for poll interval.", e); + } + try { + if ((Boolean) engine.eval(asyncStartCheck)) { + getLog().info("Java class is ready."); + break; + } + else { + getLog().info("Java class is not yet ready. Waiting ..."); + } + } catch (ScriptException e) { + throw new MojoExecutionException("Couldn't evaluate start check expression.", e); + } + } + Runtime.getRuntime().addShutdownHook(new Thread() + { + public void run() + { + try { + terminate(threadGroup); + } catch (MojoExecutionException e) { + getLog().error(e); + } + } + }); + } + } + registerSourceRoots(); + } - if ( cleanupDaemonThreads ) - { + private void terminate(IsolatedThreadGroup threadGroup) throws MojoExecutionException { + joinNonDaemonThreads(threadGroup); - terminateThreads( threadGroup ); + if (cleanupDaemonThreads) { - try - { - threadGroup.destroy(); - } - catch ( IllegalThreadStateException e ) - { - getLog().warn( "Couldn't destroy threadgroup " + threadGroup, e ); + terminateThreads(threadGroup); + + try { + if (!threadGroup.isDestroyed()) { + threadGroup.destroy(); + } + } catch (IllegalThreadStateException e) { + getLog().warn("Couldn't destroy threadgroup " + threadGroup, e); } } - if ( originalSystemProperties != null ) - { - System.setProperties( originalSystemProperties ); + if (originalSystemProperties != null) { + System.setProperties(originalSystemProperties); } - synchronized ( threadGroup ) - { - if ( threadGroup.uncaughtException != null ) - { - throw new MojoExecutionException( "An exception occured while executing the Java class. " - + threadGroup.uncaughtException.getMessage(), threadGroup.uncaughtException ); + synchronized (threadGroup) { + if (threadGroup.uncaughtException != null) { + throw new MojoExecutionException("An exception occurred while executing the Java class. " + + threadGroup.uncaughtException.getMessage(), threadGroup.uncaughtException); } } - - registerSourceRoots(); } /** diff --git a/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java b/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java index f8ed07b04..d99da28ba 100644 --- a/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java +++ b/src/test/java/org/codehaus/mojo/exec/ExecJavaMojoTest.java @@ -198,7 +198,7 @@ public void testWaitNonInterruptibleDaemonThreads() /** * See MEXEC-15. FIXME: this sometimes fail with * unit.framework.ComparisonFailure: expected:<...> but was:<...3(f)> - * + * * @throws Exception if any exception occurs */ public void testUncooperativeThread() @@ -239,6 +239,20 @@ public void testRunWithArgs() assertEquals( expectedResult, resultString ); } + /** + * Test the async feature. + * + * @throws Exception if any exception occurs + */ + public void testAsync() + throws Exception + { + File pom = new File( getBasedir(), "src/test/projects/project16/pom.xml" ); + String output = execute(pom, "java"); + assertTrue(output.trim().contains("Started test server.")); + TestServerMain.stop(); + } + /** * @return output from System.out during mojo execution */ diff --git a/src/test/java/org/codehaus/mojo/exec/TestServerMain.java b/src/test/java/org/codehaus/mojo/exec/TestServerMain.java new file mode 100644 index 000000000..7270fa3a5 --- /dev/null +++ b/src/test/java/org/codehaus/mojo/exec/TestServerMain.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Karsten Ohme. + * + * 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.codehaus.mojo.exec; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Starts the test server. + * + * @author Karsten Ohme + * (k_o_@users.sourceforge.net ) + */ +public class TestServerMain { + + public static final int SERVER_PORT = 9081; + + private static HttpServer server; + + public static void start() throws Exception { + // simulate a delay of 5 seconds + int i = 0; + while (i++ < 5) { + Thread.sleep(1000); + } + server = HttpServer.create(new InetSocketAddress("localhost", SERVER_PORT), 0); + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + public void run() { + stop(); + } + })); + try { + server.createContext("/test", new MyHttpHandler()); + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); + server.setExecutor(threadPoolExecutor); + server.start(); + System.out.println("Started test server."); + } catch (Exception e) { + throw new RuntimeException("Could not start test server.", e); + } + } + + private static class MyHttpHandler implements HttpHandler { + public void handle(HttpExchange httpExchange) throws IOException { + handleResponse(httpExchange); + } + + private void handleResponse(HttpExchange httpExchange) throws IOException { + OutputStream outputStream = httpExchange.getResponseBody(); + httpExchange.sendResponseHeaders(200, -1); + outputStream.flush(); + outputStream.close(); + } + } + + public static void stop() { + if (server != null) { + server.stop(0); + } + } + + public static void main(String[] args) throws Exception { + start(); + } + +} diff --git a/src/test/projects/project16/pom.xml b/src/test/projects/project16/pom.xml new file mode 100644 index 000000000..b6ccc12d0 --- /dev/null +++ b/src/test/projects/project16/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + org.cb.maven.plugins.exec + project16 + 0.1 + jar + Maven Exec Plugin + Test that test the async function of the java mojo + + 2020 + + + Apache License 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + Karsten Ohme + kohme + k_o_@users.sourceforge.net + + Java Developer + + +1 + + + + + + junit + junit + 3.8.1 + test + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + test + + java + + + + + true + JavaScript + 1500 + org.codehaus.mojo.exec.TestServerMain + groovy + + try { + def code = new URL('http://localhost:9081/test').openConnection().with { + requestMethod = 'HEAD' + connect() + responseCode + } + + code == 200 + } + catch (java.net.ConnectException e) { + false + } + + + + + + +