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 Leo Simons * 009 *****************************************************************************/ 010 package org.nanocontainer.script.bsh; 011 012 import bsh.EvalError; 013 import bsh.Interpreter; 014 import org.picocontainer.Parameter; 015 import org.picocontainer.PicoContainer; 016 import org.picocontainer.PicoInitializationException; 017 import org.picocontainer.PicoIntrospectionException; 018 import org.picocontainer.defaults.AbstractComponentAdapter; 019 import org.picocontainer.defaults.UnsatisfiableDependenciesException; 020 021 import java.io.IOException; 022 import java.io.InputStreamReader; 023 import java.io.Reader; 024 import java.net.URL; 025 import java.util.Arrays; 026 import java.util.Collections; 027 028 /** 029 * This adapter relies on <a href="http://beanshell.org/">Bsh</a> for instantiation 030 * (and possibly also initialisation) of component instances. 031 * <p/> 032 * When {@link org.picocontainer.ComponentAdapter#getComponentInstance} is called (by PicoContainer), 033 * the adapter instance will look for a script with the same name as the component implementation 034 * class (but with the .bsh extension). This script must reside in the same folder as the class. 035 * (It's ok to have them both in a jar). 036 * <p/> 037 * The bsh script's only contract is that it will have to instantiate a bsh variable called 038 * "instance". 039 * <p/> 040 * The script will have access to the following variables: 041 * <ul> 042 * <li>adapter - the adapter calling the script</li> 043 * <li>picoContainer - the MutablePicoContainer calling the adapter</li> 044 * <li>componentKey - the component key</li> 045 * <li>componentImplementation - the component implementation</li> 046 * <li>parameters - the ComponentParameters (as a List)</li> 047 * </ul> 048 * @author <a href="mail at leosimons dot com">Leo Simons</a> 049 * @author Aslak Hellesoy 050 * @version $Id: BeanShellComponentAdapter.java 3144 2006-12-26 10:12:19Z mauro $ 051 */ 052 public class BeanShellComponentAdapter extends AbstractComponentAdapter { 053 private final Parameter[] parameters; 054 055 private Object instance = null; 056 057 /** 058 * Classloader to set for the BeanShell interpreter. 059 */ 060 private final ClassLoader classLoader; 061 062 public BeanShellComponentAdapter(final Object componentKey, final Class componentImplementation, final Parameter[] parameters, final ClassLoader classLoader) { 063 super(componentKey, componentImplementation); 064 this.parameters = parameters; 065 this.classLoader = classLoader; 066 } 067 068 public BeanShellComponentAdapter(final Object componentKey, final Class componentImplementation, final Parameter[] parameters) { 069 this(componentKey, componentImplementation, parameters, BeanShellComponentAdapter.class.getClassLoader()); 070 } 071 072 public Object getComponentInstance(PicoContainer pico) 073 throws PicoInitializationException, PicoIntrospectionException { 074 075 if (instance == null) { 076 try { 077 Interpreter i = new Interpreter(); 078 i.setClassLoader(classLoader); 079 i.set("adapter", this); 080 i.set("picoContainer", pico); 081 i.set("componentKey", getComponentKey()); 082 i.set("componentImplementation", getComponentImplementation()); 083 i.set("parameters", parameters != null ? Arrays.asList(parameters) : Collections.EMPTY_LIST); 084 i.eval("import " + getComponentImplementation().getName() + ";"); 085 086 String scriptPath = "/" + getComponentImplementation().getName().replace('.', '/') + ".bsh"; 087 088 // Inside IDEA, this relies on the compilation output path being the same directory as the source path. 089 // kludge - copy ScriptableDemoBean.bsh to the same location in the test output compile class path. 090 // the same problem exists for maven, but some custom jelly script will be able to fix that. 091 URL scriptURL = getComponentImplementation().getResource(scriptPath); 092 if (scriptURL == null) { 093 throw new BeanShellScriptInitializationException("Couldn't load script at path " + scriptPath); 094 } 095 Reader sourceReader = new InputStreamReader(scriptURL.openStream()); 096 i.eval(sourceReader, i.getNameSpace(), scriptURL.toExternalForm()); 097 098 instance = i.get("instance"); 099 if (instance == null) { 100 throw new BeanShellScriptInitializationException("The 'instance' variable was not instantiated"); 101 } 102 } catch (EvalError e) { 103 throw new BeanShellScriptInitializationException(e); 104 } catch (IOException e) { 105 throw new BeanShellScriptInitializationException(e); 106 } 107 } 108 return instance; 109 } 110 111 public void verify(PicoContainer pico) throws UnsatisfiableDependenciesException { 112 } 113 }