Add annotations to Painless whitelist (#43239)
This change adds the ability to attach annotative information for classes, methods, fields, static methods, class bindings, and instance bindings during Painless whitelisting. Annotations are specified as @annotation or optionally as @annotation[parameter="argument",...]. Annotations open up the ability to specify whitelist objects as having a short name (no_import -> @no_import) or deprecated.
This commit is contained in:
parent
eaa9ee1f16
commit
5eb044e635
|
@ -19,6 +19,8 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import org.elasticsearch.painless.spi.annotation.WhitelistAnnotationParser;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -52,7 +54,8 @@ public final class Whitelist {
|
|||
};
|
||||
|
||||
public static final List<Whitelist> BASE_WHITELISTS =
|
||||
Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Whitelist.class, BASE_WHITELIST_FILES));
|
||||
Collections.singletonList(WhitelistLoader.loadFromResourceFiles(
|
||||
Whitelist.class, WhitelistAnnotationParser.BASE_ANNOTATION_PARSERS, BASE_WHITELIST_FILES));
|
||||
|
||||
/** The {@link ClassLoader} used to look up the whitelisted Java classes, constructors, methods, and fields. */
|
||||
public final ClassLoader classLoader;
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Class represents the equivalent of a Java class in Painless complete with super classes,
|
||||
|
@ -46,11 +49,6 @@ public final class WhitelistClass {
|
|||
/** The Java class name this class represents. */
|
||||
public final String javaClassName;
|
||||
|
||||
/**
|
||||
* Allow the Java class name to only be specified as the fully-qualified name.
|
||||
*/
|
||||
public final boolean noImport;
|
||||
|
||||
/** The {@link List} of whitelisted ({@link WhitelistConstructor}s) available to this class. */
|
||||
public final List<WhitelistConstructor> whitelistConstructors;
|
||||
|
||||
|
@ -60,17 +58,27 @@ public final class WhitelistClass {
|
|||
/** The {@link List} of whitelisted ({@link WhitelistField}s) available to this class. */
|
||||
public final List<WhitelistField> whitelistFields;
|
||||
|
||||
/** The {@link Map} of annotations for this class. */
|
||||
public final Map<Class<?>, Object> painlessAnnotations;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public WhitelistClass(String origin, String javaClassName, boolean noImport,
|
||||
List<WhitelistConstructor> whitelistConstructors, List<WhitelistMethod> whitelistMethods, List<WhitelistField> whitelistFields)
|
||||
{
|
||||
public WhitelistClass(String origin, String javaClassName,
|
||||
List<WhitelistConstructor> whitelistConstructors, List<WhitelistMethod> whitelistMethods, List<WhitelistField> whitelistFields,
|
||||
List<Object> painlessAnnotations) {
|
||||
|
||||
this.origin = Objects.requireNonNull(origin);
|
||||
this.javaClassName = Objects.requireNonNull(javaClassName);
|
||||
this.noImport = noImport;
|
||||
|
||||
this.whitelistConstructors = Collections.unmodifiableList(Objects.requireNonNull(whitelistConstructors));
|
||||
this.whitelistMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistMethods));
|
||||
this.whitelistFields = Collections.unmodifiableList(Objects.requireNonNull(whitelistFields));
|
||||
|
||||
if (painlessAnnotations.isEmpty()) {
|
||||
this.painlessAnnotations = Collections.emptyMap();
|
||||
} else {
|
||||
this.painlessAnnotations = Collections.unmodifiableMap(Objects.requireNonNull(painlessAnnotations).stream()
|
||||
.map(painlessAnnotation -> new AbstractMap.SimpleEntry<>(painlessAnnotation.getClass(), painlessAnnotation))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A class binding represents a method call that stores state. Each class binding's Java class must
|
||||
|
@ -51,9 +55,13 @@ public class WhitelistClassBinding {
|
|||
*/
|
||||
public final List<String> canonicalTypeNameParameters;
|
||||
|
||||
/** The {@link Map} of annotations for this class binding. */
|
||||
public final Map<Class<?>, Object> painlessAnnotations;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public WhitelistClassBinding(String origin, String targetJavaClassName,
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters,
|
||||
List<Object> painlessAnnotations) {
|
||||
|
||||
this.origin = Objects.requireNonNull(origin);
|
||||
this.targetJavaClassName = Objects.requireNonNull(targetJavaClassName);
|
||||
|
@ -61,5 +69,13 @@ public class WhitelistClassBinding {
|
|||
this.methodName = Objects.requireNonNull(methodName);
|
||||
this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName);
|
||||
this.canonicalTypeNameParameters = Objects.requireNonNull(canonicalTypeNameParameters);
|
||||
|
||||
if (painlessAnnotations.isEmpty()) {
|
||||
this.painlessAnnotations = Collections.emptyMap();
|
||||
} else {
|
||||
this.painlessAnnotations = Collections.unmodifiableMap(Objects.requireNonNull(painlessAnnotations).stream()
|
||||
.map(painlessAnnotation -> new AbstractMap.SimpleEntry<>(painlessAnnotation.getClass(), painlessAnnotation))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Constructor represents the equivalent of a Java constructor available as a whitelisted class
|
||||
|
@ -40,9 +43,20 @@ public final class WhitelistConstructor {
|
|||
*/
|
||||
public final List<String> canonicalTypeNameParameters;
|
||||
|
||||
/** The {@link Map} of annotations for this constructor. */
|
||||
public final Map<Class<?>, Object> painlessAnnotations;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public WhitelistConstructor(String origin, List<String> canonicalTypeNameParameters) {
|
||||
public WhitelistConstructor(String origin, List<String> canonicalTypeNameParameters, List<Object> painlessAnnotations) {
|
||||
this.origin = Objects.requireNonNull(origin);
|
||||
this.canonicalTypeNameParameters = Collections.unmodifiableList(Objects.requireNonNull(canonicalTypeNameParameters));
|
||||
|
||||
if (painlessAnnotations.isEmpty()) {
|
||||
this.painlessAnnotations = Collections.emptyMap();
|
||||
} else {
|
||||
this.painlessAnnotations = Collections.unmodifiableMap(Objects.requireNonNull(painlessAnnotations).stream()
|
||||
.map(painlessAnnotation -> new AbstractMap.SimpleEntry<>(painlessAnnotation.getClass(), painlessAnnotation))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,12 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Field represents the equivalent of a Java field available as a whitelisted class field
|
||||
|
@ -37,10 +42,21 @@ public class WhitelistField {
|
|||
/** The canonical type name for the field which can be used to look up the Java field through reflection. */
|
||||
public final String canonicalTypeNameParameter;
|
||||
|
||||
/** The {@link Map} of annotations for this field. */
|
||||
public final Map<Class<?>, Object> painlessAnnotations;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public WhitelistField(String origin, String fieldName, String canonicalTypeNameParameter) {
|
||||
public WhitelistField(String origin, String fieldName, String canonicalTypeNameParameter, List<Object> painlessAnnotations) {
|
||||
this.origin = Objects.requireNonNull(origin);
|
||||
this.fieldName = Objects.requireNonNull(fieldName);
|
||||
this.canonicalTypeNameParameter = Objects.requireNonNull(canonicalTypeNameParameter);
|
||||
|
||||
if (painlessAnnotations.isEmpty()) {
|
||||
this.painlessAnnotations = Collections.emptyMap();
|
||||
} else {
|
||||
this.painlessAnnotations = Collections.unmodifiableMap(Objects.requireNonNull(painlessAnnotations).stream()
|
||||
.map(painlessAnnotation -> new AbstractMap.SimpleEntry<>(painlessAnnotation.getClass(), painlessAnnotation))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* An instance binding represents a method call that stores state. Each instance binding must provide
|
||||
|
@ -47,9 +51,13 @@ public class WhitelistInstanceBinding {
|
|||
*/
|
||||
public final List<String> canonicalTypeNameParameters;
|
||||
|
||||
/** The {@link Map} of annotations for this instance binding. */
|
||||
public final Map<Class<?>, Object> painlessAnnotations;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public WhitelistInstanceBinding(String origin, Object targetInstance,
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters,
|
||||
List<Object> painlessAnnotations) {
|
||||
|
||||
this.origin = Objects.requireNonNull(origin);
|
||||
this.targetInstance = Objects.requireNonNull(targetInstance);
|
||||
|
@ -57,5 +65,13 @@ public class WhitelistInstanceBinding {
|
|||
this.methodName = Objects.requireNonNull(methodName);
|
||||
this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName);
|
||||
this.canonicalTypeNameParameters = Objects.requireNonNull(canonicalTypeNameParameters);
|
||||
|
||||
if (painlessAnnotations.isEmpty()) {
|
||||
this.painlessAnnotations = Collections.emptyMap();
|
||||
} else {
|
||||
this.painlessAnnotations = Collections.unmodifiableMap(Objects.requireNonNull(painlessAnnotations).stream()
|
||||
.map(painlessAnnotation -> new AbstractMap.SimpleEntry<>(painlessAnnotation.getClass(), painlessAnnotation))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import org.elasticsearch.painless.spi.annotation.WhitelistAnnotationParser;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
@ -30,11 +32,22 @@ import java.security.PrivilegedAction;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Loads and creates a {@link Whitelist} from one to many text files. */
|
||||
public final class WhitelistLoader {
|
||||
|
||||
/**
|
||||
* Loads and creates a {@link Whitelist} from one to many text files using only the base annotation parsers.
|
||||
* See {@link #loadFromResourceFiles(Class, Map, String...)} for information on how to structure a whitelist
|
||||
* text file.
|
||||
*/
|
||||
public static Whitelist loadFromResourceFiles(Class<?> resource, String... filepaths) {
|
||||
return loadFromResourceFiles(resource, WhitelistAnnotationParser.BASE_ANNOTATION_PARSERS, filepaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and creates a {@link Whitelist} from one to many text files. The file paths are passed in as an array of
|
||||
* {@link String}s with a single {@link Class} to be be used to load the resources where each {@link String}
|
||||
|
@ -54,7 +67,7 @@ public final class WhitelistLoader {
|
|||
* a Painless type name with the exception that any dollar symbols used as part of inner classes will
|
||||
* be replaced with dot symbols. </li>
|
||||
* <li> short Java type name - The text after the final dot symbol of any specified Java class. A
|
||||
* short type Java name may be excluded by using the 'no_import' token during Painless class parsing
|
||||
* short type Java name may be excluded by using the 'no_import' attribute during Painless class parsing
|
||||
* as described later. </li>
|
||||
* </ul>
|
||||
*
|
||||
|
@ -65,8 +78,8 @@ public final class WhitelistLoader {
|
|||
* be ignored by the parser. </li>
|
||||
* <li> Primitive types may be specified starting with 'class' and followed by the Java type name,
|
||||
* an opening bracket, a newline, a closing bracket, and a final newline. </li>
|
||||
* <li> Complex types may be specified starting with 'class' and followed the fully-qualified Java
|
||||
* class name, optionally followed by an 'no_import' token, an opening bracket, a newline,
|
||||
* <li> Complex types may be specified starting with 'class' and followed by the fully-qualified Java
|
||||
* class name, optionally followed by a 'no_import' attribute, an opening bracket, a newline,
|
||||
* constructor/method/field specifications, a closing bracket, and a final newline. Within a complex
|
||||
* type the following may be parsed:
|
||||
* <ul>
|
||||
|
@ -90,6 +103,10 @@ public final class WhitelistLoader {
|
|||
* of the field, followed by the Java name of the field (which all be the Painless name
|
||||
* for the field), and a newline. </li>
|
||||
* </ul>
|
||||
* <li> Annotations may be added starting with an at, followed by a name, optionally an opening brace,
|
||||
* a parameter name, an equals, an opening quote, an argument value, a closing quote, (possibly repeated
|
||||
* for multiple arguments,) and a closing brace. Multiple annotations may be added after a class (before
|
||||
* the opening bracket), after a method, or after field. </li>
|
||||
* </ul>
|
||||
*
|
||||
* Note there must be a one-to-one correspondence of Painless type names to Java type/class names.
|
||||
|
@ -110,7 +127,7 @@ public final class WhitelistLoader {
|
|||
*
|
||||
* # complex types
|
||||
*
|
||||
* class my.package.Example no_import {
|
||||
* class my.package.Example @no_import {
|
||||
* # constructors
|
||||
* ()
|
||||
* (int)
|
||||
|
@ -120,7 +137,8 @@ public final class WhitelistLoader {
|
|||
* # method
|
||||
* Example add(int, def)
|
||||
* int add(Example, Example)
|
||||
* void example()
|
||||
* void example() @deprecated[use example 2 instead]
|
||||
* void example2()
|
||||
*
|
||||
* # augmented
|
||||
* Example some.other.Class sub(Example, int, def)
|
||||
|
@ -132,7 +150,7 @@ public final class WhitelistLoader {
|
|||
* }
|
||||
* }
|
||||
*/
|
||||
public static Whitelist loadFromResourceFiles(Class<?> resource, String... filepaths) {
|
||||
public static Whitelist loadFromResourceFiles(Class<?> resource, Map<String, WhitelistAnnotationParser> parsers, String... filepaths) {
|
||||
List<WhitelistClass> whitelistClasses = new ArrayList<>();
|
||||
List<WhitelistMethod> whitelistStatics = new ArrayList<>();
|
||||
List<WhitelistClassBinding> whitelistClassBindings = new ArrayList<>();
|
||||
|
@ -149,10 +167,10 @@ public final class WhitelistLoader {
|
|||
String parseType = null;
|
||||
String whitelistClassOrigin = null;
|
||||
String javaClassName = null;
|
||||
boolean noImport = false;
|
||||
List<WhitelistConstructor> whitelistConstructors = null;
|
||||
List<WhitelistMethod> whitelistMethods = null;
|
||||
List<WhitelistField> whitelistFields = null;
|
||||
List<Object> classAnnotations = null;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
number = reader.getLineNumber();
|
||||
|
@ -164,7 +182,7 @@ public final class WhitelistLoader {
|
|||
}
|
||||
|
||||
// Handle a new class by resetting all the variables necessary to construct a new WhitelistClass for the whitelist.
|
||||
// Expects the following format: 'class' ID 'no_import'? '{' '\n'
|
||||
// Expects the following format: 'class' ID annotations? '{' '\n'
|
||||
if (line.startsWith("class ")) {
|
||||
// Ensure the final token of the line is '{'.
|
||||
if (line.endsWith("{") == false) {
|
||||
|
@ -176,19 +194,19 @@ public final class WhitelistLoader {
|
|||
throw new IllegalArgumentException("invalid definition: cannot embed class definition [" + line + "]");
|
||||
}
|
||||
|
||||
// Parse the Java class name.
|
||||
String[] tokens = line.substring(5, line.length() - 1).trim().split("\\s+");
|
||||
// Parse the Java class name and annotations if they exist.
|
||||
int annotationIndex = line.indexOf('@');
|
||||
|
||||
// Ensure the correct number of tokens.
|
||||
if (tokens.length == 2 && "no_import".equals(tokens[1])) {
|
||||
noImport = true;
|
||||
} else if (tokens.length != 1) {
|
||||
throw new IllegalArgumentException("invalid class definition: failed to parse class name [" + line + "]");
|
||||
if (annotationIndex == -1) {
|
||||
annotationIndex = line.length() - 1;
|
||||
classAnnotations = Collections.emptyList();
|
||||
} else {
|
||||
classAnnotations = parseWhitelistAnnotations(parsers, line.substring(annotationIndex, line.length() - 1));
|
||||
}
|
||||
|
||||
parseType = "class";
|
||||
whitelistClassOrigin = "[" + filepath + "]:[" + number + "]";
|
||||
javaClassName = tokens[0];
|
||||
javaClassName = line.substring(5, annotationIndex).trim();
|
||||
|
||||
// Reset all the constructors, methods, and fields to support a new class.
|
||||
whitelistConstructors = new ArrayList<>();
|
||||
|
@ -217,22 +235,22 @@ public final class WhitelistLoader {
|
|||
// Create a new WhitelistClass with all the previously gathered constructors, methods,
|
||||
// augmented methods, and fields, and add it to the list of whitelisted classes.
|
||||
if ("class".equals(parseType)) {
|
||||
whitelistClasses.add(new WhitelistClass(whitelistClassOrigin, javaClassName, noImport,
|
||||
whitelistConstructors, whitelistMethods, whitelistFields));
|
||||
whitelistClasses.add(new WhitelistClass(whitelistClassOrigin, javaClassName,
|
||||
whitelistConstructors, whitelistMethods, whitelistFields, classAnnotations));
|
||||
|
||||
whitelistClassOrigin = null;
|
||||
javaClassName = null;
|
||||
noImport = false;
|
||||
whitelistConstructors = null;
|
||||
whitelistMethods = null;
|
||||
whitelistFields = null;
|
||||
classAnnotations = null;
|
||||
}
|
||||
|
||||
// Reset the parseType.
|
||||
parseType = null;
|
||||
|
||||
// Handle static import definition types.
|
||||
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' ( 'from_class' | 'bound_to' ) ID '\n'
|
||||
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' ( 'from_class' | 'bound_to' ) ID annotations? '\n'
|
||||
} else if ("static_import".equals(parseType)) {
|
||||
// Mark the origin of this parsable object.
|
||||
String origin = "[" + filepath + "]:[" + number + "]";
|
||||
|
@ -274,8 +292,19 @@ public final class WhitelistLoader {
|
|||
canonicalTypeNameParameters = new String[0];
|
||||
}
|
||||
|
||||
// Parse the annotations if they exist.
|
||||
List<Object> annotations;
|
||||
int annotationIndex = line.indexOf('@');
|
||||
|
||||
if (annotationIndex == -1) {
|
||||
annotationIndex = line.length();
|
||||
annotations = Collections.emptyList();
|
||||
} else {
|
||||
annotations = parseWhitelistAnnotations(parsers, line.substring(annotationIndex));
|
||||
}
|
||||
|
||||
// Parse the static import type and class.
|
||||
tokens = line.substring(parameterEndIndex + 1).trim().split("\\s+");
|
||||
tokens = line.substring(parameterEndIndex + 1, annotationIndex).trim().split("\\s+");
|
||||
|
||||
String staticImportType;
|
||||
String targetJavaClassName;
|
||||
|
@ -291,10 +320,12 @@ public final class WhitelistLoader {
|
|||
// Add a static import method or binding depending on the static import type.
|
||||
if ("from_class".equals(staticImportType)) {
|
||||
whitelistStatics.add(new WhitelistMethod(origin, targetJavaClassName,
|
||||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
|
||||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters),
|
||||
annotations));
|
||||
} else if ("bound_to".equals(staticImportType)) {
|
||||
whitelistClassBindings.add(new WhitelistClassBinding(origin, targetJavaClassName,
|
||||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
|
||||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters),
|
||||
annotations));
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid static import definition: " +
|
||||
"unexpected static import type [" + staticImportType + "] [" + line + "]");
|
||||
|
@ -306,36 +337,38 @@ public final class WhitelistLoader {
|
|||
String origin = "[" + filepath + "]:[" + number + "]";
|
||||
|
||||
// Handle the case for a constructor definition.
|
||||
// Expects the following format: '(' ( ID ( ',' ID )* )? ')' '\n'
|
||||
// Expects the following format: '(' ( ID ( ',' ID )* )? ')' annotations? '\n'
|
||||
if (line.startsWith("(")) {
|
||||
// Ensure the final token of the line is ')'.
|
||||
if (line.endsWith(")") == false) {
|
||||
// Parse the constructor parameters.
|
||||
int parameterEndIndex = line.indexOf(')');
|
||||
|
||||
if (parameterEndIndex == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid constructor definition: expected a closing parenthesis [" + line + "]");
|
||||
"illegal constructor definition: end of constructor parameters not found [" + line + "]");
|
||||
}
|
||||
|
||||
// Parse the constructor parameters.
|
||||
String[] tokens = line.substring(1, line.length() - 1).replaceAll("\\s+", "").split(",");
|
||||
String[] canonicalTypeNameParameters = line.substring(1, parameterEndIndex).replaceAll("\\s+", "").split(",");
|
||||
|
||||
// Handle the case for a constructor with no parameters.
|
||||
if ("".equals(tokens[0])) {
|
||||
tokens = new String[0];
|
||||
if ("".equals(canonicalTypeNameParameters[0])) {
|
||||
canonicalTypeNameParameters = new String[0];
|
||||
}
|
||||
|
||||
whitelistConstructors.add(new WhitelistConstructor(origin, Arrays.asList(tokens)));
|
||||
// Parse the annotations if they exist.
|
||||
List<Object> annotations;
|
||||
int annotationIndex = line.indexOf('@');
|
||||
annotations = annotationIndex == -1 ?
|
||||
Collections.emptyList() : parseWhitelistAnnotations(parsers, line.substring(annotationIndex));
|
||||
|
||||
// Handle the case for a method or augmented method definition.
|
||||
// Expects the following format: ID ID? ID '(' ( ID ( ',' ID )* )? ')' '\n'
|
||||
whitelistConstructors.add(new WhitelistConstructor(
|
||||
origin, Arrays.asList(canonicalTypeNameParameters), annotations));
|
||||
|
||||
// Handle the case for a method or augmented method definition.
|
||||
// Expects the following format: ID ID? ID '(' ( ID ( ',' ID )* )? ')' annotations? '\n'
|
||||
} else if (line.contains("(")) {
|
||||
// Ensure the final token of the line is ')'.
|
||||
if (line.endsWith(")") == false) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid method definition: expected a closing parenthesis [" + line + "]");
|
||||
}
|
||||
|
||||
// Parse the tokens prior to the method parameters.
|
||||
int parameterIndex = line.indexOf('(');
|
||||
String[] tokens = line.substring(0, parameterIndex).trim().split("\\s+");
|
||||
int parameterStartIndex = line.indexOf('(');
|
||||
String[] tokens = line.substring(0, parameterStartIndex).trim().split("\\s+");
|
||||
|
||||
String methodName;
|
||||
String javaAugmentedClassName;
|
||||
|
@ -354,28 +387,54 @@ public final class WhitelistLoader {
|
|||
String returnCanonicalTypeName = tokens[0];
|
||||
|
||||
// Parse the method parameters.
|
||||
tokens = line.substring(parameterIndex + 1, line.length() - 1).replaceAll("\\s+", "").split(",");
|
||||
int parameterEndIndex = line.indexOf(')');
|
||||
|
||||
// Handle the case for a method with no parameters.
|
||||
if ("".equals(tokens[0])) {
|
||||
tokens = new String[0];
|
||||
if (parameterEndIndex == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"illegal static import definition: end of method parameters not found [" + line + "]");
|
||||
}
|
||||
|
||||
whitelistMethods.add(new WhitelistMethod(origin, javaAugmentedClassName, methodName,
|
||||
returnCanonicalTypeName, Arrays.asList(tokens)));
|
||||
String[] canonicalTypeNameParameters =
|
||||
line.substring(parameterStartIndex + 1, parameterEndIndex).replaceAll("\\s+", "").split(",");
|
||||
|
||||
// Handle the case for a field definition.
|
||||
// Expects the following format: ID ID '\n'
|
||||
// Handle the case for a method with no parameters.
|
||||
if ("".equals(canonicalTypeNameParameters[0])) {
|
||||
canonicalTypeNameParameters = new String[0];
|
||||
}
|
||||
|
||||
// Parse the annotations if they exist.
|
||||
List<Object> annotations;
|
||||
int annotationIndex = line.indexOf('@');
|
||||
annotations = annotationIndex == -1 ?
|
||||
Collections.emptyList() : parseWhitelistAnnotations(parsers, line.substring(annotationIndex));
|
||||
|
||||
whitelistMethods.add(new WhitelistMethod(origin, javaAugmentedClassName, methodName,
|
||||
returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters),
|
||||
annotations));
|
||||
|
||||
// Handle the case for a field definition.
|
||||
// Expects the following format: ID ID annotations? '\n'
|
||||
} else {
|
||||
// Parse the annotations if they exist.
|
||||
List<Object> annotations;
|
||||
int annotationIndex = line.indexOf('@');
|
||||
|
||||
if (annotationIndex == -1) {
|
||||
annotationIndex = line.length();
|
||||
annotations = Collections.emptyList();
|
||||
} else {
|
||||
annotations = parseWhitelistAnnotations(parsers, line.substring(annotationIndex));
|
||||
}
|
||||
|
||||
// Parse the field tokens.
|
||||
String[] tokens = line.split("\\s+");
|
||||
String[] tokens = line.substring(0, annotationIndex).split("\\s+");
|
||||
|
||||
// Ensure the correct number of tokens.
|
||||
if (tokens.length != 2) {
|
||||
throw new IllegalArgumentException("invalid field definition: unexpected format [" + line + "]");
|
||||
}
|
||||
|
||||
whitelistFields.add(new WhitelistField(origin, tokens[1], tokens[0]));
|
||||
whitelistFields.add(new WhitelistField(origin, tokens[1], tokens[0], annotations));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid definition: unable to parse line [" + line + "]");
|
||||
|
@ -396,5 +455,85 @@ public final class WhitelistLoader {
|
|||
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistClassBindings, Collections.emptyList());
|
||||
}
|
||||
|
||||
private static List<Object> parseWhitelistAnnotations(
|
||||
Map<String, WhitelistAnnotationParser> parsers, String line) {
|
||||
|
||||
List<Object> annotations;
|
||||
|
||||
line = line.trim();
|
||||
|
||||
if (line.isEmpty()) {
|
||||
annotations = Collections.emptyList();
|
||||
} else {
|
||||
if (line.charAt(0) != '@') {
|
||||
throw new IllegalArgumentException("invalid annotation: expected at symbol [" + line + "]");
|
||||
}
|
||||
|
||||
if (line.length() < 2) {
|
||||
throw new IllegalArgumentException("invalid annotation: expected name [" + line + "]");
|
||||
}
|
||||
|
||||
String[] annotationStrings = line.substring(1).split("@");
|
||||
annotations = new ArrayList<>(annotationStrings.length);
|
||||
|
||||
for (String annotationString : annotationStrings) {
|
||||
String name;
|
||||
Map<String, String> arguments;
|
||||
|
||||
annotationString = annotationString.trim();
|
||||
int index = annotationString.indexOf('[');
|
||||
|
||||
if (index == -1) {
|
||||
name = annotationString;
|
||||
arguments = Collections.emptyMap();
|
||||
} else {
|
||||
if (annotationString.charAt(annotationString.length() - 1) != ']') {
|
||||
throw new IllegalArgumentException("invalid annotation: expected closing brace [" + line + "]");
|
||||
}
|
||||
|
||||
name = annotationString.substring(0, index);
|
||||
arguments = new HashMap<>();
|
||||
|
||||
String[] argumentsStrings = annotationString.substring(index + 1, annotationString.length() - 1).split(",");
|
||||
|
||||
for (String argumentString : argumentsStrings) {
|
||||
String[] argumentKeyValue = argumentString.split("=");
|
||||
|
||||
if (argumentKeyValue.length != 2) {
|
||||
throw new IllegalArgumentException("invalid annotation: expected key=\"value\" [" + line + "]");
|
||||
}
|
||||
|
||||
String argumentKey = argumentKeyValue[0].trim();
|
||||
|
||||
if (argumentKey.isEmpty()) {
|
||||
throw new IllegalArgumentException("invalid annotation: expected key=\"value\" [" + line + "]");
|
||||
}
|
||||
|
||||
String argumentValue = argumentKeyValue[1];
|
||||
|
||||
if (argumentValue.length() < 3 || argumentValue.charAt(0) != '"' ||
|
||||
argumentValue.charAt(argumentValue.length() - 1) != '"') {
|
||||
throw new IllegalArgumentException("invalid annotation: expected key=\"value\" [" + line + "]");
|
||||
}
|
||||
|
||||
argumentValue = argumentValue.substring(1, argumentValue.length() - 1);
|
||||
|
||||
arguments.put(argumentKey, argumentValue);
|
||||
}
|
||||
}
|
||||
|
||||
WhitelistAnnotationParser parser = parsers.get(name);
|
||||
|
||||
if (parser == null) {
|
||||
throw new IllegalArgumentException("invalid annotation: parser not found for [" + name + "] [" + line + "]");
|
||||
}
|
||||
|
||||
annotations.add(parser.parse(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private WhitelistLoader() {}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
|
||||
package org.elasticsearch.painless.spi;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Method represents the equivalent of a Java method available as a whitelisted class method
|
||||
|
@ -61,18 +64,30 @@ public class WhitelistMethod {
|
|||
*/
|
||||
public final List<String> canonicalTypeNameParameters;
|
||||
|
||||
/** The {@link Map} of annotations for this method. */
|
||||
public final Map<Class<?>, Object> painlessAnnotations;
|
||||
|
||||
/**
|
||||
* Standard constructor. All values must be not {@code null} with the exception of
|
||||
* augmentedCanonicalClassName; augmentedCanonicalClassName will be {@code null} unless the method
|
||||
* is augmented as described in the class documentation.
|
||||
*/
|
||||
public WhitelistMethod(String origin, String augmentedCanonicalClassName, String methodName,
|
||||
String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||
String returnCanonicalTypeName, List<String> canonicalTypeNameParameters,
|
||||
List<Object> painlessAnnotations) {
|
||||
|
||||
this.origin = Objects.requireNonNull(origin);
|
||||
this.augmentedCanonicalClassName = augmentedCanonicalClassName;
|
||||
this.methodName = methodName;
|
||||
this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName);
|
||||
this.canonicalTypeNameParameters = Collections.unmodifiableList(Objects.requireNonNull(canonicalTypeNameParameters));
|
||||
|
||||
if (painlessAnnotations.isEmpty()) {
|
||||
this.painlessAnnotations = Collections.emptyMap();
|
||||
} else {
|
||||
this.painlessAnnotations = Collections.unmodifiableMap(Objects.requireNonNull(painlessAnnotations).stream()
|
||||
.map(painlessAnnotation -> new AbstractMap.SimpleEntry<>(painlessAnnotation.getClass(), painlessAnnotation))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.annotation;
|
||||
|
||||
public class DeprecatedAnnotation {
|
||||
|
||||
public static final String NAME = "deprecated";
|
||||
|
||||
private final String message;
|
||||
|
||||
public DeprecatedAnnotation(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DeprecatedAnnotationParser implements WhitelistAnnotationParser {
|
||||
|
||||
public static final DeprecatedAnnotationParser INSTANCE = new DeprecatedAnnotationParser();
|
||||
|
||||
public static final String MESSAGE = "message";
|
||||
|
||||
private DeprecatedAnnotationParser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(Map<String, String> arguments) {
|
||||
String message = arguments.getOrDefault(MESSAGE, "");
|
||||
|
||||
if ((arguments.isEmpty() || arguments.size() == 1 && arguments.containsKey(MESSAGE)) == false) {
|
||||
throw new IllegalArgumentException("unexpected parameters for [@deprecation] annotation, found " + arguments);
|
||||
}
|
||||
|
||||
return new DeprecatedAnnotation(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.annotation;
|
||||
|
||||
public class NoImportAnnotation {
|
||||
|
||||
public static final String NAME = "no_import";
|
||||
|
||||
public static final NoImportAnnotation INSTANCE = new NoImportAnnotation();
|
||||
|
||||
private NoImportAnnotation() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class NoImportAnnotationParser implements WhitelistAnnotationParser {
|
||||
|
||||
public static final NoImportAnnotationParser INSTANCE = new NoImportAnnotationParser();
|
||||
|
||||
private NoImportAnnotationParser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(Map<String, String> arguments) {
|
||||
if (arguments.isEmpty() == false) {
|
||||
throw new IllegalArgumentException("unexpected parameters for [@no_import] annotation, found " + arguments);
|
||||
}
|
||||
|
||||
return NoImportAnnotation.INSTANCE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* WhitelistAnnotationParser is an interface used to define how to
|
||||
* parse an annotation against any whitelist object while loading.
|
||||
*/
|
||||
public interface WhitelistAnnotationParser {
|
||||
|
||||
Map<String, WhitelistAnnotationParser> BASE_ANNOTATION_PARSERS = Collections.unmodifiableMap(
|
||||
Stream.of(
|
||||
new AbstractMap.SimpleEntry<>(NoImportAnnotation.NAME, NoImportAnnotationParser.INSTANCE),
|
||||
new AbstractMap.SimpleEntry<>(DeprecatedAnnotation.NAME, DeprecatedAnnotationParser.INSTANCE)
|
||||
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
);
|
||||
|
||||
Object parse(Map<String, String> arguments);
|
||||
}
|
|
@ -30,6 +30,7 @@ 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 org.elasticsearch.painless.spi.annotation.NoImportAnnotation;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.commons.GeneratorAdapter;
|
||||
|
@ -119,7 +120,8 @@ public final class PainlessLookupBuilder {
|
|||
for (WhitelistClass whitelistClass : whitelist.whitelistClasses) {
|
||||
origin = whitelistClass.origin;
|
||||
painlessLookupBuilder.addPainlessClass(
|
||||
whitelist.classLoader, whitelistClass.javaClassName, whitelistClass.noImport == false);
|
||||
whitelist.classLoader, whitelistClass.javaClassName,
|
||||
whitelistClass.painlessAnnotations.containsKey(NoImportAnnotation.class) == false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
# This file contains a whitelist for functions to be used in Score context
|
||||
|
||||
class org.elasticsearch.script.ScoreScript no_import {
|
||||
class org.elasticsearch.script.ScoreScript @no_import {
|
||||
}
|
||||
|
||||
static_import {
|
||||
|
@ -27,10 +27,10 @@ static_import {
|
|||
double sigmoid(double, double, double) from_class org.elasticsearch.script.ScoreScriptUtils
|
||||
double randomScore(org.elasticsearch.script.ScoreScript, int, String) bound_to org.elasticsearch.script.ScoreScriptUtils$RandomScoreField
|
||||
double randomScore(org.elasticsearch.script.ScoreScript, int) bound_to org.elasticsearch.script.ScoreScriptUtils$RandomScoreDoc
|
||||
double decayGeoLinear(String, String, String, double, GeoPoint) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayGeoLinear
|
||||
double decayGeoExp(String, String, String, double, GeoPoint) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayGeoExp
|
||||
double decayGeoGauss(String, String, String, double, GeoPoint) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayGeoGauss
|
||||
double decayNumericLinear(double, double, double, double, double) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayNumericLinear
|
||||
double decayGeoLinear(String, String, String, double, GeoPoint) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayGeoLinear
|
||||
double decayGeoExp(String, String, String, double, GeoPoint) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayGeoExp
|
||||
double decayGeoGauss(String, String, String, double, GeoPoint) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayGeoGauss
|
||||
double decayNumericLinear(double, double, double, double, double)bound_to org.elasticsearch.script.ScoreScriptUtils$DecayNumericLinear
|
||||
double decayNumericExp(double, double, double, double, double) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayNumericExp
|
||||
double decayNumericGauss(double, double, double, double, double) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayNumericGauss
|
||||
double decayDateLinear(String, String, String, double, JodaCompatibleZonedDateTime) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayDateLinear
|
||||
|
@ -38,4 +38,3 @@ static_import {
|
|||
double decayDateGauss(String, String, String, double, JodaCompatibleZonedDateTime) bound_to org.elasticsearch.script.ScoreScriptUtils$DecayDateGauss
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,31 +24,31 @@
|
|||
|
||||
#### Primitive types
|
||||
|
||||
class void no_import {
|
||||
class void @no_import {
|
||||
}
|
||||
|
||||
class boolean no_import {
|
||||
class boolean @no_import {
|
||||
}
|
||||
|
||||
class byte no_import {
|
||||
class byte @no_import {
|
||||
}
|
||||
|
||||
class short no_import {
|
||||
class short @no_import {
|
||||
}
|
||||
|
||||
class char no_import {
|
||||
class char @no_import {
|
||||
}
|
||||
|
||||
class int no_import {
|
||||
class int @no_import {
|
||||
}
|
||||
|
||||
class long no_import {
|
||||
class long @no_import {
|
||||
}
|
||||
|
||||
class float no_import {
|
||||
class float @no_import {
|
||||
}
|
||||
|
||||
class double no_import {
|
||||
class double @no_import {
|
||||
}
|
||||
|
||||
#### Painless debugging API
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.elasticsearch.painless.spi.annotation.WhitelistAnnotationParser;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AnnotationTestObject {
|
||||
|
||||
public static class TestAnnotation {
|
||||
|
||||
public static final String NAME = "test_annotation";
|
||||
|
||||
private final String one;
|
||||
private final String two;
|
||||
private final String three;
|
||||
|
||||
public TestAnnotation(String one, String two, String three) {
|
||||
this.one = one;
|
||||
this.two = two;
|
||||
this.three = three;
|
||||
}
|
||||
|
||||
public String getOne() {
|
||||
return one;
|
||||
}
|
||||
|
||||
public String getTwo() {
|
||||
return two;
|
||||
}
|
||||
|
||||
public String getThree() {
|
||||
return three;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestAnnotationParser implements WhitelistAnnotationParser {
|
||||
|
||||
public static final TestAnnotationParser INSTANCE = new TestAnnotationParser();
|
||||
|
||||
private TestAnnotationParser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(Map<String, String> arguments) {
|
||||
if (arguments.size() != 3) {
|
||||
throw new IllegalArgumentException("expected three arguments");
|
||||
}
|
||||
|
||||
String one = arguments.get("one");
|
||||
|
||||
if (one == null) {
|
||||
throw new IllegalArgumentException("missing one");
|
||||
}
|
||||
|
||||
String two = arguments.get("two");
|
||||
|
||||
if (two == null) {
|
||||
throw new IllegalArgumentException("missing two");
|
||||
}
|
||||
|
||||
String three = arguments.get("three");
|
||||
|
||||
if (three == null) {
|
||||
throw new IllegalArgumentException("missing three");
|
||||
}
|
||||
|
||||
return new TestAnnotation(one, two, three);
|
||||
}
|
||||
}
|
||||
|
||||
public void deprecatedMethod() {
|
||||
|
||||
}
|
||||
|
||||
public void annotatedTestMethod() {
|
||||
|
||||
}
|
||||
|
||||
public void annotatedMultipleMethod() {
|
||||
|
||||
}
|
||||
}
|
|
@ -103,9 +103,9 @@ public class BindingsTests extends ScriptTestCase {
|
|||
|
||||
InstanceBindingTestClass instanceBindingTestClass = new InstanceBindingTestClass(1);
|
||||
WhitelistInstanceBinding getter = new WhitelistInstanceBinding("test", instanceBindingTestClass,
|
||||
"setInstanceBindingValue", "void", Collections.singletonList("int"));
|
||||
"setInstanceBindingValue", "void", Collections.singletonList("int"), Collections.emptyList());
|
||||
WhitelistInstanceBinding setter = new WhitelistInstanceBinding("test", instanceBindingTestClass,
|
||||
"getInstanceBindingValue", "int", Collections.emptyList());
|
||||
"getInstanceBindingValue", "int", Collections.emptyList(), Collections.emptyList());
|
||||
List<WhitelistInstanceBinding> instanceBindingsList = new ArrayList<>();
|
||||
instanceBindingsList.add(getter);
|
||||
instanceBindingsList.add(setter);
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.elasticsearch.painless.spi.Whitelist;
|
||||
import org.elasticsearch.painless.spi.WhitelistClass;
|
||||
import org.elasticsearch.painless.spi.WhitelistLoader;
|
||||
import org.elasticsearch.painless.spi.WhitelistMethod;
|
||||
import org.elasticsearch.painless.spi.annotation.DeprecatedAnnotation;
|
||||
import org.elasticsearch.painless.spi.annotation.NoImportAnnotation;
|
||||
import org.elasticsearch.painless.spi.annotation.WhitelistAnnotationParser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class WhitelistLoaderTests extends ScriptTestCase {
|
||||
|
||||
public void testAnnotations() {
|
||||
Map<String, WhitelistAnnotationParser> parsers = new HashMap<>(WhitelistAnnotationParser.BASE_ANNOTATION_PARSERS);
|
||||
parsers.put(AnnotationTestObject.TestAnnotation.NAME, AnnotationTestObject.TestAnnotationParser.INSTANCE);
|
||||
Whitelist whitelist = WhitelistLoader.loadFromResourceFiles(Whitelist.class, parsers, "org.elasticsearch.painless.annotation");
|
||||
|
||||
assertEquals(1, whitelist.whitelistClasses.size());
|
||||
|
||||
WhitelistClass whitelistClass = whitelist.whitelistClasses.get(0);
|
||||
|
||||
assertNotNull(whitelistClass.painlessAnnotations.get(NoImportAnnotation.class));
|
||||
assertEquals(1, whitelistClass.painlessAnnotations.size());
|
||||
assertEquals(3, whitelistClass.whitelistMethods.size());
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (WhitelistMethod whitelistMethod : whitelistClass.whitelistMethods) {
|
||||
if ("deprecatedMethod".equals(whitelistMethod.methodName)) {
|
||||
assertEquals("use another method",
|
||||
((DeprecatedAnnotation)whitelistMethod.painlessAnnotations.get(DeprecatedAnnotation.class)).getMessage());
|
||||
assertEquals(1, whitelistMethod.painlessAnnotations.size());
|
||||
++count;
|
||||
}
|
||||
|
||||
if ("annotatedTestMethod".equals(whitelistMethod.methodName)) {
|
||||
AnnotationTestObject.TestAnnotation ta =
|
||||
((AnnotationTestObject.TestAnnotation)whitelistMethod.painlessAnnotations.get(
|
||||
AnnotationTestObject.TestAnnotation.class));
|
||||
assertEquals("one", ta.getOne());
|
||||
assertEquals("two", ta.getTwo());
|
||||
assertEquals("three", ta.getThree());
|
||||
assertEquals(1, whitelistMethod.painlessAnnotations.size());
|
||||
++count;
|
||||
}
|
||||
|
||||
if ("annotatedMultipleMethod".equals(whitelistMethod.methodName)) {
|
||||
assertEquals("test",
|
||||
((DeprecatedAnnotation)whitelistMethod.painlessAnnotations.get(DeprecatedAnnotation.class)).getMessage());
|
||||
AnnotationTestObject.TestAnnotation ta =
|
||||
((AnnotationTestObject.TestAnnotation)whitelistMethod.painlessAnnotations.get(
|
||||
AnnotationTestObject.TestAnnotation.class));
|
||||
assertEquals("one", ta.getOne());
|
||||
assertEquals("two", ta.getTwo());
|
||||
assertEquals("three", ta.getThree());
|
||||
assertEquals(2, whitelistMethod.painlessAnnotations.size());
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(3, count);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# whitelist for annotation tests
|
||||
|
||||
class org.elasticsearch.painless.AnnotationTestObject @no_import {
|
||||
void deprecatedMethod() @deprecated[message="use another method"]
|
||||
void annotatedTestMethod() @test_annotation[one="one",two="two",three="three"]
|
||||
void annotatedMultipleMethod() @test_annotation[one="one",two="two",three="three"] @deprecated[message="test"]
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
class org.elasticsearch.painless.BindingsTests$BindingsTestScript {
|
||||
}
|
||||
|
||||
class org.elasticsearch.painless.FeatureTestObject no_import {
|
||||
class org.elasticsearch.painless.FeatureTestObject @no_import {
|
||||
int z
|
||||
()
|
||||
(int,int)
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 ExamplePainlessAnnotation {
|
||||
|
||||
public static final String NAME = "example_annotation";
|
||||
|
||||
public int category;
|
||||
public String message;
|
||||
|
||||
public ExamplePainlessAnnotation(int category, String message) {
|
||||
this.category = category;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.elasticsearch.painless.spi.annotation.WhitelistAnnotationParser;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ExampleWhitelistAnnotationParser implements WhitelistAnnotationParser {
|
||||
|
||||
public static final ExampleWhitelistAnnotationParser INSTANCE = new ExampleWhitelistAnnotationParser();
|
||||
|
||||
private ExampleWhitelistAnnotationParser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse(Map<String, String> arguments) {
|
||||
if (arguments.size() != 2) {
|
||||
throw new IllegalArgumentException("expected exactly two arguments");
|
||||
}
|
||||
|
||||
String categoryString = arguments.get("category");
|
||||
|
||||
if (categoryString == null) {
|
||||
throw new IllegalArgumentException("expected category argument");
|
||||
}
|
||||
|
||||
int category;
|
||||
|
||||
try {
|
||||
category = Integer.parseInt(categoryString);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IllegalArgumentException("expected category as an int, found [" + categoryString + "]", nfe);
|
||||
}
|
||||
|
||||
String message = arguments.get("message");
|
||||
|
||||
if (categoryString == null) {
|
||||
throw new IllegalArgumentException("expected message argument");
|
||||
}
|
||||
|
||||
return new ExamplePainlessAnnotation(category, message);
|
||||
}
|
||||
}
|
|
@ -23,30 +23,34 @@ 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.painless.spi.annotation.WhitelistAnnotationParser;
|
||||
import org.elasticsearch.script.FieldScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** An extension of painless which adds a whitelist. */
|
||||
public class ExampleWhitelistExtension implements PainlessExtension {
|
||||
|
||||
private static final Whitelist WHITELIST =
|
||||
WhitelistLoader.loadFromResourceFiles(ExampleWhitelistExtension.class, "example_whitelist.txt");
|
||||
|
||||
@Override
|
||||
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
|
||||
Map<String, WhitelistAnnotationParser> parsers = new HashMap<>(WhitelistAnnotationParser.BASE_ANNOTATION_PARSERS);
|
||||
parsers.put(ExamplePainlessAnnotation.NAME, ExampleWhitelistAnnotationParser.INSTANCE);
|
||||
Whitelist classWhitelist =
|
||||
WhitelistLoader.loadFromResourceFiles(ExampleWhitelistExtension.class, parsers, "example_whitelist.txt");
|
||||
|
||||
ExampleWhitelistedInstance ewi = new ExampleWhitelistedInstance(1);
|
||||
WhitelistInstanceBinding addValue = new WhitelistInstanceBinding("example addValue", ewi,
|
||||
"addValue", "int", Collections.singletonList("int"));
|
||||
"addValue", "int", Collections.singletonList("int"), Collections.emptyList());
|
||||
WhitelistInstanceBinding getValue = new WhitelistInstanceBinding("example getValue", ewi,
|
||||
"getValue", "int", Collections.emptyList());
|
||||
"getValue", "int", Collections.emptyList(), 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));
|
||||
return Collections.singletonMap(FieldScript.CONTEXT, Arrays.asList(classWhitelist, instanceWhitelist));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,4 +54,9 @@ public class ExampleWhitelistedClass {
|
|||
public static int toInt(String x) {
|
||||
return Integer.parseInt(x);
|
||||
}
|
||||
|
||||
// example method to attach annotations in whitelist
|
||||
public void annotate() {
|
||||
// some logic here
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,9 @@ class org.elasticsearch.example.painlesswhitelist.ExampleWhitelistedClass {
|
|||
# getter and setter for private member
|
||||
int getPrivateMemberAccessor()
|
||||
void setPrivateMemberAccessor(int)
|
||||
|
||||
# annotation
|
||||
void annotate() @example_annotation[category="1",message="example annotation"]
|
||||
}
|
||||
|
||||
class java.lang.String {
|
||||
|
|
Loading…
Reference in New Issue