001/* 002 * (C) Copyright 2006-2008 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 * 019 * $Id$ 020 */ 021 022package org.nuxeo.ecm.webengine.scripting; 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 045import groovy.lang.GroovyRuntimeException; 046 047/** 048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 049 */ 050public class Scripting { 051 052 private static final Log log = LogFactory.getLog(Scripting.class); 053 054 private final ConcurrentMap<File, Entry> cache = new ConcurrentHashMap<File, Entry>(); 055 056 // this will be lazy initialized 057 private ScriptEngineManager scriptMgr; 058 059 private final WebLoader loader; 060 061 public Scripting(WebLoader loader) { 062 this.loader = loader; 063 } 064 065 public static CompiledScript compileScript(ScriptEngine engine, File file) throws ScriptException { 066 if (engine instanceof Compilable) { 067 Compilable comp = (Compilable) engine; 068 try { 069 Reader reader = new FileReader(file); 070 try { 071 return comp.compile(reader); 072 } finally { 073 reader.close(); 074 } 075 } catch (IOException e) { 076 throw new ScriptException(e); 077 } 078 } else { 079 return null; 080 } 081 } 082 083 /** 084 * Lazy init scripting manager to avoid loading script engines when no scripting is used. 085 * <p> 086 * Javax Scripting is not used by default in WebWengine, we are using directly the Groovy engine. This also fixes an 087 * annoying pb on Mac in java5 due to AppleScripting which is failing to register. 088 * 089 * @return the scriptMgr 090 */ 091 public ScriptEngineManager getEngineManager() { 092 if (scriptMgr == null) { 093 scriptMgr = new ScriptEngineManager(); 094 } 095 return scriptMgr; 096 } 097 098 public boolean isScript(String ext) { 099 return getEngineManager().getEngineByExtension(ext) != null; 100 } 101 102 public Object runScript(ScriptFile script) throws ScriptException { 103 return runScript(script, null); 104 } 105 106 public Object runScript(ScriptFile script, Map<String, Object> args) throws ScriptException { 107 if (log.isDebugEnabled()) { 108 log.debug("## Running Script: " + script.getFile()); 109 } 110 if ("groovy".equals(script.getExtension())) { 111 try { 112 return loader.getGroovyScripting().eval(script.file, args); 113 } catch (GroovyRuntimeException e) { 114 throw new ScriptException(e); 115 } 116 } else { 117 return _runScript(script, args); 118 } 119 } 120 121 // TODO: add an output stream to use as arg? 122 protected Object _runScript(ScriptFile script, Map<String, Object> args) throws ScriptException { 123 SimpleBindings bindings = new SimpleBindings(); 124 if (args != null) { 125 bindings.putAll(args); 126 } 127 String ext = script.getExtension(); 128 // check for a script engine 129 ScriptEngine engine = getEngineManager().getEngineByExtension(ext); 130 if (engine != null) { 131 ScriptContext ctx = new SimpleScriptContext(); 132 ctx.setBindings(bindings, ScriptContext.ENGINE_SCOPE); 133 CompiledScript comp = getCompiledScript(engine, script.getFile()); // use cache for compiled scripts 134 if (comp != null) { 135 return comp.eval(ctx); 136 } // compilation not supported - eval it on the fly 137 try { 138 Reader reader = new FileReader(script.getFile()); 139 try { // TODO use __result__ to pass return value for engine that doesn't returns like jython 140 Object result = engine.eval(reader, ctx); 141 if (result == null) { 142 result = bindings.get("__result__"); 143 } 144 return result; 145 } finally { 146 reader.close(); 147 } 148 } catch (IOException e) { 149 throw new ScriptException(e); 150 } 151 } 152 return null; 153 } 154 155 public CompiledScript getCompiledScript(ScriptEngine engine, File file) throws ScriptException { 156 Entry entry = cache.get(file); 157 long tm = file.lastModified(); 158 if (entry != null) { 159 if (entry.lastModified < tm) { // recompile 160 entry.script = compileScript(engine, file); 161 entry.lastModified = tm; 162 } 163 return entry.script; 164 } 165 CompiledScript script = compileScript(engine, file); 166 if (script != null) { 167 cache.putIfAbsent(file, new Entry(script, tm)); 168 return script; 169 } 170 return null; 171 } 172 173 class Entry { 174 public CompiledScript script; 175 176 public long lastModified; 177 178 Entry(CompiledScript script, long lastModified) { 179 this.lastModified = lastModified; 180 this.script = script; 181 } 182 } 183 184}