Package org.apache.tuscany.sca.implementation.script.engines

Source Code of org.apache.tuscany.sca.implementation.script.engines.TuscanyJRubyScriptEngine$WriterOutputStream

/*
* Copyright (c) 2006, Sun Microsystems, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*  - Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
*
*  - Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
*  - Neither the name of the Sun Microsystems, Inc. nor the names of
*    contributors may be used to endorse or promote products derived from this
*    software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package org.apache.tuscany.sca.implementation.script.engines;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import org.jruby.Ruby;
import org.jruby.RubyException;
import org.jruby.RubyIO;
import org.jruby.RubyObject;
import org.jruby.ast.Node;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.GlobalVariable;
import org.jruby.internal.runtime.GlobalVariables;
import org.jruby.internal.runtime.ReadonlyAccessor;
import org.jruby.internal.runtime.ValueAccessor;
import org.jruby.javasupport.Java;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.javasupport.JavaObject;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.IAccessor;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.KCode;

import com.sun.script.jruby.JRubyScriptEngineFactory;

/**
* This class is a copy of the class com.sun.script.ruby.JRubyScriptEngine with some minor modifications
* to work around problems with Tuscany setting SCA properties and references as global variable in JRuby
* Should only need it temporarily till a new BSF release fixes it.
*
* @version $Rev$ $Date$
*/
@SuppressWarnings("unchecked")
public class TuscanyJRubyScriptEngine extends AbstractScriptEngine
        implements Compilable, Invocable {

    // my factory, may be null
    private ScriptEngineFactory factory;
    private Ruby runtime;
  
    public TuscanyJRubyScriptEngine() {
        // Allow privileged access to ready properties. Requires PropertyPermission in security
        // policy.
        String rubyPath = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("com.sun.script.jruby.loadpath");
            }
        });
        init(rubyPath);
    }

    public TuscanyJRubyScriptEngine(String loadPath) {
        init(loadPath);
    }

    // my implementation for CompiledScript
    private class JRubyCompiledScript extends CompiledScript {
        // my compiled code
        private Node node;    

        JRubyCompiledScript (Node node) {
            this.node = node;
        }

        public ScriptEngine getEngine() {
            return TuscanyJRubyScriptEngine.this;
        }

        public Object eval(ScriptContext ctx) throws ScriptException {
            return evalNode(node, ctx);
        }
    }

    // Compilable methods
    public CompiledScript compile(String script)
                                  throws ScriptException
        Node node = compileScript(script, context);
        return new JRubyCompiledScript(node);
    }

    public CompiledScript compile (Reader reader)
                                  throws ScriptException
        Node node = compileScript(reader, context);
        return new JRubyCompiledScript(node);
    }

    // Invocable methods
    public Object invokeFunction(String name, Object... args)
                         throws ScriptException, NoSuchMethodException {      
        return invokeImpl(null, name, args, Object.class);
    }

    public Object invokeMethod(Object obj, String name, Object... args)
                         throws ScriptException, NoSuchMethodException {      
        if (obj == null) {
            throw new IllegalArgumentException("script object is null");
        }
        return invokeImpl(obj, name, args, Object.class);
    }

    public Object getInterface(Object obj, Class clazz) {
        if (obj == null) {
            throw new IllegalArgumentException("script object is null");
        }
        return makeInterface(obj, clazz);
    }

    public Object getInterface(Class clazz) {
        return makeInterface(null, clazz);
    }

    private <T> T makeInterface(Object obj, Class<T> clazz) {
        if (clazz == null || !clazz.isInterface()) {
            throw new IllegalArgumentException("interface Class expected");
        }
        final Object thiz = obj;
        return (T) Proxy.newProxyInstance(
              clazz.getClassLoader(),
              new Class[] { clazz },
              new InvocationHandler() {
                  public Object invoke(Object proxy, Method m, Object[] args)
                                       throws Throwable {
                      return invokeImpl(thiz, m.getName(),
                                        args, m.getReturnType());
                  }
              });
    }

    // ScriptEngine methods
    public synchronized Object eval(String str, ScriptContext ctx)
                       throws ScriptException {
        Node node = compileScript(str, ctx);
        return evalNode(node, ctx);
    }

    public synchronized Object eval(Reader reader, ScriptContext ctx)
                       throws ScriptException {
        Node node = compileScript(reader, ctx);
        return evalNode(node, ctx);
    }

    public ScriptEngineFactory getFactory() {
        synchronized (this) {
            if (factory == null) {
                factory = new JRubyScriptEngineFactory();
            }
        }
        return factory;
    }

    public Bindings createBindings() {
        return new SimpleBindings();
    }

    // package-private methods
    void setFactory(ScriptEngineFactory factory) {
        this.factory = factory;
    }

    // internals only below this point   

    private Object rubyToJava(IRubyObject value) {
        return rubyToJava(value, Object.class);
    }

    private Object rubyToJava(IRubyObject value, Class type) {
        return JavaUtil.convertArgument(
                runtime,
                Java.ruby_to_java(value, value, Block.NULL_BLOCK),
                type);
    }

    private IRubyObject javaToRuby(Object value) {
        if (value instanceof IRubyObject) {
            return (IRubyObject) value;
        }
        IRubyObject result = JavaUtil.convertJavaToRuby(runtime, value);
        if (result instanceof JavaObject) {
            return runtime.getModule("JavaUtilities").callMethod(runtime.getCurrentContext(), "wrap", result);
        }
        return result;
    }  

    private synchronized Node compileScript(String script, ScriptContext ctx)
                                 throws ScriptException {
        GlobalVariables oldGlobals = runtime.getGlobalVariables()
        try {
            setErrorWriter(ctx.getErrorWriter());
            setGlobalVariables(ctx);
            String filename = (String) ctx.getAttribute(ScriptEngine.FILENAME);
            if (filename == null) {
                filename = "<unknown>";
            }           
            return runtime.parseEval(script, filename, null, 0);
        } catch (RaiseException e) {
            RubyException re =  e.getException();
            runtime.printError(re);
            throw new ScriptException(e);
        } catch (Exception e) {
            throw new ScriptException(e);
        } finally {
            if (oldGlobals != null) {
                setGlobalVariables(oldGlobals);
            }
        }
    }

    private synchronized Node compileScript(Reader reader, ScriptContext ctx)
                                 throws ScriptException {
        GlobalVariables oldGlobals = runtime.getGlobalVariables()
        try {
            setErrorWriter(ctx.getErrorWriter());
            setGlobalVariables(ctx);
            String filename = (String) ctx.getAttribute(ScriptEngine.FILENAME);
            if (filename == null) {
                filename = "<unknown>";
                String script = getRubyScript(reader);
                return runtime.parseEval(script, filename, null, 0);
            }
            InputStream inputStream = getRubyReader(filename);
            return runtime.parseFile(inputStream, filename, null);
        } catch (RaiseException e) {
            RubyException re =  e.getException();
            runtime.printError(re);
            throw new ScriptException(e);
        } catch (Exception exp) {
            throw new ScriptException(exp);
        } finally {
            if (oldGlobals != null) {
                setGlobalVariables(oldGlobals);
            }
        }
    }

    private String getRubyScript(Reader reader) throws IOException {
        StringBuffer sb = new StringBuffer();
        char[] cbuf;
        while (true) {
            cbuf = new char[8*1024];
            int chars = reader.read(cbuf, 0, cbuf.length);
            if (chars < 0) {
                break;
            }
            sb.append(cbuf, 0, chars);
        }
        cbuf = null;
        return (new String(sb)).trim();
    }
   
    private InputStream getRubyReader(String filename) throws FileNotFoundException {
        File file = new File(filename);
        return new FileInputStream(file);
    }

    private void setGlobalVariables(final ScriptContext ctx) {
        ctx.setAttribute("context", ctx, ScriptContext.ENGINE_SCOPE);
        setGlobalVariables(new GlobalVariables(runtime) {
                GlobalVariables parent = runtime.getGlobalVariables();

            @Override
                public void define(String name, IAccessor accessor) {
                    assert name != null;
                    assert accessor != null;
                    assert name.startsWith("$");
                    synchronized (ctx) {
                        Bindings engineScope = ctx.getBindings(ScriptContext.ENGINE_SCOPE);                 
                        engineScope.put(name, new GlobalVariable(accessor));
                    }
                }

            @Override
                public void defineReadonly(String name, IAccessor accessor) {
                    assert name != null;
                    assert accessor != null;
                    assert name.startsWith("$");
                    synchronized (ctx) {
                        Bindings engineScope = ctx.getBindings(ScriptContext.ENGINE_SCOPE);
                        engineScope.put(name, new GlobalVariable(new
                                             ReadonlyAccessor(name, accessor)));
                    }
                }

            @Override
                public boolean isDefined(String name) {
                    assert name != null;
                    assert name.startsWith("$");
                    synchronized (ctx) {
                        String modifiedName = name.substring(1);
                        boolean defined = ctx.getAttributesScope(modifiedName) != -1;
                        return defined ? true : parent.isDefined(name);
                    }
                }

            @Override
                public void alias(String name, String oldName) {
                    assert name != null;
                    assert oldName != null;
                    assert name.startsWith("$");
                    assert oldName.startsWith("$");

                    if (runtime.getSafeLevel() >= 4) {
                        throw runtime.newSecurityError("Insecure: can't alias global variable");
                    }

                    synchronized (ctx) {
                        int scope = ctx.getAttributesScope(name);
                        if (scope == -1) {
                            scope = ScriptContext.ENGINE_SCOPE;
                        }

                        IRubyObject value = get(oldName);
                        ctx.setAttribute(name, rubyToJava(value), scope);
                    }
                }

            @Override
                public IRubyObject get(String name) {
                    assert name != null;
                    assert name.startsWith("$");

                    synchronized (ctx) {
                        // skip '$' and try
                        String modifiedName = name.substring(1);
                        int scope = ctx.getAttributesScope(modifiedName);
                        if (scope == -1) {
                            return parent.get(name);
                        }

                        Object obj = ctx.getAttribute(modifiedName, scope);
                        if (obj instanceof IAccessor) {
                            return ((IAccessor)obj).getValue();
                        } else {
                            return javaToRuby(obj);
                        }
                    }                   
                }

            @Override
                public IRubyObject set(String name, IRubyObject value) {
                    assert name != null;
                    assert name.startsWith("$");

                    if (runtime.getSafeLevel() >= 4) {
                        throw runtime.newSecurityError("Insecure: can't change global variable value");
                    }

                    synchronized (ctx) {
                        // skip '$' and try
                        String modifiedName = name.substring(1);
                        int scope = ctx.getAttributesScope(modifiedName);
                        if (scope == -1) {
                            scope = ScriptContext.ENGINE_SCOPE;
                        }
                        IRubyObject oldValue = get(name);
                        Object obj = ctx.getAttribute(modifiedName, scope);
                        if (obj instanceof IAccessor) {
                            ((IAccessor)obj).setValue(value);
                        } else {                       
                            ctx.setAttribute(modifiedName, rubyToJava(value), scope);
                            if ("KCODE".equals(modifiedName)) {
                                setKCode((String)rubyToJava(value));
                            } else if ("stdout".equals(modifiedName)) {
                                equalOutputs((RubyObject)value);
                            }
                        }
                        return oldValue;
                    }
                }

            @Override
                public Set<String> getNames() {                   
                    HashSet set = new HashSet();
                    synchronized (ctx) {
                        for (Object scope : ctx.getScopes()) {
                            Bindings b = ctx.getBindings((Integer)scope);
                            if (b != null) {
                                for (Object key: b.keySet()) {
                                    set.add(key);
                                }
                            }
                        }
                    }
                    for (Iterator<String> names = parent.getNames().iterator(); names.hasNext();) {
                        set.add(names.next());
                    }
                    return Collections.unmodifiableSet(set);
                }

            @Override
                public IRubyObject getDefaultSeparator() {
                    return parent.getDefaultSeparator();
                }
            });
    }

    private void setGlobalVariables(GlobalVariables globals) {
        runtime.setGlobalVariables(globals);
    }

    private synchronized Object evalNode(Node node, ScriptContext ctx)
                            throws ScriptException {
        GlobalVariables oldGlobals = runtime.getGlobalVariables();
        try {
            setWriterOutputStream(ctx.getWriter());
            setErrorWriter(ctx.getErrorWriter());
            setGlobalVariables(ctx);
            return rubyToJava(runtime.runNormally(node, false));
        } catch (Exception exp) {
            throw new ScriptException(exp);
        } finally {
            try {
                JavaEmbedUtils.terminate(runtime);
            } catch (RaiseException e) {
                RubyException re =  e.getException();
                runtime.printError(re);
                if (!runtime.fastGetClass("SystemExit").isInstance(re)) {
                    throw new ScriptException(e);
                }
            } finally {
                if (oldGlobals != null) {
                    setGlobalVariables(oldGlobals);
                }
            }
        }
    }

    private void init(final String loadPath) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                runtime = Ruby.newInstance();
                IAccessor d = new ValueAccessor(runtime.newString("<script>"));
                runtime.getGlobalVariables().define("$PROGRAM_NAME", d);
                runtime.getGlobalVariables().define("$0", d);
                String path = loadPath;
                if (path == null) {
                    path = System.getProperty("java.class.path");
                }
                List list = Arrays.asList(path.split(File.pathSeparator));
                runtime.getLoadService().init(list);
                runtime.getLoadService().require("java");
                return null;
            }
        });
    }

    private synchronized Object invokeImpl(final Object obj, String method,
                        Object[] args, Class returnType)
                        throws ScriptException {
        if (method == null) {
            throw new NullPointerException("method name is null");
        }
        GlobalVariables oldGlobals = runtime.getGlobalVariables();
        try {
            setWriterOutputStream(context.getWriter());
            setErrorWriter(context.getErrorWriter());
            setGlobalVariables(context);          
            IRubyObject rubyRecv = obj != null ?
                  JavaUtil.convertJavaToRuby(runtime, obj) : runtime.getTopSelf();
           
            IRubyObject result;
            if (args != null && args.length > 0) {
                IRubyObject[] rubyArgs = JavaUtil.convertJavaArrayToRuby(runtime, args);
                // Create Ruby proxies for any input arguments that are not primitives.
                IRubyObject javaUtilities = runtime.getObject().getConstant("JavaUtilities");
                for (int i = 0; i < rubyArgs.length; i++) {
                    IRubyObject tmp = rubyArgs[i];
                    if (tmp instanceof JavaObject) {
                        rubyArgs[i] = javaUtilities.callMethod(runtime.getCurrentContext(), "wrap", tmp);
                    }
                }
                result = rubyRecv.callMethod(runtime.getCurrentContext(), method, rubyArgs);
            } else {
                result = rubyRecv.callMethod(runtime.getCurrentContext(), method);
            }  
            return rubyToJava(result, returnType);
        } catch (Exception exp) {
            throw new ScriptException(exp);
        } finally {
            try {
                JavaEmbedUtils.terminate(runtime);
            } catch (RaiseException e) {
                RubyException re =  e.getException();
                runtime.printError(re);
                if (!runtime.fastGetClass("SystemExit").isInstance(re)) {
                    throw new ScriptException(e);
                }
            } finally {
                if (oldGlobals != null) {
                    setGlobalVariables(oldGlobals);
                }
            }
        }
    }
   
    private void setKCode(String encoding) {
        KCode kcode = KCode.create(runtime, encoding);
        runtime.setKCode(kcode);
    }
   
    private void equalOutputs(RubyObject value) {
        runtime.getGlobalVariables().set("$>", value);
        runtime.getGlobalVariables().set("$defout", value);
    }
   
    private void setWriterOutputStream(Writer writer) {
        try {
            RubyIO dummy_io =
                new RubyIO(runtime, new PrintStream(new WriterOutputStream(new StringWriter())));
            runtime.getGlobalVariables().set("$stderr", dummy_io); //discard unwanted warnings
            RubyIO io =
                new RubyIO(runtime, new PrintStream(new WriterOutputStream(writer)));
            io.getOpenFile().getMainStream().setSync(true);
            runtime.defineGlobalConstant("STDOUT", io);
            runtime.getGlobalVariables().set("$>", io);
            runtime.getGlobalVariables().set("$stdout", io);
            runtime.getGlobalVariables().set("$defout", io);
        } catch (UnsupportedEncodingException exp) {
            throw new IllegalArgumentException(exp);
        }
    }
   
    private void setErrorWriter(Writer writer) {
        try {
            RubyIO dummy_io =
                new RubyIO(runtime, new PrintStream(new WriterOutputStream(new StringWriter())));
            runtime.getGlobalVariables().set("$stderr", dummy_io); //discard unwanted warnings
            RubyIO io =
                new RubyIO(runtime, new PrintStream(new WriterOutputStream(writer)));
            io.getOpenFile().getMainStream().setSync(true);
            runtime.defineGlobalConstant("STDERR", io);
            runtime.getGlobalVariables().set("$stderr", io);
            runtime.getGlobalVariables().set("$deferr", io);
        } catch (UnsupportedEncodingException exp) {
            throw new IllegalArgumentException(exp);
        }
    }

    private String getEncoding() {
        String enc = System.getProperty("sun.jnu.encoding");
        if (enc != null) {
            return enc;
        }
        return ((enc = System.getProperty("file.encoding")) == null) ? "UTF-8" : enc;
    }
   
    private class WriterOutputStream extends OutputStream {

        private Writer writer;
        private CharsetDecoder decoder;
       
        private WriterOutputStream(Writer writer) throws UnsupportedEncodingException {
            this(writer, getEncoding());
        }
       
        private WriterOutputStream(Writer writer, String enc) throws UnsupportedEncodingException {
            this.writer = writer;
            if (enc == null) {
                throw new UnsupportedEncodingException("encoding is " + enc);
            }
            try {
                decoder = Charset.forName(enc).newDecoder();
            } catch (Exception e) {
                throw new UnsupportedEncodingException("Unsupported: " + enc);
            }
            decoder.onMalformedInput(CodingErrorAction.REPLACE);
            decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
        }
       
        @Override
        public void close() throws IOException {
            synchronized(writer) {
                decoder = null;
                writer.close();
            }
        }

        @Override
        public void flush() throws IOException {
            synchronized(writer) {
                writer.flush();
            }
        }
       
        @Override
        public void write(int b) throws IOException {
            byte[] buffer = new byte[1];
            write(buffer, 0, 1);
        }
       
        @Override
        public void write(byte[] buffer) throws IOException {
            write(buffer, 0, buffer.length);
        }

        @Override
        public void write(byte[] buffer, int offset, int length) throws IOException {
            synchronized(writer) {
                if (offset < 0 || offset > buffer.length - length || length < 0) {
                    throw new IndexOutOfBoundsException();
                }
                if (length == 0) {
                    return;
                }
                ByteBuffer bytes = ByteBuffer.wrap(buffer, offset, length);
                CharBuffer chars = CharBuffer.allocate(length);
                convert(bytes, chars);
                char[] cbuf = new char[chars.length()];
                chars.get(cbuf, 0, chars.length());
                writer.write(cbuf);
                writer.flush();
            }
        }

        private void convert(ByteBuffer bytes, CharBuffer chars) throws IOException {
            decoder.reset();
            chars.clear();
            CoderResult result = decoder.decode(bytes, chars, true);
            if (result.isError() || result.isOverflow()) {
                throw new IOException(result.toString());
            } else if (result.isUnderflow()) {
                chars.flip();
            }
        }
    }
}
TOP

Related Classes of org.apache.tuscany.sca.implementation.script.engines.TuscanyJRubyScriptEngine$WriterOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.