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