/*
* (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.nuxeo.automation.scripting.internals;
import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.AUTOMATION_SCRIPTING_PRECOMPILE;
import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.COMPLIANT_JAVA_VERSION_CACHE;
import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.COMPLIANT_JAVA_VERSION_CLASS_FILTER;
import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.DEFAULT_PRECOMPILE_STATUS;
import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.NASHORN_JAVA_VERSION;
import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.NASHORN_WARN_CACHE;
import static org.nuxeo.automation.scripting.api.AutomationScriptingConstants.NASHORN_WARN_CLASS_FILTER;
import static org.nuxeo.launcher.config.ConfigurationGenerator.checkJavaVersion;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.function.Supplier;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.automation.scripting.api.AutomationScriptingService;
import org.nuxeo.ecm.automation.OperationContext;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.runtime.api.Framework;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
public class AutomationScriptingServiceImpl implements AutomationScriptingService {
private final Supplier<ScriptEngine> supplier = new Factory().supplier;
protected AutomationScriptingParamsInjector paramsInjector;
@Override
public Session get(CoreSession session) {
return get(new OperationContext(session));
}
@Override
public Session get(OperationContext context) {
return new Bridge(context);
}
final ScriptEngine engine = supplier.get();
class Bridge implements Session {
final CompiledScript mapperScript = AutomationMapper.compile((Compilable) engine);
final Compilable compilable = ((Compilable) engine);
final Invocable invocable = ((Invocable) engine);
final ScriptContext scriptContext = engine.getContext();
final AutomationMapper mapper;
final ScriptObjectMirror global;
Bridge(OperationContext operationContext) {
mapper = new AutomationMapper(operationContext);
try {
mapperScript.eval(mapper);
} catch (ScriptException cause) {
throw new NuxeoException("Cannot execute mapper " + mapperScript, cause);
}
global = (ScriptObjectMirror) mapper.get("nashorn.global");
scriptContext.setBindings(mapper, ScriptContext.ENGINE_SCOPE);
}
@Override
public <T> T handleof(InputStream input, Class<T> typeof) {
run(input);
T handle = invocable.getInterface(global, typeof);
if (handle == null) {
throw new NuxeoException("Script doesn't implements " + typeof.getName());
}
return typeof.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[] { typeof }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return mapper.unwrap(method.invoke(handle, mapper.wrap(args[0]), mapper.wrap(args[1])));
}
}));
}
@Override
public Object run(InputStream input) {
try {
return mapper.unwrap(engine.eval(new InputStreamReader(input), mapper));
} catch (ScriptException cause) {
throw new NuxeoException("Cannot evaluate automation script", cause);
}
}
<T> T handleof(Class<T> typeof) {
return invocable.getInterface(global, typeof);
}
@Override
public <T> T adapt(Class<T> typeof) {
if (typeof.isAssignableFrom(engine.getClass())) {
return typeof.cast(engine);
}
if (typeof.isAssignableFrom(AutomationMapper.class)) {
return typeof.cast(mapper);
}
if (typeof.isAssignableFrom(scriptContext.getClass())) {
return typeof.cast(scriptContext);
}
throw new IllegalArgumentException("Cannot adapt scripting context to " + typeof.getName());
}
@Override
public void close() throws Exception {
mapper.flush();
}
}
class Factory {
final NashornScriptEngineFactory nashorn = new NashornScriptEngineFactory();
final Supplier<ScriptEngine> supplier = supplier();
Supplier<ScriptEngine> supplier() {
Log log = LogFactory.getLog(AutomationScriptingServiceImpl.class);
String version = Framework.getProperty("java.version");
// Check if jdk8
if (!checkJavaVersion(version, NASHORN_JAVA_VERSION)) {
throw new UnsupportedOperationException(NASHORN_JAVA_VERSION);
}
// Check if version < jdk8u25 -> no cache.
if (!checkJavaVersion(version, COMPLIANT_JAVA_VERSION_CACHE)) {
log.warn(NASHORN_WARN_CACHE);
return noCache();
}
// Check if jdk8u25 <= version < jdk8u40 -> only cache.
if (!checkJavaVersion(version, COMPLIANT_JAVA_VERSION_CLASS_FILTER)) {
if (Boolean.parseBoolean(
Framework.getProperty(AUTOMATION_SCRIPTING_PRECOMPILE, DEFAULT_PRECOMPILE_STATUS))) {
log.warn(NASHORN_WARN_CLASS_FILTER);
return cache();
} else {
log.warn(NASHORN_WARN_CLASS_FILTER);
return noCache();
}
}
// Else if version >= jdk8u40 -> cache + class filter
try {
if (Boolean.parseBoolean(
Framework.getProperty(AUTOMATION_SCRIPTING_PRECOMPILE, DEFAULT_PRECOMPILE_STATUS))) {
return cacheAndClassFilter();
} else {
return noCacheAndClassFilter();
}
} catch (NoClassDefFoundError cause) {
log.warn(NASHORN_WARN_CLASS_FILTER);
return cache();
}
}
Supplier<ScriptEngine> noCache() {
return () -> nashorn.getScriptEngine(new String[] { "-strict" });
}
Supplier<ScriptEngine> cache() {
return () -> nashorn.getScriptEngine(
new String[] { "-strict", "--optimistic-types=true", "--persistent-code-cache", "--class-cache-size=50" },
Thread.currentThread().getContextClassLoader());
}
Supplier<ScriptEngine> cacheAndClassFilter() {
return () -> nashorn.getScriptEngine(
new String[] { "-strict", "--optimistic-types=true", "--persistent-code-cache", "--class-cache-size=50" },
Thread.currentThread().getContextClassLoader(), new ClassFilter() {
@Override
public boolean exposeToScripts(String className) {
return false;
}
});
}
Supplier<ScriptEngine> noCacheAndClassFilter() {
return () -> nashorn.getScriptEngine(new String[] { "-strict" },
Thread.currentThread().getContextClassLoader(), new ClassFilter() {
@Override
public boolean exposeToScripts(String className) {
return false;
}
});
};
}
}