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