View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.geronimo.genesis.plugins.script;
21  
22  import java.io.File;
23  import java.io.InputStream;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Properties;
33  
34  import groovy.lang.GroovyClassLoader;
35  import groovy.lang.GroovyObject;
36  import groovy.lang.GroovyResourceLoader;
37  import groovy.lang.GroovyRuntimeException;
38  
39  import org.apache.maven.artifact.Artifact;
40  import org.apache.maven.artifact.DependencyResolutionRequiredException;
41  import org.apache.maven.artifact.repository.ArtifactRepository;
42  import org.apache.maven.plugin.MojoExecutionException;
43  import org.apache.maven.project.MavenProject;
44  
45  import org.apache.geronimo.genesis.MojoSupport;
46  import org.apache.geronimo.genesis.util.ArtifactItem;
47  import org.apache.geronimo.genesis.util.ExpressionParser;
48  
49  /**
50   * Executes a <a href="http://groovy.codehaus.org">Groovy</a> script.
51   *
52   * @goal groovy
53   * @configurator override
54   * @requiresDependencyResolution
55   *
56   * @version $Rev: 507770 $ $Date: 2007-02-14 17:49:14 -0800 (Wed, 14 Feb 2007) $
57   */
58  public class GroovyMojo
59      extends MojoSupport
60  {
61      /**
62       * The source of the script to execute.
63       *
64       * @parameter
65       * @required
66       */
67      private CodeSource source = null;
68  
69      /**
70       * Additional artifacts to add to the scripts classpath.
71       *
72       * @parameter
73       */
74      private ArtifactItem[] classpath = null;
75  
76      /**
77       * Path to search for imported scripts.
78       *
79       * @parameter expression
80       */
81      private File[] scriptpath = null;
82  
83      /**
84       * A set of default project properties, which the values will be used only if
85       * the project or system does not override.
86       *
87       * @parameter
88       */
89      private Map defaults;
90  
91      /**
92       * A set of additional project properties.
93       * 
94       * @parameter
95       */
96      private Map properties;
97  
98      //
99      // TODO: Find a better name for this... and figure out how to best use it to configure a custom groovy object
100     //
101     // private DelayedConfiguration custom;
102 
103     //
104     // Maven components
105     //
106     
107     /**
108      * @parameter expression="${project}"
109      * @readonly
110      * @required
111      */
112     private MavenProject project = null;
113 
114     /**
115      * @parameter expression="${localRepository}"
116      * @readonly
117      * @required
118      */
119     private ArtifactRepository artifactRepository = null;
120 
121     //
122     // MojoSupport Hooks
123     //
124 
125     protected MavenProject getProject() {
126         return project;
127     }
128     
129     protected ArtifactRepository getArtifactRepository() {
130         return artifactRepository;
131     }
132 
133     //
134     // Mojo
135     //
136 
137     protected void doExecute() throws Exception {
138         boolean debug = log.isDebugEnabled();
139 
140         Class type = loadGroovyClass(source);
141         if (type == null) {
142             throw new MojoExecutionException("Source did not evaluate to a Groovy class: " + source);
143         }
144         
145         GroovyObject obj = (GroovyObject)type.newInstance();
146 
147         /*
148         if (custom != null) {
149             log.info("Applying delayed configuration: " + custom);
150 
151             MetaClass meta = obj.getMetaClass();
152             MetaMethod method = meta.pickMethod(obj, "configure", new Object[] { custom });
153             log.info("Using configure method: " + method);
154 
155             method.invoke(obj, new Object[] { custom });
156         }
157         */
158         
159         // Expose logging
160         obj.setProperty("log", log);
161 
162         // Create a delegate to allow getProperites() to be fully resolved
163         MavenProject delegate = new MavenProject(project) {
164             private Properties resolvedProperties;
165 
166             public Properties getProperties() {
167                 if (resolvedProperties == null) {
168                     resolvedProperties = resolveProperties(project.getProperties());
169                 }
170                 return resolvedProperties;
171             }
172         };
173 
174         obj.setProperty("project", delegate);
175         obj.setProperty("pom", delegate);
176 
177         // Execute the script
178         if (debug) {
179             log.debug("Invoking run() on: " + obj);
180         }
181         
182         try {
183             obj.invokeMethod("run", new Object[0]);
184         }
185         catch (GroovyRuntimeException e) {
186             if (log.isDebugEnabled()) {
187                 // Yes, log error if debug is enabled
188                 log.error("Groovy script execution failure", e);
189             }
190             
191             Throwable cause = e.getCause();
192             if (cause == null) {
193                 cause = e;
194             }
195             
196             throw new MojoExecutionException(cause.getMessage(), cause);
197         }
198     }
199 
200     private Class loadGroovyClass(final CodeSource source) throws Exception {
201         assert source != null;
202 
203         boolean debug = log.isDebugEnabled();
204 
205         // Make sure the codesource us valid first
206         source.validate();
207 
208         Class type;
209         GroovyClassLoader loader = createGroovyClassLoader();
210 
211         if (source.getBody() != null) {
212             type = loader.parseClass(source.getBody());
213         }
214         else {
215             URL url;
216             if (source.getFile() != null) {
217                 url = source.getFile().toURL();
218             }
219             else {
220                 url = source.getUrl();
221             }
222             if (debug) {
223                 log.debug("Loading source from: " + url);
224             }
225 
226             String fileName = new File(url.getFile()).getName();
227             InputStream input = url.openConnection().getInputStream();
228             try {
229                 type = loader.parseClass(input, fileName);
230             }
231             finally {
232                 input.close();
233             }
234         }
235 
236         return type;
237     }
238 
239     private GroovyClassLoader createGroovyClassLoader() throws Exception {
240         boolean debug = log.isDebugEnabled();
241 
242         ClassLoader parent = getClass().getClassLoader();
243         URL[] urls = getClasspath();
244         URLClassLoader cl = new URLClassLoader(urls, parent);
245 
246         // Validate and dump the scriptpath
247         if (scriptpath != null) {
248             log.debug("Scriptpath:");
249             for (int i=0; i < scriptpath.length; i++) {
250                 if (scriptpath[i] == null) {
251                     throw new MojoExecutionException("Null element found in scriptpath at index: " + i);
252                 }
253 
254                 if (debug) {
255                     log.debug("    " + scriptpath[i]);
256                 }
257             }
258         }
259 
260         //
261         // TODO: Investigate using GroovyScript instead of this...
262         //
263 
264         GroovyClassLoader loader = new GroovyClassLoader(cl);
265 
266         // Allow peer scripts to be loaded
267         loader.setResourceLoader(new GroovyResourceLoader() {
268             public URL loadGroovySource(final String classname) throws MalformedURLException {
269                 return resolveGroovyScript(classname);
270             }
271         });
272 
273         return loader;
274     }
275 
276     private URL resolveGroovyScript(final String classname) throws MalformedURLException {
277         assert classname != null;
278         
279         if (log.isDebugEnabled()) {
280             log.debug("Resolving script for class: " + classname);
281         }
282         
283         String resource = classname.replace('.', '/');
284         if (!resource.startsWith("/")) {
285             resource = "/" + resource;
286         }
287         resource = resource + ".groovy";
288 
289         // First check the scriptpath
290         if (scriptpath != null) {
291             for (int i=0; i<scriptpath.length; i++) {
292                 assert scriptpath[i] != null;
293 
294                 File file = new File(scriptpath[i], resource);
295                 if (file.exists()) {
296                     return file.toURL();
297                 }
298             }
299         }
300 
301         // Then look for a resource in the classpath
302         ClassLoader cl = Thread.currentThread().getContextClassLoader();
303         URL url = cl.getResource(resource);
304         if (url == null) {
305             // And finally check for a class defined in a file next to the main script file
306             File script = source.getFile();
307             if (script != null) {
308                 File file = new File(script.getParentFile(), resource);
309                 if (file.exists()) {
310                     return file.toURL();
311                 }
312             }
313         }
314         else {
315             return url;
316         }
317         
318         if (log.isDebugEnabled()) {
319             log.debug("Unable to resolve script for class: " + classname);
320         }
321         
322         // Else not found
323         return null;
324     }
325 
326     private URL[] getClasspath() throws DependencyResolutionRequiredException, MalformedURLException, MojoExecutionException {
327         List list = new ArrayList();
328 
329         // Add the plugins dependencies
330         List classpathFiles = project.getCompileClasspathElements();
331         for (int i = 0; i < classpathFiles.size(); ++i) {
332             list.add(new File((String)classpathFiles.get(i)).toURL());
333         }
334 
335         // Add custom dependencies
336         if (classpath != null) {
337             for (int i=0; i < classpath.length; i++) {
338                 Artifact artifact = getArtifact(classpath[i]);
339                 list.add(artifact.getFile().toURL());
340             }
341         }
342 
343         URL[] urls = (URL[])list.toArray(new URL[list.size()]);
344 
345         // Dump the classpath
346         if (log.isDebugEnabled()) {
347             log.debug("Classpath:");
348             for (int i=0; i < urls.length; i++) {
349                 log.debug("    " + urls[i]);
350             }
351         }
352 
353         return urls;
354     }
355     
356     private Properties resolveProperties(final Properties source) {
357         assert source != null;
358 
359         //
360         // NOTE: Create a chain of defaults
361         //
362 
363         Properties dprops = new Properties();
364         if (defaults != null) {
365             dprops.putAll(defaults);
366         }
367 
368         Properties sprops = new Properties(dprops);
369         sprops.putAll(System.getProperties());
370 
371         Properties props = new Properties(sprops);
372 
373         // Put all of the additional project props, which should already be resolved by mvn
374         if (properties != null) {
375             props.putAll(properties);
376         }
377 
378         // Setup the variables which should be used for resolution
379         Map vars = new HashMap();
380         vars.put("project", project);
381 
382         // Resolve all source properties
383         ExpressionParser parser = new ExpressionParser(vars);
384         Iterator iter = source.keySet().iterator();
385         while (iter.hasNext()) {
386             String name = (String)iter.next();
387             props.put(name, parser.parse(source.getProperty(name)));
388         }
389 
390         return props;
391     }
392 }