    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&oslash;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    }