001/* 002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bstefanescu 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.webengine.scripting; 021 022import groovy.lang.GroovyRuntimeException; 023 024import java.io.File; 025import java.io.FileReader; 026import java.io.IOException; 027import java.io.Reader; 028import java.util.Map; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.concurrent.ConcurrentMap; 031 032import javax.script.Compilable; 033import javax.script.CompiledScript; 034import javax.script.ScriptContext; 035import javax.script.ScriptEngine; 036import javax.script.ScriptEngineManager; 037import javax.script.ScriptException; 038import javax.script.SimpleBindings; 039import javax.script.SimpleScriptContext; 040 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.webengine.loader.WebLoader; 044 045/** 046 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 047 */ 048public class Scripting { 049 050 private static final Log log = LogFactory.getLog(Scripting.class); 051 052 private final ConcurrentMap<File, Entry> cache = new ConcurrentHashMap<File, Entry>(); 053 054 // this will be lazy initialized 055 private ScriptEngineManager scriptMgr; 056 057 private final WebLoader loader; 058 059 public Scripting(WebLoader loader) { 060 this.loader = loader; 061 } 062 063 public static CompiledScript compileScript(ScriptEngine engine, File file) throws ScriptException { 064 if (engine instanceof Compilable) { 065 Compilable comp = (Compilable) engine; 066 try { 067 Reader reader = new FileReader(file); 068 try { 069 return comp.compile(reader); 070 } finally { 071 reader.close(); 072 } 073 } catch (IOException e) { 074 throw new ScriptException(e); 075 } 076 } else { 077 return null; 078 } 079 } 080 081 /** 082 * Lazy init scripting manager to avoid loading script engines when no scripting is used. 083 * <p> 084 * Javax Scripting is not used by default in WebWengine, we are using directly the Groovy engine. This also fixes an 085 * annoying pb on Mac in java5 due to AppleScripting which is failing to register. 086 * 087 * @return the scriptMgr 088 */ 089 public ScriptEngineManager getEngineManager() { 090 if (scriptMgr == null) { 091 scriptMgr = new ScriptEngineManager(); 092 } 093 return scriptMgr; 094 } 095 096 public boolean isScript(String ext) { 097 return getEngineManager().getEngineByExtension(ext) != null; 098 } 099 100 public Object runScript(ScriptFile script) throws ScriptException { 101 return runScript(script, null); 102 } 103 104 public Object runScript(ScriptFile script, Map<String, Object> args) throws ScriptException { 105 if (log.isDebugEnabled()) { 106 log.debug("## Running Script: " + script.getFile()); 107 } 108 if ("groovy".equals(script.getExtension())) { 109 try { 110 return loader.getGroovyScripting().eval(script.file, args); 111 } catch (GroovyRuntimeException e) { 112 throw new ScriptException(e); 113 } 114 } else { 115 return _runScript(script, args); 116 } 117 } 118 119 // TODO: add an output stream to use as arg? 120 protected Object _runScript(ScriptFile script, Map<String, Object> args) throws ScriptException { 121 SimpleBindings bindings = new SimpleBindings(); 122 if (args != null) { 123 bindings.putAll(args); 124 } 125 String ext = script.getExtension(); 126 // check for a script engine 127 ScriptEngine engine = getEngineManager().getEngineByExtension(ext); 128 if (engine != null) { 129 ScriptContext ctx = new SimpleScriptContext(); 130 ctx.setBindings(bindings, ScriptContext.ENGINE_SCOPE); 131 CompiledScript comp = getCompiledScript(engine, script.getFile()); // use cache for compiled scripts 132 if (comp != null) { 133 return comp.eval(ctx); 134 } // compilation not supported - eval it on the fly 135 try { 136 Reader reader = new FileReader(script.getFile()); 137 try { // TODO use __result__ to pass return value for engine that doesn't returns like jython 138 Object result = engine.eval(reader, ctx); 139 if (result == null) { 140 result = bindings.get("__result__"); 141 } 142 return result; 143 } finally { 144 reader.close(); 145 } 146 } catch (IOException e) { 147 throw new ScriptException(e); 148 } 149 } 150 return null; 151 } 152 153 public CompiledScript getCompiledScript(ScriptEngine engine, File file) throws ScriptException { 154 Entry entry = cache.get(file); 155 long tm = file.lastModified(); 156 if (entry != null) { 157 if (entry.lastModified < tm) { // recompile 158 entry.script = compileScript(engine, file); 159 entry.lastModified = tm; 160 } 161 return entry.script; 162 } 163 CompiledScript script = compileScript(engine, file); 164 if (script != null) { 165 cache.putIfAbsent(file, new Entry(script, tm)); 166 return script; 167 } 168 return null; 169 } 170 171 class Entry { 172 public CompiledScript script; 173 174 public long lastModified; 175 176 Entry(CompiledScript script, long lastModified) { 177 this.lastModified = lastModified; 178 this.script = script; 179 } 180 } 181 182}