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 }