View Javadoc
1   package org.codehaus.gmavenplus.mojo;
2   
3   import org.apache.maven.artifact.DependencyResolutionRequiredException;
4   import org.apache.maven.plugin.MojoExecutionException;
5   import org.apache.maven.plugins.annotations.Mojo;
6   import org.apache.maven.plugins.annotations.Parameter;
7   import org.apache.maven.plugins.annotations.ResolutionScope;
8   import org.codehaus.gmavenplus.model.internal.Version;
9   import org.codehaus.gmavenplus.util.FileUtils;
10  import org.codehaus.gmavenplus.util.NoExitSecurityManager;
11  
12  import java.io.BufferedReader;
13  import java.io.File;
14  import java.io.IOException;
15  import java.io.InputStream;
16  import java.io.InputStreamReader;
17  import java.io.Reader;
18  import java.lang.reflect.InvocationTargetException;
19  import java.lang.reflect.Method;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.net.URLConnection;
23  
24  import static org.codehaus.gmavenplus.util.ReflectionUtils.findConstructor;
25  import static org.codehaus.gmavenplus.util.ReflectionUtils.findMethod;
26  import static org.codehaus.gmavenplus.util.ReflectionUtils.invokeConstructor;
27  import static org.codehaus.gmavenplus.util.ReflectionUtils.invokeMethod;
28  
29  
30  /**
31   * Executes Groovy scripts (in the pom or external), bound to the current project.
32   * Note that this mojo requires Groovy >= 1.5.0.
33   * Note that it references the plugin classloader to pull in dependencies Groovy didn't include
34   * (for things like Ant for AntBuilder, Ivy for @grab, and Jansi for Groovysh).
35   *
36   * @author Keegan Witt
37   * @since 1.0-beta-1
38   */
39  @Mojo(name = "execute", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
40  public class ExecuteMojo extends AbstractToolsMojo {
41  
42      /**
43       * Groovy 4.0.0-RC-1 version.
44       */
45      protected static final Version GROOVY_4_0_0_RC_1 = new Version(4, 0, 0, "RC-1");
46  
47      /**
48       * Groovy 1.7.0 version.
49       */
50      protected static final Version GROOVY_1_7_0 = new Version(1, 7, 0);
51  
52      /**
53       * Groovy scripts to run (in order). Can be a script body, a {@link java.net.URL URL} to a script
54       * (local or remote), or a filename.
55       */
56      @Parameter(required = true, property = "scripts")
57      protected String[] scripts;
58  
59      /**
60       * Whether to continue executing remaining scripts when a script fails.
61       */
62      @Parameter(defaultValue = "false", property = "continueExecuting")
63      protected boolean continueExecuting;
64  
65      /**
66       * The encoding of script files/URLs.
67       *
68       * @since 1.0-beta-2
69       */
70      @Parameter(defaultValue = "${project.build.sourceEncoding}")
71      protected String sourceEncoding;
72  
73      /**
74       * Flag to allow script execution to be skipped.
75       *
76       * @since 1.9.1
77       */
78      @Parameter(defaultValue = "false", property = "skipScriptExecution")
79      protected boolean skipScriptExecution;
80  
81      /**
82       * The timeout to use for URL connections.
83       *
84       * @since 4.1.0
85       */
86      @Parameter(defaultValue = "0", property = "urlConnectionTimeout")
87      protected int urlConnectionTimeout;
88  
89      /**
90       * The timeout to use for URL reading.
91       *
92       * @since 4.1.0
93       */
94      @Parameter(defaultValue = "0", property = "urlReadTimeout")
95      protected int urlReadTimeout;
96  
97      /**
98       * Executes this mojo.
99       *
100      * @throws MojoExecutionException If an unexpected problem occurs (causes a "BUILD ERROR" message to be displayed)
101      */
102     @Override
103     public void execute() throws MojoExecutionException {
104         doExecute();
105     }
106 
107     /**
108      * Does the actual execution.
109      *
110      * @throws MojoExecutionException If an unexpected problem occurs (causes a "BUILD ERROR" message to be displayed)
111      */
112     protected synchronized void doExecute() throws MojoExecutionException {
113         if (skipScriptExecution) {
114             getLog().info("Skipping script execution because ${skipScriptExecution} was set to true.");
115             return;
116         }
117 
118         if (scripts == null || scripts.length == 0) {
119             getLog().info("No scripts specified for execution. Skipping.");
120             return;
121         }
122 
123         try {
124             setupClassWrangler(project.getTestClasspathElements(), includeClasspath);
125         } catch (MalformedURLException e) {
126             throw new MojoExecutionException("Unable to add project test dependencies to classpath.", e);
127         } catch (DependencyResolutionRequiredException e) {
128             throw new MojoExecutionException("Test dependencies weren't resolved.", e);
129         }
130 
131         logPluginClasspath();
132         classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
133 
134         try {
135             getLog().debug("Project test classpath:\n" + project.getTestClasspathElements());
136         } catch (DependencyResolutionRequiredException e) {
137             getLog().debug("Unable to log project test classpath");
138         }
139 
140         if (!groovyVersionSupportsAction()) {
141             getLog().error("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support script execution. The minimum version of Groovy required is " + minGroovyVersion + ". Skipping script execution.");
142             return;
143         }
144 
145         final SecurityManager defaultSecurityManager = System.getSecurityManager();
146         try {
147             if (!allowSystemExits) {
148                 getLog().warn("JEP 411 deprecated Security Manager in Java 17 for removal. Therefore `allowSystemExits` is also deprecated for removal.");
149                 try {
150                     System.setSecurityManager(new NoExitSecurityManager());
151                 } catch (UnsupportedOperationException e) {
152                     getLog().warn("Attempted to use Security Manager in a JVM where it's disabled by default. You might try `-Djava.security.manager=allow` to override this.");
153                 }
154             }
155 
156             // get classes we need with reflection
157             Class<?> groovyShellClass = classWrangler.getClass("groovy.lang.GroovyShell");
158 
159             // create a GroovyShell to run scripts in
160             Object shell = setupShell(groovyShellClass);
161 
162             // run the scripts
163             executeScripts(groovyShellClass, shell);
164         } catch (ClassNotFoundException e) {
165             throw new MojoExecutionException("Unable to get a Groovy class from classpath (" + e.getMessage() + "). Do you have Groovy as a compile dependency in your project or the plugin?", e);
166         } catch (InvocationTargetException e) {
167             throw new MojoExecutionException("Error occurred while calling a method on a Groovy class from classpath.", e);
168         } catch (InstantiationException e) {
169             throw new MojoExecutionException("Error occurred while instantiating a Groovy class from classpath.", e);
170         } catch (IllegalAccessException e) {
171             throw new MojoExecutionException("Unable to access a method on a Groovy class from classpath.", e);
172         } finally {
173             if (!allowSystemExits) {
174                 try {
175                     System.setSecurityManager(defaultSecurityManager);
176                 } catch (UnsupportedOperationException e) {
177                     getLog().warn("Attempted to use Security Manager in a JVM where it's disabled by default. You might try `-Djava.security.manager=allow` to override this.");
178                 }
179             }
180         }
181     }
182 
183     /**
184      * Instantiates a new groovy.lang.GroovyShell object.
185      *
186      * @param groovyShellClass the groovy.lang.GroovyShell class
187      * @return a new groovy.lang.GroovyShell object
188      * @throws InvocationTargetException when a reflection invocation needed for shell configuration cannot be completed
189      * @throws IllegalAccessException    when a method needed for shell configuration cannot be accessed
190      * @throws InstantiationException    when a class needed for shell configuration cannot be instantiated
191      * @throws ClassNotFoundException    when a class needed for shell configuration cannot be found
192      */
193     protected Object setupShell(final Class<?> groovyShellClass) throws InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
194         Object shell;
195         if (sourceEncoding != null) {
196             Class<?> compilerConfigurationClass = classWrangler.getClass("org.codehaus.groovy.control.CompilerConfiguration");
197             Object compilerConfiguration = invokeConstructor(findConstructor(compilerConfigurationClass));
198             invokeMethod(findMethod(compilerConfigurationClass, "setSourceEncoding", String.class), compilerConfiguration, sourceEncoding);
199             shell = invokeConstructor(findConstructor(groovyShellClass, ClassLoader.class, compilerConfigurationClass), classWrangler.getClassLoader(), compilerConfiguration);
200         } else {
201             shell = invokeConstructor(findConstructor(groovyShellClass, ClassLoader.class), classWrangler.getClassLoader());
202         }
203         initializeProperties();
204         Method setProperty = findMethod(groovyShellClass, "setProperty", String.class, Object.class);
205         if (bindPropertiesToSeparateVariables) {
206             for (Object k : properties.keySet()) {
207                 invokeMethod(setProperty, shell, k, properties.get(k));
208             }
209         } else {
210             if (groovyOlderThan(GROOVY_4_0_0_RC_1)) {
211                 invokeMethod(setProperty, shell, "properties", properties);
212             } else {
213                 throw new IllegalArgumentException("properties is a read-only property in Groovy " + GROOVY_4_0_0_RC_1 + " and later.");
214             }
215         }
216 
217         return shell;
218     }
219 
220     /**
221      * Executes the configured scripts.
222      *
223      * @param groovyShellClass the groovy.lang.GroovyShell class
224      * @param shell            a groovy.lag.GroovyShell object
225      * @throws InvocationTargetException when a reflection invocation needed for script execution cannot be completed
226      * @throws IllegalAccessException    when a method needed for script execution cannot be accessed
227      * @throws MojoExecutionException    when an exception occurred during script execution (causes a "BUILD ERROR" message to be displayed)
228      */
229     protected void executeScripts(final Class<?> groovyShellClass, final Object shell) throws InvocationTargetException, IllegalAccessException, MojoExecutionException {
230         int scriptNum = 1;
231         for (String script : scripts) {
232             try {
233                 // TODO: try as file first, then as URL?
234                 try {
235                     // it's a URL to a script
236                     executeScriptFromUrl(groovyShellClass, shell, script);
237                 } catch (MalformedURLException e) {
238                     // it's not a URL to a script, try as a filename
239                     File scriptFile = new File(script);
240                     if (scriptFile.isFile()) {
241                         getLog().info("Running Groovy script from " + scriptFile.getCanonicalPath() + ".");
242                         Method evaluateFile = findMethod(groovyShellClass, "evaluate", File.class);
243                         invokeMethod(evaluateFile, shell, scriptFile);
244                     } else {
245                         // it's neither a filename or URL, treat as a script body
246                         Method evaluateString = findMethod(groovyShellClass, "evaluate", String.class);
247                         invokeMethod(evaluateString, shell, script);
248                     }
249                 }
250             } catch (IOException ioe) {
251                 if (continueExecuting) {
252                     getLog().error("An Exception occurred while executing script " + scriptNum + ". Continuing to execute remaining scripts.", ioe);
253                 } else {
254                     throw new MojoExecutionException("An Exception occurred while executing script " + scriptNum + ".", ioe);
255                 }
256             }
257             scriptNum++;
258         }
259     }
260 
261     /**
262      * Executes a script at a URL location.
263      *
264      * @param groovyShellClass the GroovyShell class
265      * @param shell            a groovy.lag.GroovyShell object
266      * @param script           the script URL to execute
267      * @throws IOException               when the stream can't be opened on the URL
268      * @throws InvocationTargetException when a reflection invocation needed for script execution cannot be completed
269      * @throws IllegalAccessException    when a method needed for script execution cannot be accessed
270      */
271     protected void executeScriptFromUrl(Class<?> groovyShellClass, Object shell, String script) throws IOException, InvocationTargetException, IllegalAccessException {
272         URLConnection urlConnection = new URL(script).openConnection();
273         if (urlConnectionTimeout > 0) {
274             urlConnection.setConnectTimeout(urlConnectionTimeout);
275         }
276         if (urlReadTimeout > 0) {
277             urlConnection.setReadTimeout(urlReadTimeout);
278         }
279         getLog().info("Running Groovy script from " + urlConnection.getURL() + ".");
280         if (groovyAtLeast(GROOVY_1_7_0)) {
281             Method evaluateUrlWithReader = findMethod(groovyShellClass, "evaluate", Reader.class);
282             BufferedReader reader = null;
283             try {
284                 if (sourceEncoding != null) {
285                     reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), sourceEncoding));
286                 } else {
287                     reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
288                 }
289                 invokeMethod(evaluateUrlWithReader, shell, reader);
290             } finally {
291                 FileUtils.closeQuietly(reader);
292             }
293         } else {
294             Method evaluateUrlWithStream = findMethod(groovyShellClass, "evaluate", InputStream.class);
295             InputStream inputStream = null;
296             try {
297                 if (sourceEncoding != null) {
298                     getLog().warn("Source encoding does not apply to Groovy versions previous to 1.7.0, ignoring.");
299                 }
300                 inputStream = urlConnection.getInputStream();
301                 invokeMethod(evaluateUrlWithStream, shell, urlConnection.getInputStream());
302             } finally {
303                 FileUtils.closeQuietly(inputStream);
304             }
305         }
306     }
307 
308 }