001 /***************************************************************************** 002 * Copyright (C) PicoContainer 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 * Original code by Nick Sieger * 008 *****************************************************************************/ 009 010 package org.picocontainer.script.jruby; 011 012 import java.io.IOException; 013 import java.io.Reader; 014 import java.io.StringWriter; 015 import java.util.Collections; 016 017 import org.jruby.Ruby; 018 import org.jruby.RubyInstanceConfig; 019 import org.jruby.exceptions.RaiseException; 020 import org.jruby.javasupport.JavaEmbedUtils; 021 import org.jruby.runtime.builtin.IRubyObject; 022 import org.picocontainer.MutablePicoContainer; 023 import org.picocontainer.PicoCompositionException; 024 import org.picocontainer.PicoContainer; 025 import org.picocontainer.DefaultPicoContainer; 026 import org.picocontainer.containers.EmptyPicoContainer; 027 import org.picocontainer.classname.ClassLoadingPicoContainer; 028 import org.picocontainer.classname.DefaultClassLoadingPicoContainer; 029 import org.picocontainer.script.LifecycleMode; 030 import org.picocontainer.script.ScriptedPicoContainerMarkupException; 031 import org.picocontainer.script.ScriptedContainerBuilder; 032 import org.picocontainer.behaviors.Caching; 033 034 /** 035 * The script uses the {@code scriptedcontainer.rb} script to create an instance of 036 * {@link PicoContainer}. There are implicit variables named "$parent" and 037 * "$assembly_scope". 038 * 039 * @author Nick Sieger 040 */ 041 public final class JRubyContainerBuilder extends ScriptedContainerBuilder { 042 public static final String MARKUP_EXCEPTION_PREFIX = "scriptedbuilder: "; 043 044 private final String script; 045 046 public JRubyContainerBuilder(Reader script, ClassLoader classLoader) { 047 this(script,classLoader, LifecycleMode.AUTO_LIFECYCLE); 048 } 049 050 051 public JRubyContainerBuilder(Reader script, ClassLoader classLoader, LifecycleMode lifecycle) { 052 super(script, classLoader, lifecycle); 053 this.script = toString(script); 054 } 055 056 private String toString(Reader script) { 057 int charsRead; 058 char[] chars = new char[1024]; 059 StringWriter writer = new StringWriter(); 060 try { 061 while ((charsRead = script.read(chars)) != -1) { 062 writer.write(chars, 0, charsRead); 063 } 064 } catch (IOException e) { 065 throw new RuntimeException("unable to read script from reader", e); 066 } 067 return writer.toString(); 068 } 069 070 /** 071 * {@inheritDoc} 072 * <p>Latest method of invoking jruby script have been adapted from <a 073 * href="http://wiki.jruby.org/wiki/Java_Integration" title="Click to visit JRuby Wiki"> 074 * JRuby wiki:</a></p> 075 * @todo create a way to prevent initialization and shutdown with each script invocation. 076 */ 077 protected PicoContainer createContainerFromScript(PicoContainer parentContainer, Object assemblyScope) { 078 if ( parentContainer == null ){ 079 parentContainer = new DefaultClassLoadingPicoContainer(getClassLoader(), new DefaultPicoContainer(new Caching(), new EmptyPicoContainer())); 080 } 081 082 if (! (parentContainer instanceof ClassLoadingPicoContainer)) { 083 if (parentContainer instanceof MutablePicoContainer) { 084 parentContainer = new DefaultClassLoadingPicoContainer(getClassLoader(), (MutablePicoContainer)parentContainer); 085 } else { 086 //We want this last because it will never propagate parent behaviors 087 parentContainer = new DefaultClassLoadingPicoContainer(getClassLoader(), new DefaultPicoContainer(new Caching(), parentContainer)); 088 } 089 } 090 091 092 093 RubyInstanceConfig rubyConfig = new RubyInstanceConfig(); 094 rubyConfig.setLoader(this.getClassLoader()); 095 Ruby ruby = JavaEmbedUtils.initialize(Collections.EMPTY_LIST, rubyConfig); 096 ruby.getLoadService().require("org/picocontainer/script/jruby/scriptedbuilder"); 097 ruby.defineReadonlyVariable("$parent", JavaEmbedUtils.javaToRuby(ruby, parentContainer)); 098 ruby.defineReadonlyVariable("$assembly_scope", JavaEmbedUtils.javaToRuby(ruby, assemblyScope)); 099 100 101 try { 102 103 //IRubyObject result = ruby.executeScript(script); 104 IRubyObject result = JavaEmbedUtils.newRuntimeAdapter().eval(ruby, script); 105 return (PicoContainer) JavaEmbedUtils.rubyToJava(ruby, result, PicoContainer.class); 106 } catch (RaiseException re) { 107 if (re.getCause() instanceof ScriptedPicoContainerMarkupException) { 108 throw (ScriptedPicoContainerMarkupException) re.getCause(); 109 } 110 Object errorMessage = JavaEmbedUtils.rubyToJava(ruby, re.getException().message, String.class); 111 if (errorMessage instanceof String) { 112 String message = (String) JavaEmbedUtils.rubyToJava(ruby, re.getException().message, String.class); 113 if (message.startsWith(MARKUP_EXCEPTION_PREFIX)) { 114 throw new ScriptedPicoContainerMarkupException(message.substring(MARKUP_EXCEPTION_PREFIX.length())); 115 } else { 116 throw new PicoCompositionException(message, re); 117 } 118 } else { 119 throw new PicoCompositionException(errorMessage.toString(), re); 120 } 121 } finally { 122 JavaEmbedUtils.terminate(ruby); 123 } 124 } 125 }