001    /*******************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.
003     * ---------------------------------------------------------------------------
004     * The software in this package is published under the terms of the BSD style
005     * license a copy of which has been included with this distribution in the
006     * LICENSE.txt file.
007     ******************************************************************************/
008    package org.picocontainer.script.groovy;
009    
010    import groovy.lang.Binding;
011    import groovy.lang.GroovyClassLoader;
012    import groovy.lang.GroovyCodeSource;
013    import groovy.lang.GroovyObject;
014    import groovy.lang.MissingPropertyException;
015    import groovy.lang.Script;
016    
017    import java.io.IOException;
018    import java.io.InputStream;
019    import java.io.Reader;
020    import java.net.URL;
021    
022    import org.codehaus.groovy.control.CompilationFailedException;
023    import org.codehaus.groovy.runtime.InvokerHelper;
024    import org.picocontainer.PicoContainer;
025    import org.picocontainer.behaviors.Caching;
026    import org.picocontainer.containers.EmptyPicoContainer;
027    import org.picocontainer.classname.DefaultClassLoadingPicoContainer;
028    import org.picocontainer.script.LifecycleMode;
029    import org.picocontainer.script.ScriptedPicoContainerMarkupException;
030    import org.picocontainer.script.ScriptedContainerBuilder;
031    import org.picocontainer.classname.ClassLoadingPicoContainer;
032    import org.picocontainer.DefaultPicoContainer;
033    
034    /**
035     * {@inheritDoc}
036     * The groovy script has to return an instance of {@link org.picocontainer.classname.ClassLoadingPicoContainer}.
037     * There is an implicit variable named "parent" that may contain a reference to a parent
038     * container. 
039     *
040     * @author Paul Hammant
041     * @author Aslak Hellesøy
042     * @author Mauro Talevi
043     */
044    public class GroovyContainerBuilder extends ScriptedContainerBuilder {
045        private Class<?> scriptClass;
046    
047        public GroovyContainerBuilder(final Reader script, ClassLoader classLoader) {
048            this(script,classLoader, LifecycleMode.AUTO_LIFECYCLE);
049        }
050        
051        public GroovyContainerBuilder(final Reader script, ClassLoader classLoader, LifecycleMode lifecycleMode) {
052            super(script,classLoader, lifecycleMode);
053            createGroovyClass();
054        }
055    
056        public GroovyContainerBuilder(final URL script, ClassLoader classLoader) {
057            this(script,classLoader, LifecycleMode.AUTO_LIFECYCLE);
058        }
059    
060        public GroovyContainerBuilder(final URL script, ClassLoader classLoader, LifecycleMode lifecycleMode) {
061            super(script, classLoader, lifecycleMode);
062            createGroovyClass();
063        }
064        
065        protected PicoContainer createContainerFromScript(PicoContainer parentContainer, Object assemblyScope) {
066    
067            Binding binding = new Binding();
068            if ( parentContainer == null ){
069                parentContainer = new DefaultClassLoadingPicoContainer(getClassLoader(), new DefaultPicoContainer(new Caching(), new EmptyPicoContainer()));
070            }
071            binding.setVariable("parent", parentContainer);
072            binding.setVariable("builder", createNodeBuilder());
073            binding.setVariable("assemblyScope", assemblyScope);
074            handleBinding(binding);
075            return runGroovyScript(binding);
076        }
077    
078        /**
079         * Allows customization of the groovy node builder in descendants.
080         * @return GroovyNodeBuilder
081         */
082        protected GroovyObject createNodeBuilder() {
083            return new GroovyNodeBuilder();
084        }
085    
086        /**
087         * This allows children of this class to add to the default binding.
088         * Might want to add similar or a more generic implementation of this
089         * method to support the other scripting languages.
090         * @param binding the binding
091         */
092        protected void handleBinding(Binding binding) {
093            // does nothing but adds flexibility for children
094        }
095    
096    
097        /**
098         * Parses the groovy script into a class.  We store the Class instead
099         * of the script proper so that it doesn't invoke race conditions on
100         * multiple executions of the script.
101         */
102        private void createGroovyClass() {
103            try {
104                GroovyClassLoader loader = new GroovyClassLoader(getClassLoader());
105                InputStream scriptIs = getScriptInputStream();
106                GroovyCodeSource groovyCodeSource = new GroovyCodeSource(scriptIs,"picocontainer.groovy","groovyGeneratedForPicoContainer");
107                scriptClass = loader.parseClass(groovyCodeSource);
108            } catch (CompilationFailedException e) {
109                throw new GroovyCompilationException("Compilation Failed '" + e.getMessage() + "'", e);
110            } catch (IOException e) {
111                throw new ScriptedPicoContainerMarkupException(e);
112            }
113    
114        }
115    
116        /**
117         * Executes the groovy script with the given binding.
118         * @param binding Binding
119         * @return PicoContainer
120         */
121        private PicoContainer runGroovyScript(Binding binding){
122            Script script = createGroovyScript(binding);
123    
124            Object result = script.run();
125            Object picoVariable;
126            try {
127                picoVariable = binding.getVariable("pico");
128            } catch (MissingPropertyException e) {
129                picoVariable = result;
130            }
131            if (picoVariable == null) {
132                throw new NullPointerException("Groovy Script Variable: pico");
133            }
134    
135            if (picoVariable instanceof PicoContainer) {
136                return (PicoContainer) picoVariable;
137            } else if (picoVariable instanceof ClassLoadingPicoContainer) {
138                return ((ClassLoadingPicoContainer) picoVariable);
139            } else {
140                throw new ScriptedPicoContainerMarkupException("Bad type for pico:" + picoVariable.getClass().getName());
141            }
142    
143        }
144    
145        private Script createGroovyScript(Binding binding) {
146            return  InvokerHelper.createScript(scriptClass, binding);
147        }
148    }