001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.ecm.core.event.script;
013
014import java.io.File;
015import java.io.IOException;
016import java.io.Reader;
017import java.net.URI;
018import java.net.URISyntaxException;
019import java.net.URL;
020
021import javax.script.Bindings;
022import javax.script.Compilable;
023import javax.script.CompiledScript;
024import javax.script.ScriptContext;
025import javax.script.ScriptEngine;
026import javax.script.ScriptEngineManager;
027import javax.script.ScriptException;
028import javax.script.SimpleBindings;
029import javax.script.SimpleScriptContext;
030
031import org.nuxeo.common.utils.FileUtils;
032
033/**
034 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
035 */
036public abstract class Script {
037
038    public static boolean trackChanges = true;
039
040    public static ScriptEngineManager scripting;
041
042    public CompiledScript script;
043
044    public long lastModified = -1;
045
046    public static ScriptEngineManager getScripting() {
047        if (scripting == null) {
048            synchronized (Script.class) {
049                scripting = new ScriptEngineManager();
050            }
051        }
052        return scripting;
053    }
054
055    public static Script newScript(String location) throws IOException {
056        if (location.indexOf(':') > -1) {
057            return newScript(new URL(location));
058        } else {
059            return new FileScript(location);
060        }
061    }
062
063    public static Script newScript(URL location) throws IOException {
064        String proto = location.getProtocol();
065        if (proto.equals("jar")) {
066            String path = location.getPath();
067            int p = path.indexOf('!');
068            if (p == -1) { // invalid jar URL .. returns a generic URL script
069                return new URLScript(location);
070            }
071            path = path.substring(0, p);
072            if (path.startsWith("file:")) {
073                URI uri;
074                try {
075                    uri = new URI(path);
076                } catch (URISyntaxException e) {
077                    throw new IOException(e);
078                }
079                return new JARFileScript(new File(uri), location);
080            } else { // TODO import query string too?
081                return new JARUrlScript(new URL(path), location);
082            }
083        } else if (proto.equals("file")) {
084            URI uri;
085            try {
086                uri = location.toURI();
087            } catch (URISyntaxException e) {
088                throw new IOException(e);
089            }
090            return new FileScript(new File(uri));
091        } else {
092            return new URLScript(location);
093        }
094    }
095
096    public static Script newScript(File location) {
097        return new FileScript(location);
098    }
099
100    public abstract Reader getReader() throws IOException;
101
102    public abstract Reader getReaderIfModified() throws IOException;
103
104    public abstract String getExtension();
105
106    public abstract String getLocation();
107
108    protected String getExtension(String location) {
109        int p = location.lastIndexOf('.');
110        if (p > -1) {
111            return location.substring(p + 1);
112        }
113        return null;
114    }
115
116    public Object run(Bindings args) throws ScriptException {
117        if (args == null) {
118            args = new SimpleBindings();
119        }
120        ScriptContext ctx = new SimpleScriptContext();
121        ctx.setBindings(args, ScriptContext.ENGINE_SCOPE);
122        Object result = null;
123        if (!trackChanges && script != null) {
124            result = script.eval(ctx);
125        } else {
126            result = getCompiledScript().eval(ctx);
127        }
128        return result;
129    }
130
131    public CompiledScript getCompiledScript() throws ScriptException {
132        try {
133            Reader reader = script == null ? getReader() : getReaderIfModified();
134            if (reader != null) {
135                script = compile(reader);
136            }
137            return script;
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}