7614ff8c6f
Precursor for InvocableScriptAdapter, which compiles/evaluates a script without affecting the engine's bindings. This allows the same script to be compiled once and then evaluated multiple times (with the same ScriptEngine).
126 lines
4.8 KiB
Java
126 lines
4.8 KiB
Java
/*
|
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
* and other contributors as indicated by the @author tags.
|
|
*
|
|
* 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.keycloak.scripting;
|
|
|
|
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 org.keycloak.models.ScriptModel;
|
|
|
|
/**
|
|
* A {@link ScriptingProvider} that uses a {@link ScriptEngineManager} to evaluate scripts with a {@link ScriptEngine}.
|
|
*
|
|
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
|
|
*/
|
|
public class DefaultScriptingProvider implements ScriptingProvider {
|
|
|
|
private final ScriptEngineManager scriptEngineManager;
|
|
|
|
DefaultScriptingProvider(ScriptEngineManager scriptEngineManager) {
|
|
if (scriptEngineManager == null) {
|
|
throw new IllegalStateException("scriptEngineManager must not be null!");
|
|
}
|
|
|
|
this.scriptEngineManager = scriptEngineManager;
|
|
}
|
|
|
|
/**
|
|
* Wraps the provided {@link ScriptModel} in a {@link javax.script.Invocable} instance with bindings configured through the {@link ScriptBindingsConfigurer}.
|
|
*
|
|
* @param scriptModel must not be {@literal null}
|
|
* @param bindingsConfigurer must not be {@literal null}
|
|
*/
|
|
@Override
|
|
public InvocableScriptAdapter prepareInvocableScript(ScriptModel scriptModel, ScriptBindingsConfigurer bindingsConfigurer) {
|
|
final AbstractEvaluatableScriptAdapter evaluatable = prepareEvaluatableScript(scriptModel);
|
|
return evaluatable.prepareInvokableScript(bindingsConfigurer);
|
|
}
|
|
|
|
/**
|
|
* Wraps the provided {@link ScriptModel} in a {@link javax.script.Invocable} instance with bindings configured through the {@link ScriptBindingsConfigurer}.
|
|
*
|
|
* @param scriptModel must not be {@literal null}
|
|
*/
|
|
@Override
|
|
public AbstractEvaluatableScriptAdapter prepareEvaluatableScript(ScriptModel scriptModel) {
|
|
if (scriptModel == null) {
|
|
throw new IllegalArgumentException("script must not be null");
|
|
}
|
|
|
|
if (scriptModel.getCode() == null || scriptModel.getCode().trim().isEmpty()) {
|
|
throw new IllegalArgumentException("script must not be null or empty");
|
|
}
|
|
|
|
ScriptEngine engine = createPreparedScriptEngine(scriptModel);
|
|
|
|
if (engine instanceof Compilable) {
|
|
try {
|
|
final CompiledScript compiledScript = ((Compilable) engine).compile(scriptModel.getCode());
|
|
return new CompiledEvaluatableScriptAdapter(scriptModel, compiledScript);
|
|
}
|
|
catch (ScriptException e) {
|
|
throw new ScriptExecutionException(scriptModel, e);
|
|
}
|
|
}
|
|
return new UncompiledEvaluatableScriptAdapter(scriptModel, engine);
|
|
}
|
|
|
|
//TODO allow scripts to be maintained independently of other components, e.g. with dedicated persistence
|
|
//TODO allow script lookup by (scriptId)
|
|
//TODO allow script lookup by (name, realmName)
|
|
|
|
@Override
|
|
public ScriptModel createScript(String realmId, String mimeType, String scriptName, String scriptCode, String scriptDescription) {
|
|
return new Script(null /* scriptId */, realmId, scriptName, mimeType, scriptCode, scriptDescription);
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
//NOOP
|
|
}
|
|
|
|
/**
|
|
* Looks-up a {@link ScriptEngine} with prepared {@link Bindings} for the given {@link ScriptModel Script}.
|
|
*/
|
|
private ScriptEngine createPreparedScriptEngine(ScriptModel script) {
|
|
ScriptEngine scriptEngine = lookupScriptEngineFor(script);
|
|
|
|
if (scriptEngine == null) {
|
|
throw new IllegalStateException("Could not find ScriptEngine for script: " + script);
|
|
}
|
|
|
|
return scriptEngine;
|
|
}
|
|
|
|
/**
|
|
* Looks-up a {@link ScriptEngine} based on the MIME-type provided by the given {@link Script}.
|
|
*/
|
|
private ScriptEngine lookupScriptEngineFor(ScriptModel script) {
|
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
|
try {
|
|
Thread.currentThread().setContextClassLoader(DefaultScriptingProvider.class.getClassLoader());
|
|
return scriptEngineManager.getEngineByMimeType(script.getMimeType());
|
|
}
|
|
finally {
|
|
Thread.currentThread().setContextClassLoader(cl);
|
|
}
|
|
}
|
|
}
|