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 }