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    }