001/* 002 * (C) Copyright 2016 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 */ 017package org.nuxeo.automation.scripting.internals; 018 019import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.AUTOMATION_SCRIPTING_PRECOMPILE; 020import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.COMPLIANT_JAVA_VERSION_CACHE; 021import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.COMPLIANT_JAVA_VERSION_CLASS_FILTER; 022import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.DEFAULT_PRECOMPILE_STATUS; 023import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.NASHORN_JAVA_VERSION; 024import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.NASHORN_WARN_CACHE; 025import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.NASHORN_WARN_CLASS_FILTER; 026import static org.nuxeo.launcher.config.ConfigurationGenerator.checkJavaVersion; 027 028import java.io.InputStream; 029import java.io.InputStreamReader; 030import java.lang.reflect.InvocationHandler; 031import java.lang.reflect.Method; 032import java.lang.reflect.Proxy; 033import java.util.function.Supplier; 034 035import javax.script.Compilable; 036import javax.script.CompiledScript; 037import javax.script.Invocable; 038import javax.script.ScriptContext; 039import javax.script.ScriptEngine; 040import javax.script.ScriptException; 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.automation.scripting.api.AutomationScriptingService; 044import org.nuxeo.ecm.automation.OperationContext; 045import org.nuxeo.ecm.core.api.CoreSession; 046import org.nuxeo.ecm.core.api.NuxeoException; 047import org.nuxeo.runtime.api.Framework; 048import jdk.nashorn.api.scripting.ClassFilter; 049import jdk.nashorn.api.scripting.NashornScriptEngineFactory; 050import jdk.nashorn.api.scripting.ScriptObjectMirror; 051 052public class AutomationScriptingServiceImpl implements AutomationScriptingService { 053 054 private final Supplier<ScriptEngine> supplier = new Factory().supplier; 055 056 protected AutomationScriptingParamsInjector paramsInjector; 057 058 @Override 059 public Session get(CoreSession session) { 060 return get(new OperationContext(session)); 061 } 062 063 @Override 064 public Session get(OperationContext context) { 065 return new Bridge(context); 066 } 067 068 069 final ScriptEngine engine = supplier.get(); 070 071 class Bridge implements Session { 072 073 final CompiledScript mapperScript = AutomationMapper.compile((Compilable) engine); 074 075 final Compilable compilable = ((Compilable) engine); 076 077 final Invocable invocable = ((Invocable) engine); 078 079 final ScriptContext scriptContext = engine.getContext(); 080 081 final AutomationMapper mapper; 082 083 final ScriptObjectMirror global; 084 085 Bridge(OperationContext operationContext) { 086 mapper = new AutomationMapper(operationContext); 087 try { 088 mapperScript.eval(mapper); 089 } catch (ScriptException cause) { 090 throw new NuxeoException("Cannot execute mapper " + mapperScript, cause); 091 } 092 global = (ScriptObjectMirror) mapper.get("nashorn.global"); 093 scriptContext.setBindings(mapper, ScriptContext.ENGINE_SCOPE); 094 } 095 096 @Override 097 public <T> T handleof(InputStream input, Class<T> typeof) { 098 run(input); 099 T handle = invocable.getInterface(global, typeof); 100 if (handle == null) { 101 throw new NuxeoException("Script doesn't implements " + typeof.getName()); 102 } 103 return typeof.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 104 new Class[] { typeof }, new InvocationHandler() { 105 106 @Override 107 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 108 return mapper.unwrap(method.invoke(handle, mapper.wrap(args[0]), mapper.wrap(args[1]))); 109 } 110 })); 111 } 112 113 @Override 114 public Object run(InputStream input) { 115 try { 116 return mapper.unwrap(engine.eval(new InputStreamReader(input), mapper)); 117 } catch (ScriptException cause) { 118 throw new NuxeoException("Cannot evaluate automation script", cause); 119 } 120 } 121 122 <T> T handleof(Class<T> typeof) { 123 return invocable.getInterface(global, typeof); 124 } 125 126 @Override 127 public <T> T adapt(Class<T> typeof) { 128 if (typeof.isAssignableFrom(engine.getClass())) { 129 return typeof.cast(engine); 130 } 131 if (typeof.isAssignableFrom(AutomationMapper.class)) { 132 return typeof.cast(mapper); 133 } 134 if (typeof.isAssignableFrom(scriptContext.getClass())) { 135 return typeof.cast(scriptContext); 136 } 137 throw new IllegalArgumentException("Cannot adapt scripting context to " + typeof.getName()); 138 } 139 140 @Override 141 public void close() throws Exception { 142 mapper.flush(); 143 } 144 } 145 146 class Factory { 147 148 final NashornScriptEngineFactory nashorn = new NashornScriptEngineFactory(); 149 150 final Supplier<ScriptEngine> supplier = supplier(); 151 152 Supplier<ScriptEngine> supplier() { 153 154 Log log = LogFactory.getLog(AutomationScriptingServiceImpl.class); 155 String version = Framework.getProperty("java.version"); 156 // Check if jdk8 157 if (!checkJavaVersion(version, NASHORN_JAVA_VERSION)) { 158 throw new UnsupportedOperationException(NASHORN_JAVA_VERSION); 159 } 160 // Check if version < jdk8u25 -> no cache. 161 if (!checkJavaVersion(version, COMPLIANT_JAVA_VERSION_CACHE)) { 162 log.warn(NASHORN_WARN_CACHE); 163 return noCache(); 164 } 165 // Check if jdk8u25 <= version < jdk8u40 -> only cache. 166 if (!checkJavaVersion(version, COMPLIANT_JAVA_VERSION_CLASS_FILTER)) { 167 if (Boolean.parseBoolean( 168 Framework.getProperty(AUTOMATION_SCRIPTING_PRECOMPILE, DEFAULT_PRECOMPILE_STATUS))) { 169 log.warn(NASHORN_WARN_CLASS_FILTER); 170 return cache(); 171 } else { 172 log.warn(NASHORN_WARN_CLASS_FILTER); 173 return noCache(); 174 } 175 } 176 // Else if version >= jdk8u40 -> cache + class filter 177 try { 178 if (Boolean.parseBoolean( 179 Framework.getProperty(AUTOMATION_SCRIPTING_PRECOMPILE, DEFAULT_PRECOMPILE_STATUS))) { 180 return cacheAndClassFilter(); 181 } else { 182 return noCacheAndClassFilter(); 183 } 184 } catch (NoClassDefFoundError cause) { 185 log.warn(NASHORN_WARN_CLASS_FILTER); 186 return cache(); 187 } 188 } 189 190 Supplier<ScriptEngine> noCache() { 191 return () -> nashorn.getScriptEngine(new String[] { "-strict" }); 192 } 193 194 Supplier<ScriptEngine> cache() { 195 return () -> nashorn.getScriptEngine( 196 new String[] { "-strict", "--optimistic-types=true", "--persistent-code-cache", "--class-cache-size=50" }, 197 Thread.currentThread().getContextClassLoader()); 198 } 199 200 Supplier<ScriptEngine> cacheAndClassFilter() { 201 return () -> nashorn.getScriptEngine( 202 new String[] { "-strict", "--optimistic-types=true", "--persistent-code-cache", "--class-cache-size=50" }, 203 Thread.currentThread().getContextClassLoader(), new ClassFilter() { 204 205 @Override 206 public boolean exposeToScripts(String className) { 207 return false; 208 } 209 210 }); 211 } 212 213 Supplier<ScriptEngine> noCacheAndClassFilter() { 214 return () -> nashorn.getScriptEngine(new String[] { "-strict" }, 215 Thread.currentThread().getContextClassLoader(), new ClassFilter() { 216 217 @Override 218 public boolean exposeToScripts(String className) { 219 return false; 220 } 221 222 }); 223 }; 224 } 225 226}