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
038/**
039 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
040 */
041public abstract class Script {
042
043    protected static volatile ScriptEngineManager scripting;
044
045    protected CompiledScript script;
046
047    public volatile long lastModified = -1;
048
049    public static ScriptEngineManager getScripting() {
050        if (scripting == null) {
051            synchronized (Script.class) {
052                if (scripting == null) {
053                    scripting = new ScriptEngineManager();
054                }
055            }
056        }
057        return scripting;
058    }
059
060    public static Script newScript(String location) throws IOException {
061        if (location.indexOf(':') > -1) {
062            return newScript(new URL(location));
063        } else {
064            return new FileScript(location);
065        }
066    }
067
068    public static Script newScript(URL location) throws IOException {
069        String proto = location.getProtocol();
070        if (proto.equals("jar")) {
071            String path = location.getPath();
072            int p = path.indexOf('!');
073            if (p == -1) { // invalid jar URL .. returns a generic URL script
074                return new URLScript(location);
075            }
076            path = path.substring(0, p);
077            if (path.startsWith("file:")) {
078                URI uri;
079                try {
080                    uri = new URI(path);
081                } catch (URISyntaxException e) {
082                    throw new IOException(e);
083                }
084                return new JARFileScript(new File(uri), location);
085            } else { // TODO import query string too?
086                return new JARUrlScript(new URL(path), location);
087            }
088        } else if (proto.equals("file")) {
089            URI uri;
090            try {
091                uri = location.toURI();
092            } catch (URISyntaxException e) {
093                throw new IOException(e);
094            }
095            return new FileScript(new File(uri));
096        } else {
097            return new URLScript(location);
098        }
099    }
100
101    public static Script newScript(File location) {
102        return new FileScript(location);
103    }
104
105    public abstract Reader getReader() throws IOException;
106
107    public abstract Reader getReaderIfModified() throws IOException;
108
109    public abstract String getExtension();
110
111    public abstract String getLocation();
112
113    protected String getExtension(String location) {
114        int p = location.lastIndexOf('.');
115        if (p > -1) {
116            return location.substring(p + 1);
117        }
118        return null;
119    }
120
121    public Object run(Bindings args) throws ScriptException {
122        if (args == null) {
123            args = new SimpleBindings();
124        }
125        ScriptContext ctx = new SimpleScriptContext();
126        ctx.setBindings(args, ScriptContext.ENGINE_SCOPE);
127        return getCompiledScript().eval(ctx);
128    }
129
130    public CompiledScript getCompiledScript() throws ScriptException {
131        try {
132            try (Reader reader = script == null ? getReader() : getReaderIfModified()) {
133                if (reader != null) {
134                    script = compile(reader);
135                }
136                return script;
137            }
138        } catch (IOException e) {
139            throw new ScriptException(e);
140        }
141    }
142
143    public CompiledScript compile(Reader reader) throws ScriptException {
144        ScriptEngine engine = getScripting().getEngineByExtension(getExtension());
145        if (engine == null) {
146            throw new ScriptException("Unknown script type: " + getExtension());
147        }
148        if (engine instanceof Compilable) {
149            Compilable comp = (Compilable) engine;
150            try {
151                try {
152                    return comp.compile(reader);
153                } finally {
154                    reader.close();
155                }
156            } catch (IOException e) {
157                throw new ScriptException(e);
158            }
159        } else {// TODO this will read sources twice the fist time - pass
160                // reader?
161            return new FakeCompiledScript(engine, this);
162        }
163    }
164
165}