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