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.HashSet; 034import java.util.Set; 035 036import javax.script.Compilable; 037import javax.script.CompiledScript; 038import javax.script.Invocable; 039import javax.script.ScriptContext; 040import javax.script.ScriptEngine; 041import javax.script.ScriptException; 042import org.apache.commons.logging.Log; 043import org.apache.commons.logging.LogFactory; 044import org.nuxeo.automation.scripting.api.AutomationScriptingService; 045import org.nuxeo.ecm.automation.OperationContext; 046import org.nuxeo.ecm.core.api.CoreSession; 047import org.nuxeo.ecm.core.api.NuxeoException; 048import org.nuxeo.runtime.api.Framework; 049import jdk.nashorn.api.scripting.ClassFilter; 050import jdk.nashorn.api.scripting.NashornScriptEngineFactory; 051import jdk.nashorn.api.scripting.ScriptObjectMirror; 052 053public class AutomationScriptingServiceImpl implements AutomationScriptingService { 054 055 private static final Log log = LogFactory.getLog(AutomationScriptingServiceImpl.class); 056 057 protected final ScriptEngine engine = getScriptEngine(); 058 059 protected AutomationScriptingParamsInjector paramsInjector; 060 061 // updated in-place only by extension points, so no concurrency issues 062 protected Set<String> allowedClassNames = new HashSet<>(); 063 064 @Override 065 public Session get(CoreSession session) { 066 return get(new OperationContext(session)); 067 } 068 069 @Override 070 public Session get(OperationContext context) { 071 return new Bridge(context); 072 } 073 074 class Bridge implements Session { 075 076 final CompiledScript mapperScript = AutomationMapper.compile((Compilable) engine); 077 078 final Compilable compilable = ((Compilable) engine); 079 080 final Invocable invocable = ((Invocable) engine); 081 082 final ScriptContext scriptContext = engine.getContext(); 083 084 final AutomationMapper mapper; 085 086 final ScriptObjectMirror global; 087 088 Bridge(OperationContext operationContext) { 089 mapper = new AutomationMapper(operationContext); 090 try { 091 mapperScript.eval(mapper); 092 } catch (ScriptException cause) { 093 throw new NuxeoException("Cannot execute mapper " + mapperScript, cause); 094 } 095 global = (ScriptObjectMirror) mapper.get("nashorn.global"); 096 scriptContext.setBindings(mapper, ScriptContext.ENGINE_SCOPE); 097 } 098 099 @Override 100 public <T> T handleof(InputStream input, Class<T> typeof) { 101 run(input); 102 T handle = invocable.getInterface(global, typeof); 103 if (handle == null) { 104 throw new NuxeoException("Script doesn't implements " + typeof.getName()); 105 } 106 return typeof.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 107 new Class[] { typeof }, new InvocationHandler() { 108 109 @Override 110 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 111 return mapper.unwrap(method.invoke(handle, mapper.wrap(args[0]), mapper.wrap(args[1]))); 112 } 113 })); 114 } 115 116 @Override 117 public Object run(InputStream input) { 118 try { 119 return mapper.unwrap(engine.eval(new InputStreamReader(input), mapper)); 120 } catch (ScriptException cause) { 121 throw new NuxeoException("Cannot evaluate automation script", cause); 122 } 123 } 124 125 <T> T handleof(Class<T> typeof) { 126 return invocable.getInterface(global, typeof); 127 } 128 129 @Override 130 public <T> T adapt(Class<T> typeof) { 131 if (typeof.isAssignableFrom(engine.getClass())) { 132 return typeof.cast(engine); 133 } 134 if (typeof.isAssignableFrom(AutomationMapper.class)) { 135 return typeof.cast(mapper); 136 } 137 if (typeof.isAssignableFrom(scriptContext.getClass())) { 138 return typeof.cast(scriptContext); 139 } 140 throw new IllegalArgumentException("Cannot adapt scripting context to " + typeof.getName()); 141 } 142 143 @Override 144 public void close() throws Exception { 145 mapper.flush(); 146 } 147 } 148 149 protected ScriptEngine getScriptEngine() { 150 String version = Framework.getProperty("java.version"); 151 // Check if jdk8 152 if (!checkJavaVersion(version, NASHORN_JAVA_VERSION)) { 153 throw new UnsupportedOperationException(NASHORN_JAVA_VERSION); 154 } 155 // Check if version < jdk8u25 -> no cache. 156 if (!checkJavaVersion(version, COMPLIANT_JAVA_VERSION_CACHE)) { 157 log.warn(NASHORN_WARN_CACHE); 158 return getScriptEngine(false, false); 159 } 160 boolean cache = Boolean.parseBoolean( 161 Framework.getProperty(AUTOMATION_SCRIPTING_PRECOMPILE, DEFAULT_PRECOMPILE_STATUS)); 162 // Check if jdk8u25 <= version < jdk8u40 -> only cache. 163 if (!checkJavaVersion(version, COMPLIANT_JAVA_VERSION_CLASS_FILTER)) { 164 log.warn(NASHORN_WARN_CLASS_FILTER); 165 return getScriptEngine(cache, false); 166 } 167 // Else if version >= jdk8u40 -> cache + class filter 168 try { 169 return getScriptEngine(cache, true); 170 } catch (NoClassDefFoundError cause) { 171 log.warn(NASHORN_WARN_CLASS_FILTER); 172 return getScriptEngine(cache, false); 173 } 174 } 175 176 protected ScriptEngine getScriptEngine(boolean cache, boolean filter) { 177 NashornScriptEngineFactory nashorn = new NashornScriptEngineFactory(); 178 String[] args = cache 179 ? new String[] { "-strict", "--optimistic-types=true", "--persistent-code-cache", 180 "--class-cache-size=50" } 181 : new String[] { "-strict" }; 182 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 183 ClassFilter classFilter = filter ? getClassFilter() : null; 184 return nashorn.getScriptEngine(args, classLoader, classFilter); 185 } 186 187 protected ClassFilter getClassFilter() { 188 return className -> allowedClassNames.contains(className); 189 } 190 191}