001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.genesis.plugins.script;
021    
022    import java.io.File;
023    import java.io.InputStream;
024    import java.net.MalformedURLException;
025    import java.net.URL;
026    import java.net.URLClassLoader;
027    import java.util.ArrayList;
028    import java.util.HashMap;
029    import java.util.Iterator;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Properties;
033    
034    import groovy.lang.GroovyClassLoader;
035    import groovy.lang.GroovyObject;
036    import groovy.lang.GroovyResourceLoader;
037    import groovy.lang.GroovyRuntimeException;
038    
039    import org.apache.maven.artifact.Artifact;
040    import org.apache.maven.artifact.DependencyResolutionRequiredException;
041    import org.apache.maven.artifact.repository.ArtifactRepository;
042    import org.apache.maven.plugin.MojoExecutionException;
043    import org.apache.maven.project.MavenProject;
044    
045    import org.apache.geronimo.genesis.MojoSupport;
046    import org.apache.geronimo.genesis.util.ArtifactItem;
047    import org.apache.geronimo.genesis.util.ExpressionParser;
048    
049    /**
050     * Executes a <a href="http://groovy.codehaus.org">Groovy</a> script.
051     *
052     * @goal groovy
053     * @configurator override
054     * @requiresDependencyResolution
055     *
056     * @version $Rev: 474105 $ $Date: 2006-11-12 16:28:53 -0800 (Sun, 12 Nov 2006) $
057     */
058    public class GroovyMojo
059        extends MojoSupport
060    {
061        /**
062         * The source of the script to execute.
063         *
064         * @parameter
065         * @required
066         */
067        private CodeSource source = null;
068    
069        /**
070         * Additional artifacts to add to the scripts classpath.
071         *
072         * @parameter
073         */
074        private ArtifactItem[] classpath = null;
075    
076        /**
077         * Path to search for imported scripts.
078         *
079         * @parameter expression
080         */
081        private File[] scriptpath = null;
082    
083        /**
084         * A set of default project properties, which the values will be used only if
085         * the project or system does not override.
086         *
087         * @parameter
088         */
089        private Map defaults;
090    
091        /**
092         * A set of additional project properties.
093         * 
094         * @parameter
095         */
096        private Map properties;
097    
098        //
099        // 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            GroovyObject obj = (GroovyObject)type.newInstance();
142    
143            /*
144            if (custom != null) {
145                log.info("Applying delayed configuration: " + custom);
146    
147                MetaClass meta = obj.getMetaClass();
148                MetaMethod method = meta.pickMethod(obj, "configure", new Object[] { custom });
149                log.info("Using configure method: " + method);
150    
151                method.invoke(obj, new Object[] { custom });
152            }
153            */
154            
155            // Expose logging
156            obj.setProperty("log", log);
157    
158            // Create a delegate to allow getProperites() to be fully resolved
159            MavenProject delegate = new MavenProject(project) {
160                private Properties resolvedProperties;
161    
162                public Properties getProperties() {
163                    if (resolvedProperties == null) {
164                        resolvedProperties = resolveProperties(project.getProperties());
165                    }
166                    return resolvedProperties;
167                }
168            };
169    
170            obj.setProperty("project", delegate);
171            obj.setProperty("pom", delegate);
172    
173            // Execute the script
174            if (debug) {
175                log.debug("Invoking run() on: " + obj);
176            }
177            
178            try {
179                obj.invokeMethod("run", new Object[0]);
180            }
181            catch (GroovyRuntimeException e) {
182                if (log.isDebugEnabled()) {
183                    // Yes, log error if debug is enabled
184                    log.error("Groovy script execution failure", e);
185                }
186                
187                Throwable cause = e.getCause();
188                if (cause == null) {
189                    cause = e;
190                }
191                
192                throw new MojoExecutionException(cause.getMessage(), cause);
193            }
194        }
195    
196        private Class loadGroovyClass(final CodeSource source) throws Exception {
197            assert source != null;
198    
199            boolean debug = log.isDebugEnabled();
200    
201            // Make sure the codesource us valid first
202            source.validate();
203    
204            Class type;
205            GroovyClassLoader loader = createGroovyClassLoader();
206    
207            if (source.getBody() != null) {
208                type = loader.parseClass(source.getBody());
209            }
210            else {
211                URL url;
212                if (source.getFile() != null) {
213                    url = source.getFile().toURL();
214                }
215                else {
216                    url = source.getUrl();
217                }
218                if (debug) {
219                    log.debug("Loading source from: " + url);
220                }
221    
222                String fileName = new File(url.getFile()).getName();
223                InputStream input = url.openConnection().getInputStream();
224                try {
225                    type = loader.parseClass(input, fileName);
226                }
227                finally {
228                    input.close();
229                }
230            }
231    
232            return type;
233        }
234    
235        private GroovyClassLoader createGroovyClassLoader() throws Exception {
236            boolean debug = log.isDebugEnabled();
237    
238            ClassLoader parent = getClass().getClassLoader();
239            URL[] urls = getClasspath();
240            URLClassLoader cl = new URLClassLoader(urls, parent);
241    
242            // Validate and dump the scriptpath
243            if (scriptpath != null) {
244                log.debug("Scriptpath:");
245                for (int i=0; i < scriptpath.length; i++) {
246                    if (scriptpath[i] == null) {
247                        throw new MojoExecutionException("Null element found in scriptpath at index: " + i);
248                    }
249    
250                    if (debug) {
251                        log.debug("    " + scriptpath[i]);
252                    }
253                }
254            }
255    
256            //
257            // TODO: Investigate using GroovyScript instead of this...
258            //
259    
260            GroovyClassLoader loader = new GroovyClassLoader(cl);
261    
262            // Allow peer scripts to be loaded
263            loader.setResourceLoader(new GroovyResourceLoader() {
264                public URL loadGroovySource(final String classname) throws MalformedURLException {
265                    return resolveGroovyScript(classname);
266                }
267            });
268    
269            return loader;
270        }
271    
272        private URL resolveGroovyScript(final String classname) throws MalformedURLException {
273            assert classname != null;
274            
275            if (log.isDebugEnabled()) {
276                log.debug("Resolving script for class: " + classname);
277            }
278            
279            String resource = classname.replace('.', '/');
280            if (!resource.startsWith("/")) {
281                resource = "/" + resource;
282            }
283            resource = resource + ".groovy";
284    
285            // First check the scriptpath
286            if (scriptpath != null) {
287                for (int i=0; i<scriptpath.length; i++) {
288                    assert scriptpath[i] != null;
289    
290                    File file = new File(scriptpath[i], resource);
291                    if (file.exists()) {
292                        return file.toURL();
293                    }
294                }
295            }
296    
297            // Then look for a resource in the classpath
298            ClassLoader cl = Thread.currentThread().getContextClassLoader();
299            URL url = cl.getResource(resource);
300            if (url == null) {
301                // And finally check for a class defined in a file next to the main script file
302                File script = source.getFile();
303                if (script != null) {
304                    File file = new File(script.getParentFile(), resource);
305                    if (file.exists()) {
306                        return file.toURL();
307                    }
308                }
309            }
310            else {
311                return url;
312            }
313            
314            if (log.isDebugEnabled()) {
315                log.debug("Unable to resolve script for class: " + classname);
316            }
317            
318            // Else not found
319            return null;
320        }
321    
322        private URL[] getClasspath() throws DependencyResolutionRequiredException, MalformedURLException, MojoExecutionException {
323            List list = new ArrayList();
324    
325            // Add the plugins dependencies
326            List classpathFiles = project.getCompileClasspathElements();
327            for (int i = 0; i < classpathFiles.size(); ++i) {
328                list.add(new File((String)classpathFiles.get(i)).toURL());
329            }
330    
331            // Add custom dependencies
332            if (classpath != null) {
333                for (int i=0; i < classpath.length; i++) {
334                    Artifact artifact = getArtifact(classpath[i]);
335                    list.add(artifact.getFile().toURL());
336                }
337            }
338    
339            URL[] urls = (URL[])list.toArray(new URL[list.size()]);
340    
341            // Dump the classpath
342            if (log.isDebugEnabled()) {
343                log.debug("Classpath:");
344                for (int i=0; i < urls.length; i++) {
345                    log.debug("    " + urls[i]);
346                }
347            }
348    
349            return urls;
350        }
351        
352        private Properties resolveProperties(final Properties source) {
353            assert source != null;
354    
355            //
356            // NOTE: Create a chain of defaults
357            //
358    
359            Properties dprops = new Properties();
360            if (defaults != null) {
361                dprops.putAll(defaults);
362            }
363    
364            Properties sprops = new Properties(dprops);
365            sprops.putAll(System.getProperties());
366    
367            Properties props = new Properties(sprops);
368    
369            // Put all of the additional project props, which should already be resolved by mvn
370            if (properties != null) {
371                props.putAll(properties);
372            }
373    
374            // Setup the variables which should be used for resolution
375            Map vars = new HashMap();
376            vars.put("project", project);
377    
378            // Resolve all source properties
379            ExpressionParser parser = new ExpressionParser(vars);
380            Iterator iter = source.keySet().iterator();
381            while (iter.hasNext()) {
382                String name = (String)iter.next();
383                props.put(name, parser.parse(source.getProperty(name)));
384            }
385    
386            return props;
387        }
388    }