[Painless] Add instance bindings (#34410)
This change adds instance bindings to Painless. This binding allows a whitelisted method to be called on an instance instantiated prior to script compilation. Whitelisting must be done in code as there is no practical way to instantiate a useful instance from a text file (see the tests for an example). Since an instance can be shared by multiple scripts, each method called must be thread-safe.
This commit is contained in:
parent
5f588180f9
commit
1b085252c3
|
@ -66,13 +66,17 @@ public final class Whitelist {
|
|||
/** The {@link List} of all the whitelisted Painless class bindings. */
|
||||
public final List<WhitelistClassBinding> whitelistClassBindings;
|
||||
|
||||
/** The {@link List} of all the whitelisted Painless instance bindings. */
|
||||
public final List<WhitelistInstanceBinding> whitelistInstanceBindings;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses,
|
||||
List<WhitelistMethod> whitelistImportedMethods, List<WhitelistClassBinding> whitelistClassBindings) {
|
||||
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses, List<WhitelistMethod> whitelistImportedMethods,
|
||||
List<WhitelistClassBinding> whitelistClassBindings, List<WhitelistInstanceBinding> whitelistInstanceBindings) {
|
||||
|
||||
this.classLoader = Objects.requireNonNull(classLoader);
|
||||
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
|
||||
this.whitelistImportedMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistImportedMethods));
|
||||
this.whitelistClassBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistClassBindings));
|
||||
this.whitelistInstanceBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistInstanceBindings));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,9 +42,7 @@ public class WhitelistClassBinding {
|
|||
/** The method name for this class binding. */
|
||||
public final String methodName;
|
||||
|
||||
/**
|
||||
* The canonical type name for the return type.
|
||||
*/
|
||||
/** The canonical type name for the return type. */
|
||||
public final String returnCanonicalTypeName;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.painless.spi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An instance binding represents a method call that stores state. Each instance binding must provide
|
||||
* exactly one public method name. The canonical type name parameters provided must match those of the
|
||||
* method. The method for an instance binding will target the specified Java instance.
|
||||
*/
|
||||
public class WhitelistInstanceBinding {
|
||||
|
||||
/** Information about where this constructor was whitelisted from. */
|
||||
public final String origin;
|
||||
|
||||
/** The Java instance this instance binding targets. */
|
||||
public final Object targetInstance;
|
||||
|
||||
/** The method name for this class binding. */
|
||||
public final String methodName;
|
||||
|
||||
/** The canonical type name for the return type. */
|
||||
public final String returnCanonicalTypeName;
|
||||
|
||||
/**
|
||||
* A {@link List} of {@link String}s that are the Painless type names for the parameters of the
|
||||
* constructor which can be used to look up the Java constructor through reflection.
|
||||
*/
|
||||
public final List<String> canonicalTypeNameParameters;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public WhitelistInstanceBinding(String origin, Object targetInstance,
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||
|
||||
this.origin = Objects.requireNonNull(origin);
|
||||
this.targetInstance = Objects.requireNonNull(targetInstance);
|
||||
|
||||
this.methodName = Objects.requireNonNull(methodName);
|
||||
this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName);
|
||||
this.canonicalTypeNameParameters = Objects.requireNonNull(canonicalTypeNameParameters);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import java.security.AccessController;
|
|||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Loads and creates a {@link Whitelist} from one to many text files. */
|
||||
|
@ -392,7 +393,7 @@ public final class WhitelistLoader {
|
|||
|
||||
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
||||
|
||||
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistClassBindings);
|
||||
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistClassBindings, Collections.emptyList());
|
||||
}
|
||||
|
||||
private WhitelistLoader() {}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.bootstrap.BootstrapInfo;
|
||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
||||
import org.elasticsearch.painless.antlr.Walker;
|
||||
import org.elasticsearch.painless.lookup.PainlessLookup;
|
||||
import org.elasticsearch.painless.node.SSource;
|
||||
|
@ -222,8 +221,8 @@ final class Compiler {
|
|||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
|
||||
SSource root = Walker.buildPainlessTree(scriptClassInfo, reserved, name, source, settings, painlessLookup,
|
||||
null);
|
||||
Map<String, LocalMethod> localMethods = root.analyze(painlessLookup);
|
||||
root.write();
|
||||
root.analyze(painlessLookup);
|
||||
Map<String, Object> statics = root.write();
|
||||
|
||||
try {
|
||||
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, root.getBytes());
|
||||
|
@ -231,7 +230,10 @@ final class Compiler {
|
|||
clazz.getField("$SOURCE").set(null, source);
|
||||
clazz.getField("$STATEMENTS").set(null, root.getStatements());
|
||||
clazz.getField("$DEFINITION").set(null, painlessLookup);
|
||||
clazz.getField("$LOCALS").set(null, localMethods);
|
||||
|
||||
for (Map.Entry<String, Object> statik : statics.entrySet()) {
|
||||
clazz.getField(statik.getKey()).set(null, statik.getValue());
|
||||
}
|
||||
|
||||
return clazz.getConstructors()[0];
|
||||
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
|
||||
|
|
|
@ -31,7 +31,8 @@ import java.util.Map;
|
|||
public class Globals {
|
||||
private final Map<String,SFunction> syntheticMethods = new HashMap<>();
|
||||
private final Map<String,Constant> constantInitializers = new HashMap<>();
|
||||
private final Map<String,Class<?>> bindings = new HashMap<>();
|
||||
private final Map<String,Class<?>> classBindings = new HashMap<>();
|
||||
private final Map<Object,String> instanceBindings = new HashMap<>();
|
||||
private final BitSet statements;
|
||||
|
||||
/** Create a new Globals from the set of statement boundaries */
|
||||
|
@ -56,14 +57,19 @@ public class Globals {
|
|||
}
|
||||
}
|
||||
|
||||
/** Adds a new binding to be written as a local variable */
|
||||
public String addBinding(Class<?> type) {
|
||||
String name = "$binding$" + bindings.size();
|
||||
bindings.put(name, type);
|
||||
/** Adds a new class binding to be written as a local variable */
|
||||
public String addClassBinding(Class<?> type) {
|
||||
String name = "$class_binding$" + classBindings.size();
|
||||
classBindings.put(name, type);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Adds a new binding to be written as a local variable */
|
||||
public String addInstanceBinding(Object instance) {
|
||||
return instanceBindings.computeIfAbsent(instance, key -> "$instance_binding$" + instanceBindings.size());
|
||||
}
|
||||
|
||||
/** Returns the current synthetic methods */
|
||||
public Map<String,SFunction> getSyntheticMethods() {
|
||||
return syntheticMethods;
|
||||
|
@ -75,8 +81,13 @@ public class Globals {
|
|||
}
|
||||
|
||||
/** Returns the current bindings */
|
||||
public Map<String,Class<?>> getBindings() {
|
||||
return bindings;
|
||||
public Map<String,Class<?>> getClassBindings() {
|
||||
return classBindings;
|
||||
}
|
||||
|
||||
/** Returns the current bindings */
|
||||
public Map<Object,String> getInstanceBindings() {
|
||||
return instanceBindings;
|
||||
}
|
||||
|
||||
/** Returns the set of statement boundaries */
|
||||
|
|
|
@ -60,7 +60,6 @@ public class PainlessClassBinding {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(javaConstructor, javaMethod, returnType, typeParameters);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.painless.lookup;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class PainlessInstanceBinding {
|
||||
|
||||
public final Object targetInstance;
|
||||
public final Method javaMethod;
|
||||
|
||||
public final Class<?> returnType;
|
||||
public final List<Class<?>> typeParameters;
|
||||
|
||||
PainlessInstanceBinding(Object targetInstance, Method javaMethod, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||
this.targetInstance = targetInstance;
|
||||
this.javaMethod = javaMethod;
|
||||
|
||||
this.returnType = returnType;
|
||||
this.typeParameters = typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (object == null || getClass() != object.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PainlessInstanceBinding that = (PainlessInstanceBinding)object;
|
||||
|
||||
return targetInstance == that.targetInstance &&
|
||||
Objects.equals(javaMethod, that.javaMethod) &&
|
||||
Objects.equals(returnType, that.returnType) &&
|
||||
Objects.equals(typeParameters, that.typeParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(targetInstance, javaMethod, returnType, typeParameters);
|
||||
}
|
||||
}
|
|
@ -40,13 +40,15 @@ public final class PainlessLookup {
|
|||
|
||||
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
|
||||
private final Map<String, PainlessInstanceBinding> painlessMethodKeysToPainlessInstanceBindings;
|
||||
|
||||
PainlessLookup(
|
||||
Map<String, Class<?>> javaClassNamesToClasses,
|
||||
Map<String, Class<?>> canonicalClassNamesToClasses,
|
||||
Map<Class<?>, PainlessClass> classesToPainlessClasses,
|
||||
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
|
||||
Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings) {
|
||||
Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings,
|
||||
Map<String, PainlessInstanceBinding> painlessMethodKeysToPainlessInstanceBindings) {
|
||||
|
||||
Objects.requireNonNull(javaClassNamesToClasses);
|
||||
Objects.requireNonNull(canonicalClassNamesToClasses);
|
||||
|
@ -54,6 +56,7 @@ public final class PainlessLookup {
|
|||
|
||||
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
|
||||
Objects.requireNonNull(painlessMethodKeysToPainlessClassBindings);
|
||||
Objects.requireNonNull(painlessMethodKeysToPainlessInstanceBindings);
|
||||
|
||||
this.javaClassNamesToClasses = javaClassNamesToClasses;
|
||||
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
||||
|
@ -61,6 +64,7 @@ public final class PainlessLookup {
|
|||
|
||||
this.painlessMethodKeysToImportedPainlessMethods = Collections.unmodifiableMap(painlessMethodKeysToImportedPainlessMethods);
|
||||
this.painlessMethodKeysToPainlessClassBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessClassBindings);
|
||||
this.painlessMethodKeysToPainlessInstanceBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessInstanceBindings);
|
||||
}
|
||||
|
||||
public Class<?> javaClassNameToClass(String javaClassName) {
|
||||
|
@ -200,6 +204,14 @@ public final class PainlessLookup {
|
|||
return painlessMethodKeysToPainlessClassBindings.get(painlessMethodKey);
|
||||
}
|
||||
|
||||
public PainlessInstanceBinding lookupPainlessInstanceBinding(String methodName, int arity) {
|
||||
Objects.requireNonNull(methodName);
|
||||
|
||||
String painlessMethodKey = buildPainlessMethodKey(methodName, arity);
|
||||
|
||||
return painlessMethodKeysToPainlessInstanceBindings.get(painlessMethodKey);
|
||||
}
|
||||
|
||||
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
|
||||
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.elasticsearch.painless.spi.WhitelistClass;
|
|||
import org.elasticsearch.painless.spi.WhitelistClassBinding;
|
||||
import org.elasticsearch.painless.spi.WhitelistConstructor;
|
||||
import org.elasticsearch.painless.spi.WhitelistField;
|
||||
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
|
||||
import org.elasticsearch.painless.spi.WhitelistMethod;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
|
@ -50,10 +51,11 @@ import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCan
|
|||
|
||||
public final class PainlessLookupBuilder {
|
||||
|
||||
private static final Map<PainlessConstructor , PainlessConstructor> painlessConstructorCache = new HashMap<>();
|
||||
private static final Map<PainlessMethod , PainlessMethod> painlessMethodCache = new HashMap<>();
|
||||
private static final Map<PainlessField , PainlessField> painlessFieldCache = new HashMap<>();
|
||||
private static final Map<PainlessClassBinding, PainlessClassBinding> painlessClassBindingCache = new HashMap<>();
|
||||
private static final Map<PainlessConstructor , PainlessConstructor> painlessConstructorCache = new HashMap<>();
|
||||
private static final Map<PainlessMethod , PainlessMethod> painlessMethodCache = new HashMap<>();
|
||||
private static final Map<PainlessField , PainlessField> painlessFieldCache = new HashMap<>();
|
||||
private static final Map<PainlessClassBinding , PainlessClassBinding> painlessClassBindingCache = new HashMap<>();
|
||||
private static final Map<PainlessInstanceBinding, PainlessInstanceBinding> painlessInstanceBindingCache = new HashMap<>();
|
||||
|
||||
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
|
||||
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
|
||||
|
@ -108,9 +110,15 @@ public final class PainlessLookupBuilder {
|
|||
for (WhitelistClassBinding whitelistClassBinding : whitelist.whitelistClassBindings) {
|
||||
origin = whitelistClassBinding.origin;
|
||||
painlessLookupBuilder.addPainlessClassBinding(
|
||||
whitelist.classLoader, whitelistClassBinding.targetJavaClassName,
|
||||
whitelistClassBinding.methodName, whitelistClassBinding.returnCanonicalTypeName,
|
||||
whitelistClassBinding.canonicalTypeNameParameters);
|
||||
whitelist.classLoader, whitelistClassBinding.targetJavaClassName, whitelistClassBinding.methodName,
|
||||
whitelistClassBinding.returnCanonicalTypeName, whitelistClassBinding.canonicalTypeNameParameters);
|
||||
}
|
||||
|
||||
for (WhitelistInstanceBinding whitelistInstanceBinding : whitelist.whitelistInstanceBindings) {
|
||||
origin = whitelistInstanceBinding.origin;
|
||||
painlessLookupBuilder.addPainlessInstanceBinding(
|
||||
whitelistInstanceBinding.targetInstance, whitelistInstanceBinding.methodName,
|
||||
whitelistInstanceBinding.returnCanonicalTypeName, whitelistInstanceBinding.canonicalTypeNameParameters);
|
||||
}
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
|
@ -134,6 +142,7 @@ public final class PainlessLookupBuilder {
|
|||
|
||||
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||
private final Map<String, PainlessClassBinding> painlessMethodKeysToPainlessClassBindings;
|
||||
private final Map<String, PainlessInstanceBinding> painlessMethodKeysToPainlessInstanceBindings;
|
||||
|
||||
public PainlessLookupBuilder() {
|
||||
javaClassNamesToClasses = new HashMap<>();
|
||||
|
@ -142,6 +151,7 @@ public final class PainlessLookupBuilder {
|
|||
|
||||
painlessMethodKeysToImportedPainlessMethods = new HashMap<>();
|
||||
painlessMethodKeysToPainlessClassBindings = new HashMap<>();
|
||||
painlessMethodKeysToPainlessInstanceBindings = new HashMap<>();
|
||||
}
|
||||
|
||||
private Class<?> canonicalTypeNameToType(String canonicalTypeName) {
|
||||
|
@ -763,6 +773,10 @@ public final class PainlessLookupBuilder {
|
|||
throw new IllegalArgumentException("imported method and class binding cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
if (painlessMethodKeysToPainlessInstanceBindings.containsKey(painlessMethodKey)) {
|
||||
throw new IllegalArgumentException("imported method and instance binding cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
MethodHandle methodHandle;
|
||||
|
||||
try {
|
||||
|
@ -783,7 +797,7 @@ public final class PainlessLookupBuilder {
|
|||
painlessMethodKeysToImportedPainlessMethods.put(painlessMethodKey, newImportedPainlessMethod);
|
||||
} else if (newImportedPainlessMethod.equals(existingImportedPainlessMethod) == false) {
|
||||
throw new IllegalArgumentException("cannot add imported methods with the same name and arity " +
|
||||
"but are not equivalent for methods " +
|
||||
"but do not have equivalent methods " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "] and " +
|
||||
|
@ -942,6 +956,11 @@ public final class PainlessLookupBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
if (isValidType(returnType) == false) {
|
||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for class binding " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
||||
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
||||
|
@ -955,6 +974,15 @@ public final class PainlessLookupBuilder {
|
|||
throw new IllegalArgumentException("class binding and imported method cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
if (painlessMethodKeysToPainlessInstanceBindings.containsKey(painlessMethodKey)) {
|
||||
throw new IllegalArgumentException("class binding and instance binding cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
if (Modifier.isStatic(javaMethod.getModifiers())) {
|
||||
throw new IllegalArgumentException("class binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "] cannot be static");
|
||||
}
|
||||
|
||||
PainlessClassBinding existingPainlessClassBinding = painlessMethodKeysToPainlessClassBindings.get(painlessMethodKey);
|
||||
PainlessClassBinding newPainlessClassBinding =
|
||||
new PainlessClassBinding(javaConstructor, javaMethod, returnType, typeParameters);
|
||||
|
@ -962,9 +990,9 @@ public final class PainlessLookupBuilder {
|
|||
if (existingPainlessClassBinding == null) {
|
||||
newPainlessClassBinding = painlessClassBindingCache.computeIfAbsent(newPainlessClassBinding, key -> key);
|
||||
painlessMethodKeysToPainlessClassBindings.put(painlessMethodKey, newPainlessClassBinding);
|
||||
} else if (newPainlessClassBinding.equals(existingPainlessClassBinding)) {
|
||||
} else if (newPainlessClassBinding.equals(existingPainlessClassBinding) == false) {
|
||||
throw new IllegalArgumentException("cannot add class bindings with the same name and arity " +
|
||||
"but are not equivalent for methods " +
|
||||
"but do not have equivalent methods " +
|
||||
"[[" + targetCanonicalClassName + "], " +
|
||||
"[" + methodName + "], " +
|
||||
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||
|
@ -976,6 +1004,136 @@ public final class PainlessLookupBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
public void addPainlessInstanceBinding(Object targetInstance,
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||
|
||||
Objects.requireNonNull(targetInstance);
|
||||
Objects.requireNonNull(methodName);
|
||||
Objects.requireNonNull(returnCanonicalTypeName);
|
||||
Objects.requireNonNull(canonicalTypeNameParameters);
|
||||
|
||||
Class<?> targetClass = targetInstance.getClass();
|
||||
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
|
||||
List<Class<?>> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());
|
||||
|
||||
for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
|
||||
Class<?> typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);
|
||||
|
||||
if (typeParameter == null) {
|
||||
throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for instance binding " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||
}
|
||||
|
||||
typeParameters.add(typeParameter);
|
||||
}
|
||||
|
||||
Class<?> returnType = canonicalTypeNameToType(returnCanonicalTypeName);
|
||||
|
||||
if (returnType == null) {
|
||||
throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for class binding " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||
}
|
||||
|
||||
addPainlessInstanceBinding(targetInstance, methodName, returnType, typeParameters);
|
||||
}
|
||||
|
||||
public void addPainlessInstanceBinding(Object targetInstance, String methodName, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||
Objects.requireNonNull(targetInstance);
|
||||
Objects.requireNonNull(methodName);
|
||||
Objects.requireNonNull(returnType);
|
||||
Objects.requireNonNull(typeParameters);
|
||||
|
||||
Class<?> targetClass = targetInstance.getClass();
|
||||
|
||||
if (targetClass == def.class) {
|
||||
throw new IllegalArgumentException("cannot add instance binding as reserved class [" + DEF_CLASS_NAME + "]");
|
||||
}
|
||||
|
||||
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
|
||||
Class<?> existingTargetClass = javaClassNamesToClasses.get(targetClass.getName());
|
||||
|
||||
if (existingTargetClass == null) {
|
||||
javaClassNamesToClasses.put(targetClass.getName(), targetClass);
|
||||
} else if (existingTargetClass != targetClass) {
|
||||
throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] " +
|
||||
"cannot represent multiple java classes with the same name from different class loaders");
|
||||
}
|
||||
|
||||
if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid method name [" + methodName + "] for instance binding [" + targetCanonicalClassName + "].");
|
||||
}
|
||||
|
||||
int typeParametersSize = typeParameters.size();
|
||||
List<Class<?>> javaTypeParameters = new ArrayList<>(typeParametersSize);
|
||||
|
||||
for (Class<?> typeParameter : typeParameters) {
|
||||
if (isValidType(typeParameter) == false) {
|
||||
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
|
||||
"not found for instance binding [[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
javaTypeParameters.add(typeToJavaType(typeParameter));
|
||||
}
|
||||
|
||||
if (isValidType(returnType) == false) {
|
||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for imported method " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
Method javaMethod;
|
||||
|
||||
try {
|
||||
javaMethod = targetClass.getMethod(methodName, javaTypeParameters.toArray(new Class<?>[typeParametersSize]));
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
throw new IllegalArgumentException("instance binding reflection object [[" + targetCanonicalClassName + "], " +
|
||||
"[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme);
|
||||
}
|
||||
|
||||
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
||||
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
||||
"for instance binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
if (Modifier.isStatic(javaMethod.getModifiers())) {
|
||||
throw new IllegalArgumentException("instance binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "] cannot be static");
|
||||
}
|
||||
|
||||
String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);
|
||||
|
||||
if (painlessMethodKeysToImportedPainlessMethods.containsKey(painlessMethodKey)) {
|
||||
throw new IllegalArgumentException("instance binding and imported method cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
if (painlessMethodKeysToPainlessClassBindings.containsKey(painlessMethodKey)) {
|
||||
throw new IllegalArgumentException("instance binding and class binding cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
PainlessInstanceBinding existingPainlessInstanceBinding = painlessMethodKeysToPainlessInstanceBindings.get(painlessMethodKey);
|
||||
PainlessInstanceBinding newPainlessInstanceBinding =
|
||||
new PainlessInstanceBinding(targetInstance, javaMethod, returnType, typeParameters);
|
||||
|
||||
if (existingPainlessInstanceBinding == null) {
|
||||
newPainlessInstanceBinding = painlessInstanceBindingCache.computeIfAbsent(newPainlessInstanceBinding, key -> key);
|
||||
painlessMethodKeysToPainlessInstanceBindings.put(painlessMethodKey, newPainlessInstanceBinding);
|
||||
} else if (newPainlessInstanceBinding.equals(existingPainlessInstanceBinding) == false) {
|
||||
throw new IllegalArgumentException("cannot add instances bindings with the same name and arity " +
|
||||
"but do not have equivalent methods " +
|
||||
"[[" + targetCanonicalClassName + "], " +
|
||||
"[" + methodName + "], " +
|
||||
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "] and " +
|
||||
"[[" + targetCanonicalClassName + "], " +
|
||||
"[" + methodName + "], " +
|
||||
"[" + typeToCanonicalTypeName(existingPainlessInstanceBinding.returnType) + "], " +
|
||||
typesToCanonicalTypeNames(existingPainlessInstanceBinding.typeParameters) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
public PainlessLookup build() {
|
||||
copyPainlessClassMembers();
|
||||
cacheRuntimeHandles();
|
||||
|
@ -1003,8 +1161,13 @@ public final class PainlessLookupBuilder {
|
|||
"must have the same classes as the keys of classes to painless classes");
|
||||
}
|
||||
|
||||
return new PainlessLookup(javaClassNamesToClasses, canonicalClassNamesToClasses, classesToPainlessClasses,
|
||||
painlessMethodKeysToImportedPainlessMethods, painlessMethodKeysToPainlessClassBindings);
|
||||
return new PainlessLookup(
|
||||
javaClassNamesToClasses,
|
||||
canonicalClassNamesToClasses,
|
||||
classesToPainlessClasses,
|
||||
painlessMethodKeysToImportedPainlessMethods,
|
||||
painlessMethodKeysToPainlessClassBindings,
|
||||
painlessMethodKeysToPainlessInstanceBindings);
|
||||
}
|
||||
|
||||
private void copyPainlessClassMembers() {
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.elasticsearch.painless.Locals.LocalMethod;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.lookup.PainlessClassBinding;
|
||||
import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
|
||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Type;
|
||||
|
@ -48,6 +49,7 @@ public final class ECallLocal extends AExpression {
|
|||
private LocalMethod localMethod = null;
|
||||
private PainlessMethod importedMethod = null;
|
||||
private PainlessClassBinding classBinding = null;
|
||||
private PainlessInstanceBinding instanceBinding = null;
|
||||
|
||||
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
||||
super(location);
|
||||
|
@ -74,8 +76,12 @@ public final class ECallLocal extends AExpression {
|
|||
classBinding = locals.getPainlessLookup().lookupPainlessClassBinding(name, arguments.size());
|
||||
|
||||
if (classBinding == null) {
|
||||
throw createError(
|
||||
new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
||||
instanceBinding = locals.getPainlessLookup().lookupPainlessInstanceBinding(name, arguments.size());
|
||||
|
||||
if (instanceBinding == null) {
|
||||
throw createError(
|
||||
new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +97,9 @@ public final class ECallLocal extends AExpression {
|
|||
} else if (classBinding != null) {
|
||||
typeParameters = new ArrayList<>(classBinding.typeParameters);
|
||||
actual = classBinding.returnType;
|
||||
} else if (instanceBinding != null) {
|
||||
typeParameters = new ArrayList<>(instanceBinding.typeParameters);
|
||||
actual = instanceBinding.returnType;
|
||||
} else {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
@ -125,7 +134,7 @@ public final class ECallLocal extends AExpression {
|
|||
writer.invokeStatic(Type.getType(importedMethod.targetClass),
|
||||
new Method(importedMethod.javaMethod.getName(), importedMethod.methodType.toMethodDescriptorString()));
|
||||
} else if (classBinding != null) {
|
||||
String name = globals.addBinding(classBinding.javaConstructor.getDeclaringClass());
|
||||
String name = globals.addClassBinding(classBinding.javaConstructor.getDeclaringClass());
|
||||
Type type = Type.getType(classBinding.javaConstructor.getDeclaringClass());
|
||||
int javaConstructorParameterCount = classBinding.javaConstructor.getParameterCount();
|
||||
|
||||
|
@ -154,6 +163,18 @@ public final class ECallLocal extends AExpression {
|
|||
}
|
||||
|
||||
writer.invokeVirtual(type, Method.getMethod(classBinding.javaMethod));
|
||||
} else if (instanceBinding != null) {
|
||||
String name = globals.addInstanceBinding(instanceBinding.targetInstance);
|
||||
Type type = Type.getType(instanceBinding.targetInstance.getClass());
|
||||
|
||||
writer.loadThis();
|
||||
writer.getStatic(CLASS_TYPE, name, type);
|
||||
|
||||
for (int argument = 0; argument < instanceBinding.javaMethod.getParameterCount(); ++argument) {
|
||||
arguments.get(argument).write(writer, globals);
|
||||
}
|
||||
|
||||
writer.invokeVirtual(type, Method.getMethod(instanceBinding.javaMethod));
|
||||
} else {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ public final class SSource extends AStatement {
|
|||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
public Map<String, LocalMethod> analyze(PainlessLookup painlessLookup) {
|
||||
public void analyze(PainlessLookup painlessLookup) {
|
||||
Map<String, LocalMethod> methods = new HashMap<>();
|
||||
|
||||
for (SFunction function : functions) {
|
||||
|
@ -180,8 +180,6 @@ public final class SSource extends AStatement {
|
|||
|
||||
Locals locals = Locals.newProgramScope(painlessLookup, methods.values());
|
||||
analyze(locals);
|
||||
|
||||
return locals.getMethods();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -228,7 +226,7 @@ public final class SSource extends AStatement {
|
|||
}
|
||||
}
|
||||
|
||||
public void write() {
|
||||
public Map<String, Object> write() {
|
||||
// Create the ClassWriter.
|
||||
|
||||
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
|
||||
|
@ -359,13 +357,20 @@ public final class SSource extends AStatement {
|
|||
clinit.endMethod();
|
||||
}
|
||||
|
||||
// Write binding variables
|
||||
for (Map.Entry<String, Class<?>> binding : globals.getBindings().entrySet()) {
|
||||
String name = binding.getKey();
|
||||
String descriptor = Type.getType(binding.getValue()).getDescriptor();
|
||||
// Write class binding variables
|
||||
for (Map.Entry<String, Class<?>> classBinding : globals.getClassBindings().entrySet()) {
|
||||
String name = classBinding.getKey();
|
||||
String descriptor = Type.getType(classBinding.getValue()).getDescriptor();
|
||||
visitor.visitField(Opcodes.ACC_PRIVATE, name, descriptor, null, null).visitEnd();
|
||||
}
|
||||
|
||||
// Write instance binding variables
|
||||
for (Map.Entry<Object, String> instanceBinding : globals.getInstanceBindings().entrySet()) {
|
||||
String name = instanceBinding.getValue();
|
||||
String descriptor = Type.getType(instanceBinding.getKey().getClass()).getDescriptor();
|
||||
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, name, descriptor, null, null).visitEnd();
|
||||
}
|
||||
|
||||
// Write any needsVarName methods for used variables
|
||||
for (org.objectweb.asm.commons.Method needsMethod : scriptClassInfo.getNeedsMethods()) {
|
||||
String name = needsMethod.getName();
|
||||
|
@ -382,6 +387,15 @@ public final class SSource extends AStatement {
|
|||
|
||||
visitor.visitEnd();
|
||||
bytes = writer.toByteArray();
|
||||
|
||||
Map<String, Object> statics = new HashMap<>();
|
||||
statics.put("$LOCALS", mainMethod.getMethods());
|
||||
|
||||
for (Map.Entry<Object, String> instanceBinding : globals.getInstanceBindings().entrySet()) {
|
||||
statics.put(instanceBinding.getValue(), instanceBinding.getKey());
|
||||
}
|
||||
|
||||
return statics;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,14 +20,32 @@
|
|||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.painless.spi.Whitelist;
|
||||
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BindingsTests extends ScriptTestCase {
|
||||
|
||||
public static class InstanceBindingTestClass {
|
||||
private int value;
|
||||
|
||||
public InstanceBindingTestClass(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setInstanceBindingValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getInstanceBindingValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class BindingsTestScript {
|
||||
public static final String[] PARAMETERS = { "test", "bound" };
|
||||
public abstract int execute(int test, int bound);
|
||||
|
@ -40,15 +58,29 @@ public class BindingsTests extends ScriptTestCase {
|
|||
@Override
|
||||
protected Map<ScriptContext<?>, List<Whitelist>> scriptContexts() {
|
||||
Map<ScriptContext<?>, List<Whitelist>> contexts = super.scriptContexts();
|
||||
contexts.put(BindingsTestScript.CONTEXT, Whitelist.BASE_WHITELISTS);
|
||||
List<Whitelist> whitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS);
|
||||
|
||||
InstanceBindingTestClass instanceBindingTestClass = new InstanceBindingTestClass(1);
|
||||
WhitelistInstanceBinding getter = new WhitelistInstanceBinding("test", instanceBindingTestClass,
|
||||
"setInstanceBindingValue", "void", Collections.singletonList("int"));
|
||||
WhitelistInstanceBinding setter = new WhitelistInstanceBinding("test", instanceBindingTestClass,
|
||||
"getInstanceBindingValue", "int", Collections.emptyList());
|
||||
List<WhitelistInstanceBinding> instanceBindingsList = new ArrayList<>();
|
||||
instanceBindingsList.add(getter);
|
||||
instanceBindingsList.add(setter);
|
||||
Whitelist instanceBindingsWhitelist = new Whitelist(instanceBindingTestClass.getClass().getClassLoader(),
|
||||
Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), instanceBindingsList);
|
||||
whitelists.add(instanceBindingsWhitelist);
|
||||
|
||||
contexts.put(BindingsTestScript.CONTEXT, whitelists);
|
||||
return contexts;
|
||||
}
|
||||
|
||||
public void testBasicBinding() {
|
||||
public void testBasicClassBinding() {
|
||||
assertEquals(15, exec("testAddWithState(4, 5, 6, 0.0)"));
|
||||
}
|
||||
|
||||
public void testRepeatedBinding() {
|
||||
public void testRepeatedClassBinding() {
|
||||
String script = "testAddWithState(4, 5, test, 0.0)";
|
||||
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||
BindingsTestScript executableScript = factory.newInstance();
|
||||
|
@ -58,7 +90,7 @@ public class BindingsTests extends ScriptTestCase {
|
|||
assertEquals(16, executableScript.execute(7, 0));
|
||||
}
|
||||
|
||||
public void testBoundBinding() {
|
||||
public void testBoundClassBinding() {
|
||||
String script = "testAddWithState(4, bound, test, 0.0)";
|
||||
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||
BindingsTestScript executableScript = factory.newInstance();
|
||||
|
@ -66,4 +98,21 @@ public class BindingsTests extends ScriptTestCase {
|
|||
assertEquals(10, executableScript.execute(5, 1));
|
||||
assertEquals(9, executableScript.execute(4, 2));
|
||||
}
|
||||
|
||||
public void testInstanceBinding() {
|
||||
String script = "getInstanceBindingValue() + test + bound";
|
||||
BindingsTestScript.Factory factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||
BindingsTestScript executableScript = factory.newInstance();
|
||||
assertEquals(3, executableScript.execute(1, 1));
|
||||
|
||||
script = "setInstanceBindingValue(test + bound); getInstanceBindingValue()";
|
||||
factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||
executableScript = factory.newInstance();
|
||||
assertEquals(4, executableScript.execute(-2, 6));
|
||||
|
||||
script = "getInstanceBindingValue() + test + bound";
|
||||
factory = scriptEngine.compile(null, script, BindingsTestScript.CONTEXT, Collections.emptyMap());
|
||||
executableScript = factory.newInstance();
|
||||
assertEquals(8, executableScript.execute(-2, 6));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,12 @@ package org.elasticsearch.example.painlesswhitelist;
|
|||
|
||||
import org.elasticsearch.painless.spi.PainlessExtension;
|
||||
import org.elasticsearch.painless.spi.Whitelist;
|
||||
import org.elasticsearch.painless.spi.WhitelistInstanceBinding;
|
||||
import org.elasticsearch.painless.spi.WhitelistLoader;
|
||||
import org.elasticsearch.script.FieldScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -37,6 +39,14 @@ public class ExampleWhitelistExtension implements PainlessExtension {
|
|||
|
||||
@Override
|
||||
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
|
||||
return Collections.singletonMap(FieldScript.CONTEXT, Collections.singletonList(WHITELIST));
|
||||
ExampleWhitelistedInstance ewi = new ExampleWhitelistedInstance(1);
|
||||
WhitelistInstanceBinding addValue = new WhitelistInstanceBinding("example addValue", ewi,
|
||||
"addValue", "int", Collections.singletonList("int"));
|
||||
WhitelistInstanceBinding getValue = new WhitelistInstanceBinding("example getValue", ewi,
|
||||
"getValue", "int", Collections.emptyList());
|
||||
Whitelist instanceWhitelist = new Whitelist(ewi.getClass().getClassLoader(), Collections.emptyList(),
|
||||
Collections.emptyList(), Collections.emptyList(), Arrays.asList(addValue, getValue));
|
||||
|
||||
return Collections.singletonMap(FieldScript.CONTEXT, Arrays.asList(WHITELIST, instanceWhitelist));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.example.painlesswhitelist;
|
||||
|
||||
public class ExampleWhitelistedInstance {
|
||||
private final int value;
|
||||
|
||||
public ExampleWhitelistedInstance(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int addValue(int value) {
|
||||
return this.value + value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
# Example tests using an instance binding
|
||||
|
||||
"custom instance binding":
|
||||
- do:
|
||||
index:
|
||||
index: test
|
||||
type: test
|
||||
id: 1
|
||||
body: { "num1": 1 }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
- do:
|
||||
index: test
|
||||
search:
|
||||
body:
|
||||
query:
|
||||
match_all: {}
|
||||
script_fields:
|
||||
sNum1:
|
||||
script:
|
||||
source: "addValue((int)doc['num1'][0])"
|
||||
lang: painless
|
||||
|
||||
- match: { hits.total: 1 }
|
||||
- match: { hits.hits.0.fields.sNum1.0: 2 }
|
||||
|
||||
- do:
|
||||
index: test
|
||||
search:
|
||||
body:
|
||||
query:
|
||||
match_all: {}
|
||||
script_fields:
|
||||
sNum1:
|
||||
script:
|
||||
source: "getValue() + doc['num1'][0]"
|
||||
lang: painless
|
||||
|
||||
- match: { hits.total: 1 }
|
||||
- match: { hits.hits.0.fields.sNum1.0: 2 }
|
Loading…
Reference in New Issue