001package com.hammurapi.common;
002
003import java.io.ByteArrayInputStream;
004import java.io.IOException;
005import java.io.InputStream;
006import java.io.Reader;
007import java.io.StringWriter;
008import java.net.URL;
009import java.net.URLConnection;
010import java.net.URLStreamHandler;
011import java.util.Arrays;
012import java.util.HashMap;
013import java.util.Map;
014
015import javax.script.Bindings;
016import javax.script.ScriptEngine;
017import javax.script.ScriptEngineManager;
018import javax.script.ScriptException;
019import javax.script.SimpleBindings;
020
021/**
022 * This class handles <code>script</code> protocol/schema and evaluates expressions using Java Scripting framework. 
023 * Expression language extension is passed as authority and expression is passed as path, e.g. <code>script://java/myObject.toString()</code>.
024 * Evaluation result is converted to input stream in the following way: 
025 * a) If it is input stream then it is returned as is. 
026 * b) If it is Reader then it is wrapped into input stream.
027 * c) If it is null, then null is returned.
028 * d) Result is converted to String with toString() method and string bytes are returned as input stream.  
029 * @author Pavel Vlasov
030 *
031 */
032public class ScriptURLStreamHandler extends URLStreamHandler {
033
034        public static final String SCRIPT_PROTOCOL = "script";
035        private Bindings bindings;
036        private ScriptEngineManager scriptEngineManager;        
037        
038        /**
039         * 
040         * @param classLoader ClassLoader to use to load script engines and other classes.
041         * @param bindings Bindings for evaluating script expression.
042         */
043        public ScriptURLStreamHandler(ClassLoader classLoader, Map<String, Object> bindings) {
044                this.bindings = bindings==null ? new SimpleBindings() : new SimpleBindings(bindings);
045                this.scriptEngineManager = new ScriptEngineManager(classLoader);
046        }
047        
048        /**
049         * 
050         * @param bindings Bindings for evaluating script expression.
051         */
052        public ScriptURLStreamHandler(Map<String, Object> bindings) {
053                this.bindings = bindings==null ? new SimpleBindings() : new SimpleBindings(bindings);
054                this.scriptEngineManager = new ScriptEngineManager();
055        }
056        
057        // For testing.
058        public static void main(String[] args) throws Exception {
059                Map<String, Object> env = new HashMap<String, Object>();
060                env.put("z", "366");
061                ScriptURLStreamHandler suh = new ScriptURLStreamHandler(null, env);
062                URL testURL = new URL(null, "script://js/z.length", suh);
063                InputStream is = testURL.openStream();
064                byte[] buf = new byte[40];
065                is.read(buf);
066                System.out.println(Arrays.toString(buf));
067                
068        }
069        @Override
070        protected URLConnection openConnection(final URL u) throws IOException {
071                if (u==null || !SCRIPT_PROTOCOL.equals(u.getProtocol())) {
072                        return null;
073                }
074                return new URLConnection(u) {
075                        
076                        @Override
077                        public void connect() throws IOException {
078                                // Nothing to do - everything is done in "getInputStream()".
079                                
080                        }
081                        
082                        @Override
083                        public InputStream getInputStream() throws IOException {
084                                String authority = u.getHost();
085                                if (authority==null) {
086                                        return null;
087                                }
088                                
089                                ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension(authority);
090                                if (scriptEngine==null) {
091                                        throw new IOException("Cannot create script engine for extension "+authority);
092                                }
093                                
094                                String expr = u.getPath();
095                                if (expr.startsWith("/")) {
096                                        expr = expr.substring(1);
097                                }
098                                try {
099                                        Object res = scriptEngine.eval(expr, bindings);
100                                        if (res==null) {
101                                                return null;
102                                        }
103                                        if (res instanceof InputStream) {
104                                                return (InputStream) res;
105                                        }
106                                        
107                                        if (res instanceof Reader) {
108                                                StringWriter sw = new StringWriter();
109                                                char[] cbuf = new char[4096];
110                                                int l;
111                                                while ((l=((Reader) res).read(cbuf))!=-1) {
112                                                        sw.write(cbuf, 0, l);
113                                                }
114                                                ((Reader) res).close();
115                                                sw.close();
116                                                res = sw.toString();
117                                        }
118                                        
119                                        return new ByteArrayInputStream(res.toString().getBytes());
120                                } catch (ScriptException e) {
121                                        throw new IOException("Cannot evaluate "+expr, e);
122                                }
123                        }
124                        
125                        
126                };
127        }
128        
129}
130
131