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             final SecurityManager sm = System.getSecurityManager();
141             try {
142                 if (!allowSystemExits) {
143                     System.setSecurityManager(new NoExitSecurityManager());
144                 }
145 
146                 // get classes we need with reflection
147                 Class<?> groovyShellClass = classWrangler.getClass("groovy.lang.GroovyShell");
148 
149                 // create a GroovyShell to run scripts in
150                 Object shell = setupShell(groovyShellClass);
151 
152                 // run the scripts
153                 executeScripts(groovyShellClass, shell);
154             } catch (ClassNotFoundException e) {
155                 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);
156             } catch (InvocationTargetException e) {
157                 throw new MojoExecutionException("Error occurred while calling a method on a Groovy class from classpath.", e);
158             } catch (InstantiationException e) {
159                 throw new MojoExecutionException("Error occurred while instantiating a Groovy class from classpath.", e);
160             } catch (IllegalAccessException e) {
161                 throw new MojoExecutionException("Unable to access a method on a Groovy class from classpath.", e);
162             } finally {
163                 if (!allowSystemExits) {
164                     System.setSecurityManager(sm);
165                 }
166             }
167         } else {
168             getLog().error("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support script execution. The minimum version of Groovy required is " + minGroovyVersion + ". Skipping script execution.");
169         }
170     }
171 
172     /**
173      * Instantiates a new groovy.lang.GroovyShell object.
174      *
175      * @param groovyShellClass the groovy.lang.GroovyShell class
176      * @return a new groovy.lang.GroovyShell object
177      * @throws InvocationTargetException when a reflection invocation needed for shell configuration cannot be completed
178      * @throws IllegalAccessException    when a method needed for shell configuration cannot be accessed
179      * @throws InstantiationException    when a class needed for shell configuration cannot be instantiated
180      * @throws ClassNotFoundException    when a class needed for shell configuration cannot be found
181      */
182     protected Object setupShell(final Class<?> groovyShellClass) throws InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
183         Object shell;
184         if (sourceEncoding != null) {
185             Class<?> compilerConfigurationClass = classWrangler.getClass("org.codehaus.groovy.control.CompilerConfiguration");
186             Object compilerConfiguration = invokeConstructor(findConstructor(compilerConfigurationClass));
187             invokeMethod(findMethod(compilerConfigurationClass, "setSourceEncoding", String.class), compilerConfiguration, sourceEncoding);
188             shell = invokeConstructor(findConstructor(groovyShellClass, ClassLoader.class, compilerConfigurationClass), classWrangler.getClassLoader(), compilerConfiguration);
189         } else {
190             shell = invokeConstructor(findConstructor(groovyShellClass, ClassLoader.class), classWrangler.getClassLoader());
191         }
192         initializeProperties();
193         Method setProperty = findMethod(groovyShellClass, "setProperty", String.class, Object.class);
194         if (bindPropertiesToSeparateVariables) {
195             for (Object k : properties.keySet()) {
196                 invokeMethod(setProperty, shell, k, properties.get(k));
197             }
198         } else {
199             if (groovyOlderThan(GROOVY_4_0_0_RC_1)) {
200                 invokeMethod(setProperty, shell, "properties", properties);
201             } else {
202                 throw new IllegalArgumentException("properties is a read-only property in Groovy " + GROOVY_4_0_0_RC_1 + " and later.");
203             }
204         }
205 
206         return shell;
207     }
208 
209     /**
210      * Executes the configured scripts.
211      *
212      * @param groovyShellClass the groovy.lang.GroovyShell class
213      * @param shell            a groovy.lag.GroovyShell object
214      * @throws InvocationTargetException when a reflection invocation needed for script execution cannot be completed
215      * @throws IllegalAccessException    when a method needed for script execution cannot be accessed
216      * @throws MojoExecutionException    when an exception occurred during script execution (causes a "BUILD ERROR" message to be displayed)
217      */
218     protected void executeScripts(final Class<?> groovyShellClass, final Object shell) throws InvocationTargetException, IllegalAccessException, MojoExecutionException {
219         int scriptNum = 1;
220         for (String script : scripts) {
221             try {
222                 // TODO: try as file first, then as URL?
223                 try {
224                     // it's a URL to a script
225                     executeScriptFromUrl(groovyShellClass, shell, script);
226                 } catch (MalformedURLException e) {
227                     // it's not a URL to a script, try as a filename
228                     File scriptFile = new File(script);
229                     if (scriptFile.isFile()) {
230                         getLog().info("Running Groovy script from " + scriptFile.getCanonicalPath() + ".");
231                         Method evaluateFile = findMethod(groovyShellClass, "evaluate", File.class);
232                         invokeMethod(evaluateFile, shell, scriptFile);
233                     } else {
234                         // it's neither a filename or URL, treat as a script body
235                         Method evaluateString = findMethod(groovyShellClass, "evaluate", String.class);
236                         invokeMethod(evaluateString, shell, script);
237                     }
238                 }
239             } catch (IOException ioe) {
240                 if (continueExecuting) {
241                     getLog().error("An Exception occurred while executing script " + scriptNum + ". Continuing to execute remaining scripts.", ioe);
242                 } else {
243                     throw new MojoExecutionException("An Exception occurred while executing script " + scriptNum + ".", ioe);
244                 }
245             }
246             scriptNum++;
247         }
248     }
249 
250     /**
251      * Executes a script at a URL location.
252      *
253      * @param groovyShellClass the GroovyShell class
254      * @param shell            a groovy.lag.GroovyShell object
255      * @param script           the script URL to execute
256      * @throws IOException               when the stream can't be opened on the URL
257      * @throws InvocationTargetException when a reflection invocation needed for script execution cannot be completed
258      * @throws IllegalAccessException    when a method needed for script execution cannot be accessed
259      */
260     protected void executeScriptFromUrl(Class<?> groovyShellClass, Object shell, String script) throws IOException, InvocationTargetException, IllegalAccessException {
261         URL url = new URL(script);
262         getLog().info("Running Groovy script from " + url + ".");
263         if (groovyAtLeast(GROOVY_1_7_0)) {
264             Method evaluateUrlWithReader = findMethod(groovyShellClass, "evaluate", Reader.class);
265             BufferedReader reader = null;
266             try {
267                 if (sourceEncoding != null) {
268                     reader = new BufferedReader(new InputStreamReader(url.openStream(), sourceEncoding));
269                 } else {
270                     reader = new BufferedReader(new InputStreamReader(url.openStream()));
271                 }
272                 invokeMethod(evaluateUrlWithReader, shell, reader);
273             } finally {
274                 FileUtils.closeQuietly(reader);
275             }
276         } else {
277             Method evaluateUrlWithStream = findMethod(groovyShellClass, "evaluate", InputStream.class);
278             InputStream inputStream = null;
279             try {
280                 if (sourceEncoding != null) {
281                     getLog().warn("Source encoding does not apply to Groovy versions previous to 1.7.0, ignoring.");
282                 }
283                 inputStream = url.openStream();
284                 invokeMethod(evaluateUrlWithStream, shell, inputStream);
285             } finally {
286                 FileUtils.closeQuietly(inputStream);
287             }
288         }
289     }
290 
291 }