/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.scripting.visat; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineManager; import javax.script.SimpleScriptContext; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.net.URL; import java.text.MessageFormat; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Executes all {@link ScriptEngine} code in a single, dedicated thread. */ public class ScriptManager { private final ClassLoader classLoader; private final PrintWriter output; private ScriptEngineManager scriptEngineManager; private ScriptEngine engine; /* * executorService has two roles: * (1) provide a single thread for all script engine calls * (2) create a thread with a dedicated context class loader */ private ExecutorService executorService; public ScriptManager(ClassLoader classLoader, PrintWriter output) { this.classLoader = classLoader; this.output = output; executorService = createExecutorService(); executorService.submit(() -> scriptEngineManager = new ScriptEngineManager(ScriptManager.this.classLoader)); } public ScriptEngineFactory[] getEngineFactories() { List<ScriptEngineFactory> scriptEngineFactoryList = scriptEngineManager.getEngineFactories(); return scriptEngineFactoryList.toArray(new ScriptEngineFactory[scriptEngineFactoryList.size()]); } public ScriptEngine getEngine() { return engine; } public void setEngine(final ScriptEngine engine) { if (this.engine != null && this.engine.getFactory() == engine.getFactory()) { return; } executorService.submit(() -> { ScriptManager.this.engine = engine; configureEngine(); }); } public ScriptEngine getEngineByFactory(final ScriptEngineFactory scriptEngineFactory) { return getEngine(scriptEngineFactory::getScriptEngine); } public ScriptEngine getEngineByMimeType(final String mimeType) { return getEngine(() -> scriptEngineManager.getEngineByMimeType(mimeType)); } public ScriptEngine getEngineByExtension(final String extension) { return getEngine(() -> scriptEngineManager.getEngineByExtension(extension)); } public void execute(final String code, final Observer observer) { executorService.submit(() -> execute0(code, observer)); } public void execute(final URL url, final Observer observer) { executorService.submit(() -> execute0(url, observer)); } private void execute0(String code, Observer observer) { checkEngineSet(); Object object; try { object = engine.eval(code); } catch (Throwable throwable) { observer.onFailure(throwable); return; } observer.onSuccess(object); // Throwables thrown in here shall not be catched! } private void execute0(URL url, Observer observer) { checkEngineSet(); Reader reader = null; Object object; try { reader = new InputStreamReader(url.openStream()); object = engine.eval(reader); } catch (Throwable e) { observer.onFailure(e); return; } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // ignore } } } observer.onSuccess(object); // Throwables thrown in here shall not be catched! } private ScriptEngine getEngine(Callable<ScriptEngine> task) { try { return executorService.submit(task).get(); } catch (Throwable t) { throw new IllegalStateException(t); } } private void configureEngine() { ScriptContext context = new SimpleScriptContext(); context.setWriter(output); context.setErrorWriter(output); engine.setContext(context); engine.put("out", output); engine.put("err", output); output.println(MessageFormat.format("Script language set to {0}.", engine.getFactory().getLanguageName())); final URL url = findInitScript(); if (url != null) { output.println(MessageFormat.format("Loading initialisation script ''{0}''...", url)); execute0(url, new Observer() { @Override public void onSuccess(Object value) { } @Override public void onFailure(Throwable throwable) { output.println("Failed to load initialisation script. " + "BEAM-specific language extensions are disabled."); throwable.printStackTrace(output); } }); output.println("Initialisation script loaded. BEAM-specific language extensions are enabled."); } else { output.println("No initialisation script found. " + "BEAM-specific language extensions are disabled."); } } private URL findInitScript() { String cl = getClass().getSimpleName(); String ln = engine.getFactory().getLanguageName(); URL url = findInitScript(cl + "_" + ln + ".%s"); if (url == null) { return findInitScript(cl + ".%s"); } return null; } private URL findInitScript(String pattern) { for (String extension : engine.getFactory().getExtensions()) { URL resource = getClass().getResource(String.format(pattern, extension)); if (resource != null) { return resource; } } return null; } private void checkEngineSet() { if (engine == null) { throw new IllegalStateException("engine == null"); } } public void reset() { executorService.shutdownNow(); executorService = createExecutorService(); } private ExecutorService createExecutorService() { return Executors.newSingleThreadExecutor(r -> { Thread thread = new Thread(r, "ScriptRunner"); thread.setContextClassLoader(ScriptManager.this.classLoader); return thread; }); } public static interface Observer { void onSuccess(Object value); void onFailure(Throwable throwable); } }