package org.infinispan.scripting.impl;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import org.infinispan.Cache;
import org.infinispan.commons.marshall.jboss.GenericJBossMarshaller;
import org.infinispan.commons.util.CollectionFactory;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.interceptors.impl.CacheMgmtInterceptor;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.registry.InternalCacheRegistry;
import org.infinispan.registry.InternalCacheRegistry.Flag;
import org.infinispan.scripting.ScriptingManager;
import org.infinispan.scripting.logging.Log;
import org.infinispan.security.AuthorizationManager;
import org.infinispan.security.AuthorizationPermission;
import org.infinispan.security.impl.AuthorizationHelper;
import org.infinispan.security.impl.CacheRoleImpl;
import org.infinispan.tasks.TaskContext;
import org.infinispan.tasks.TaskManager;
import org.infinispan.tasks.impl.TaskManagerImpl;
import org.infinispan.util.logging.LogFactory;
/**
* ScriptingManagerImpl.
*
* @author Tristan Tarrant
* @since 7.2
*/
@Scope(Scopes.GLOBAL)
public class ScriptingManagerImpl implements ScriptingManager {
private static final Log log = LogFactory.getLog(ScriptingManagerImpl.class, Log.class);
EmbeddedCacheManager cacheManager;
private ScriptEngineManager scriptEngineManager;
private ConcurrentMap<String, ScriptEngine> scriptEnginesByExtension = CollectionFactory.makeConcurrentMap(2);
private ConcurrentMap<String, ScriptEngine> scriptEnginesByLanguage = CollectionFactory.makeConcurrentMap(2);
private Cache<String, String> scriptCache;
ConcurrentMap<String, CompiledScript> compiledScripts = CollectionFactory.makeConcurrentMap();
private AuthorizationHelper globalAuthzHelper;
private final Function<String, ScriptEngine> getEngineByName = this::getEngineByName;
private final Function<String, ScriptEngine> getEngineByExtension = this::getEngineByExtension;
public ScriptingManagerImpl() {
}
@Inject
public void initialize(final EmbeddedCacheManager cacheManager, InternalCacheRegistry internalCacheRegistry, TaskManager taskManager) {
this.cacheManager = cacheManager;
ClassLoader classLoader = cacheManager.getCacheManagerConfiguration().classLoader();
this.scriptEngineManager = new ScriptEngineManager(classLoader);
internalCacheRegistry.registerInternalCache(SCRIPT_CACHE, getScriptCacheConfiguration().build(), EnumSet.of(Flag.USER, Flag.PROTECTED, Flag.PERSISTENT));
((TaskManagerImpl)taskManager).registerTaskEngine(new ScriptingTaskEngine(this));
}
Cache<String, String> getScriptCache() {
if (scriptCache == null) {
scriptCache = cacheManager.getCache(SCRIPT_CACHE);
}
return scriptCache;
}
private ConfigurationBuilder getScriptCacheConfiguration() {
GlobalConfiguration globalConfiguration = cacheManager.getGlobalComponentRegistry().getGlobalConfiguration();
CacheMode cacheMode = globalConfiguration.isClustered() ? CacheMode.REPL_SYNC : CacheMode.LOCAL;
ConfigurationBuilder cfg = new ConfigurationBuilder();
cfg.clustering().cacheMode(cacheMode).sync().stateTransfer().fetchInMemoryState(true).awaitInitialTransfer(false).compatibility().enable()
.marshaller(new GenericJBossMarshaller()).customInterceptors().addInterceptor().interceptor(new ScriptingInterceptor()).before(CacheMgmtInterceptor.class);
if (globalConfiguration.security().authorization().enabled()) {
globalConfiguration.security().authorization().roles().put(SCRIPT_MANAGER_ROLE, new CacheRoleImpl(SCRIPT_MANAGER_ROLE, AuthorizationPermission.ALL));
cfg.security().authorization().enable().role(SCRIPT_MANAGER_ROLE);
globalAuthzHelper = cacheManager.getGlobalComponentRegistry().getComponent(AuthorizationHelper.class);
}
return cfg;
}
ScriptMetadata compileScript(String name, String script) {
ScriptMetadata metadata = ScriptMetadataParser.parse(name, script);
ScriptEngine engine = getEngineForScript(metadata);
if (engine instanceof Compilable) {
try {
CompiledScript compiledScript = ((Compilable) engine).compile(script);
compiledScripts.put(name, compiledScript);
return metadata;
} catch (ScriptException e) {
throw log.scriptCompilationException(e, name);
}
} else {
return null;
}
}
@Override
public void addScript(String name, String script) {
ScriptMetadata metadata = ScriptMetadataParser.parse(name, script);
ScriptEngine engine = getEngineForScript(metadata);
if (engine == null) {
throw log.noScriptEngineForScript(name);
}
getScriptCache().getAdvancedCache().put(name, script, metadata);
}
@Override
public void removeScript(String name) {
if (containsScript(name)) {
getScriptCache().remove(name);
} else {
throw log.noNamedScript(name);
}
}
@Override
public String getScript(String name) {
if (containsScript(name)) {
return SecurityActions.getUnwrappedCache(getScriptCache()).get(name);
} else {
throw log.noNamedScript(name);
}
}
@Override
public Set<String> getScriptNames() {
return SecurityActions.getUnwrappedCache(getScriptCache()).keySet();
}
public boolean containsScript(String taskName) {
return SecurityActions.getUnwrappedCache(getScriptCache()).containsKey(taskName);
}
@Override
public <T> CompletableFuture<T> runScript(String scriptName) {
return runScript(scriptName, new TaskContext());
}
@Override
public <T> CompletableFuture<T> runScript(String scriptName, TaskContext context) {
ScriptMetadata metadata = getScriptMetadata(scriptName);
if (globalAuthzHelper != null) {
AuthorizationManager authorizationManager = context.getCache().isPresent() ?
SecurityActions.getAuthorizationManager(context.getCache().get().getAdvancedCache()) : null;
if (authorizationManager != null) {
authorizationManager.checkPermission(AuthorizationPermission.EXEC, metadata.role().orElse(null));
} else {
globalAuthzHelper.checkPermission(AuthorizationPermission.EXEC, metadata.role().orElse(null));
}
}
Bindings userBindings = context.getParameters()
.map(p -> {
Map<String, ?> params = metadata.dataType().transformer.toDataType(context.getParameters().get(), context.getMarshaller());
return new SimpleBindings((Map<String, Object>) params);
})
.orElseGet(() -> new SimpleBindings());
SimpleBindings systemBindings = new SimpleBindings();
DataTypedCacheManager cm = new DataTypedCacheManager(metadata.dataType(), context.getMarshaller(), cacheManager);
systemBindings.put(SystemBindings.CACHE_MANAGER.toString(), cm);
systemBindings.put(SystemBindings.SCRIPTING_MANAGER.toString(), this);
context.getCache().ifPresent(cache -> {
Cache<?, ?> c = cm.getCacheConfiguration(cache.getName()).compatibility().enabled()
? cache : new DataTypedCache<>(cm, cache);
systemBindings.put(SystemBindings.CACHE.toString(), c);
});
context.getMarshaller().ifPresent(marshaller -> {
systemBindings.put(SystemBindings.MARSHALLER.toString(), marshaller);
});
CacheScriptBindings bindings = new CacheScriptBindings(systemBindings, userBindings);
ScriptRunner runner = metadata.mode().getRunner();
return runner.runScript(this, metadata, bindings).thenApply(t ->
(T) metadata.dataType().transformer.fromDataType(t, context.getMarshaller()));
}
ScriptMetadata getScriptMetadata(String scriptName) {
CacheEntry<String, String> scriptEntry = SecurityActions.getCacheEntry(getScriptCache().getAdvancedCache(), scriptName);
if (scriptEntry == null) {
throw log.noNamedScript(scriptName);
}
ScriptMetadata metadata = (ScriptMetadata) scriptEntry.getMetadata();
return metadata;
}
<T> CompletableFuture<T> execute(ScriptMetadata metadata, Bindings bindings) {
CompiledScript compiled = compiledScripts.get(metadata.name());
try {
if (compiled != null) {
T result = (T) compiled.eval(bindings);
return CompletableFuture.completedFuture(result);
} else {
ScriptEngine engine = getEngineForScript(metadata);
T result = (T) engine.eval(getScriptCache().get(metadata.name()), bindings);
return CompletableFuture.completedFuture(result);
}
} catch (ScriptException e) {
throw log.scriptExecutionError(e);
}
}
ScriptEngine getEngineForScript(ScriptMetadata metadata) {
ScriptEngine engine;
if (metadata.language().isPresent()) {
engine = scriptEnginesByLanguage.computeIfAbsent(metadata.language().get(), getEngineByName);
} else {
engine = scriptEnginesByExtension.computeIfAbsent(metadata.extension(), getEngineByExtension);
}
if (engine == null) {
throw log.noEngineForScript(metadata.name());
} else {
return engine;
}
}
private ScriptEngine getEngineByName(String shortName) {
return withClassLoader(ScriptingManagerImpl.class.getClassLoader(),
scriptEngineManager, shortName,
ScriptEngineManager::getEngineByName);
}
private ScriptEngine getEngineByExtension(String extension) {
return withClassLoader(ScriptingManagerImpl.class.getClassLoader(),
scriptEngineManager, extension,
ScriptEngineManager::getEngineByExtension);
}
private static ScriptEngine withClassLoader(ClassLoader cl,
ScriptEngineManager manager, String name,
BiFunction<ScriptEngineManager, String, ScriptEngine> f) {
ClassLoader curr = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(cl);
return f.apply(manager, name);
} finally {
Thread.currentThread().setContextClassLoader(curr);
}
}
}