001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.ecm.core.event.script;
020
021import java.io.File;
022import java.io.IOException;
023import java.io.Reader;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.net.URL;
027
028import javax.script.Bindings;
029import javax.script.Compilable;
030import javax.script.CompiledScript;
031import javax.script.ScriptContext;
032import javax.script.ScriptEngine;
033import javax.script.ScriptEngineManager;
034import javax.script.ScriptException;
035import javax.script.SimpleBindings;
036import javax.script.SimpleScriptContext;
037
038import org.nuxeo.common.utils.FileUtils;
039
040/**
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public abstract class Script {
044
045    public static boolean trackChanges = true;
046
047    public static ScriptEngineManager scripting;
048
049    public CompiledScript script;
050
051    public long lastModified = -1;
052
053    public static ScriptEngineManager getScripting() {
054        if (scripting == null) {
055            synchronized (Script.class) {
056                scripting = new ScriptEngineManager();
057            }
058        }
059        return scripting;
060    }
061
062    public static Script newScript(String location) throws IOException {
063        if (location.indexOf(':') > -1) {
064            return newScript(new URL(location));
065        } else {
066            return new FileScript(location);
067        }
068    }
069
070    public static Script newScript(URL location) throws IOException {
071        String proto = location.getProtocol();
072        if (proto.equals("jar")) {
073            String path = location.getPath();
074            int p = path.indexOf('!');
075            if (p == -1) { // invalid jar URL .. returns a generic URL script
076                return new URLScript(location);
077            }
078            path = path.substring(0, p);
079            if (path.startsWith("file:")) {
080                URI uri;
081                try {
082                    uri = new URI(path);
083                } catch (URISyntaxException e) {
084                    throw new IOException(e);
085                }
086                return new JARFileScript(new File(uri), location);
087            } else { // TODO import query string too?
088                return new JARUrlScript(new URL(path), location);
089            }
090        } else if (proto.equals("file")) {
091            URI uri;
092            try {
093                uri = location.toURI();
094            } catch (URISyntaxException e) {
095                throw new IOException(e);
096            }
097            return new FileScript(new File(uri));
098        } else {
099            return new URLScript(location);
100        }
101    }
102
103    public static Script newScript(File location) {
104        return new FileScript(location);
105    }
106
107    public abstract Reader getReader() throws IOException;
108
109    public abstract Reader getReaderIfModified() throws IOException;
110
111    public abstract String getExtension();
112
113    public abstract String getLocation();
114
115    protected String getExtension(String location) {
116        int p = location.lastIndexOf('.');
117        if (p > -1) {
118            return location.substring(p + 1);
119        }
120        return null;
121    }
122
123    public Object run(Bindings args) throws ScriptException {
124        if (args == null) {
125            args = new SimpleBindings();
126        }
127        ScriptContext ctx = new SimpleScriptContext();
128        ctx.setBindings(args, ScriptContext.ENGINE_SCOPE);
129        Object result = null;
130        if (!trackChanges && script != null) {
131            result = script.eval(ctx);
132        } else {
133            result = getCompiledScript().eval(ctx);
134        }
135        return result;
136    }
137
138    public CompiledScript getCompiledScript() throws ScriptException {
139        try {
140            Reader reader = script == null ? getReader() : getReaderIfModified();
141            if (reader != null) {
142                script = compile(reader);
143            }
144            return script;
145        } catch (IOException e) {
146            throw new ScriptException(e);
147        }
148    }
149
150    public CompiledScript compile(Reader reader) throws ScriptException {
151        ScriptEngine engine = getScripting().getEngineByExtension(getExtension());
152        if (engine == null) {
153            throw new ScriptException("Unknown script type: " + getExtension());
154        }
155        if (engine instanceof Compilable) {
156            Compilable comp = (Compilable) engine;
157            try {
158                try {
159                    return comp.compile(reader);
160                } finally {
161                    reader.close();
162                }
163            } catch (IOException e) {
164                throw new ScriptException(e);
165            }
166        } else {// TODO this will read sources twice the fist time - pass
167                // reader?
168            return new FakeCompiledScript(engine, this);
169        }
170    }
171
172}