diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ee03c3d999c4..c591459f01bb7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,6 +100,12 @@ JDK 10 and testing on a JDK 8 runtime; to do this, set `RUNTIME_JAVA_HOME` pointing to the Java home of a JDK 8 installation. Note that this mechanism can be used to test against other JDKs as well, this is not only limited to JDK 8. +> Note: It is also required to have `JAVA7_HOME`, `JAVA8_HOME` and +`JAVA10_HOME` available so that the tests can pass. + +> Warning: do not use `sdkman` for Java installations which do not have proper +`jrunscript` for jdk distributions. + Elasticsearch uses the Gradle wrapper for its build. You can execute Gradle using the wrapper via the `gradlew` script in the root of the repository. diff --git a/README.textile b/README.textile index ce7b3b7d34476..aa61c03574275 100644 --- a/README.textile +++ b/README.textile @@ -209,10 +209,6 @@ The distribution for each project will be created under the @build/distributions See the "TESTING":TESTING.asciidoc file for more information about running the Elasticsearch test suite. -h3. Upgrading from Elasticsearch 1.x? +h3. Upgrading from older Elasticsearch versions -In order to ensure a smooth upgrade process from earlier versions of -Elasticsearch (1.x), it is required to perform a full cluster restart. Please -see the "setup reference": -https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-upgrade.html -for more details on the upgrade process. +In order to ensure a smooth upgrade process from earlier versions of Elasticsearch, please see our "upgrade documentation":https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-upgrade.html for more details on the upgrade process. diff --git a/Vagrantfile b/Vagrantfile index de344e1818360..7322399fed576 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -115,11 +115,6 @@ Vagrant.configure(2) do |config| 'opensuse-42'.tap do |box| config.vm.define box, define_opts do |config| config.vm.box = 'elastic/opensuse-42-x86_64' - - # https://github.com/elastic/elasticsearch/issues/30295 - config.vm.provider 'virtualbox' do |vbox| - vbox.customize ['storagectl', :id, '--name', 'SATA Controller', '--hostiocache', 'on'] - end suse_common config, box end end diff --git a/benchmarks/README.md b/benchmarks/README.md index 03aaac7f3c4e6..5be14bab0ecbc 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -4,36 +4,39 @@ This directory contains the microbenchmark suite of Elasticsearch. It relies on ## Purpose -We do not want to microbenchmark everything but the kitchen sink and should typically rely on our -[macrobenchmarks](https://elasticsearch-benchmarks.elastic.co/app/kibana#/dashboard/Nightly-Benchmark-Overview) with -[Rally](http://github.com/elastic/rally). Microbenchmarks are intended to spot performance regressions in performance-critical components. +We do not want to microbenchmark everything but the kitchen sink and should typically rely on our +[macrobenchmarks](https://elasticsearch-benchmarks.elastic.co/app/kibana#/dashboard/Nightly-Benchmark-Overview) with +[Rally](http://github.com/elastic/rally). Microbenchmarks are intended to spot performance regressions in performance-critical components. The microbenchmark suite is also handy for ad-hoc microbenchmarks but please remove them again before merging your PR. ## Getting Started -Just run `gradle :benchmarks:jmh` from the project root directory. It will build all microbenchmarks, execute them and print the result. +Just run `gradlew -p benchmarks run` from the project root +directory. It will build all microbenchmarks, execute them and print +the result. ## Running Microbenchmarks -Benchmarks are always run via Gradle with `gradle :benchmarks:jmh`. - -Running via an IDE is not supported as the results are meaningless (we have no control over the JVM running the benchmarks). +Running via an IDE is not supported as the results are meaningless +because we have no control over the JVM running the benchmarks. -If you want to run a specific benchmark class, e.g. `org.elasticsearch.benchmark.MySampleBenchmark` or have special requirements -generate the uberjar with `gradle :benchmarks:jmhJar` and run it directly with: +If you want to run a specific benchmark class like, say, +`MemoryStatsBenchmark`, you can use `--args`: ``` -java -jar benchmarks/build/distributions/elasticsearch-benchmarks-*.jar +gradlew -p benchmarks run --args ' MemoryStatsBenchmark' ``` -JMH supports lots of command line parameters. Add `-h` to the command above to see the available command line options. +Everything in the `'` gets sent on the command line to JMH. The leading ` ` +inside the `'`s is important. Without it parameters are sometimes sent to +gradle. ## Adding Microbenchmarks -Before adding a new microbenchmark, make yourself familiar with the JMH API. You can check our existing microbenchmarks and also the +Before adding a new microbenchmark, make yourself familiar with the JMH API. You can check our existing microbenchmarks and also the [JMH samples](http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/). -In contrast to tests, the actual name of the benchmark class is not relevant to JMH. However, stick to the naming convention and +In contrast to tests, the actual name of the benchmark class is not relevant to JMH. However, stick to the naming convention and end the class name of a benchmark with `Benchmark`. To have JMH execute a benchmark, annotate the respective methods with `@Benchmark`. ## Tips and Best Practices @@ -42,15 +45,15 @@ To get realistic results, you should exercise care when running benchmarks. Here ### Do -* Ensure that the system executing your microbenchmarks has as little load as possible. Shutdown every process that can cause unnecessary +* Ensure that the system executing your microbenchmarks has as little load as possible. Shutdown every process that can cause unnecessary runtime jitter. Watch the `Error` column in the benchmark results to see the run-to-run variance. * Ensure to run enough warmup iterations to get the benchmark into a stable state. If you are unsure, don't change the defaults. * Avoid CPU migrations by pinning your benchmarks to specific CPU cores. On Linux you can use `taskset`. -* Fix the CPU frequency to avoid Turbo Boost from kicking in and skewing your results. On Linux you can use `cpufreq-set` and the +* Fix the CPU frequency to avoid Turbo Boost from kicking in and skewing your results. On Linux you can use `cpufreq-set` and the `performance` CPU governor. * Vary the problem input size with `@Param`. * Use the integrated profilers in JMH to dig deeper if benchmark results to not match your hypotheses: - * Run the generated uberjar directly and use `-prof gc` to check whether the garbage collector runs during a microbenchmarks and skews + * Run the generated uberjar directly and use `-prof gc` to check whether the garbage collector runs during a microbenchmarks and skews your results. If so, try to force a GC between runs (`-gc true`) but watch out for the caveats. * Use `-prof perf` or `-prof perfasm` (both only available on Linux) to see hotspots. * Have your benchmarks peer-reviewed. @@ -59,4 +62,4 @@ To get realistic results, you should exercise care when running benchmarks. Here * Blindly believe the numbers that your microbenchmark produces but verify them by measuring e.g. with `-prof perfasm`. * Run more threads than your number of CPU cores (in case you run multi-threaded microbenchmarks). -* Look only at the `Score` column and ignore `Error`. Instead take countermeasures to keep `Error` low / variance explainable. \ No newline at end of file +* Look only at the `Score` column and ignore `Error`. Instead take countermeasures to keep `Error` low / variance explainable. diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 80d1982300dd1..0838af7287126 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -18,11 +18,8 @@ */ apply plugin: 'elasticsearch.build' - -// order of this section matters, see: https://github.com/johnrengelman/shadow/issues/336 -apply plugin: 'application' // have the shadow plugin provide the runShadow task +apply plugin: 'application' mainClassName = 'org.openjdk.jmh.Main' -apply plugin: 'com.github.johnrengelman.shadow' // build an uberjar with all benchmarks // Not published so no need to assemble tasks.remove(assemble) @@ -50,10 +47,8 @@ compileJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-u // needs to be added separately otherwise Gradle will quote it and javac will fail compileJava.options.compilerArgs.addAll(["-processor", "org.openjdk.jmh.generators.BenchmarkProcessor"]) -forbiddenApis { - // classes generated by JMH can use all sorts of forbidden APIs but we have no influence at all and cannot exclude these classes - ignoreFailures = true -} +// classes generated by JMH can use all sorts of forbidden APIs but we have no influence at all and cannot exclude these classes +forbiddenApisMain.enabled = false // No licenses for our benchmark deps (we don't ship benchmarks) dependencyLicenses.enabled = false @@ -69,20 +64,3 @@ thirdPartyAudit.excludes = [ 'org.openjdk.jmh.profile.HotspotRuntimeProfiler', 'org.openjdk.jmh.util.Utils' ] - -runShadow { - executable = new File(project.runtimeJavaHome, 'bin/java') -} - -// alias the shadowJar and runShadow tasks to abstract from the concrete plugin that we are using and provide a more consistent interface -task jmhJar( - dependsOn: shadowJar, - description: 'Generates an uberjar with the microbenchmarks and all dependencies', - group: 'Benchmark' -) - -task jmh( - dependsOn: runShadow, - description: 'Runs all microbenchmarks', - group: 'Benchmark' -) diff --git a/benchmarks/src/main/resources/log4j2.properties b/benchmarks/src/main/resources/log4j2.properties index c3ae1fe56d3d1..808d611670f31 100644 --- a/benchmarks/src/main/resources/log4j2.properties +++ b/benchmarks/src/main/resources/log4j2.properties @@ -1,7 +1,7 @@ appender.console.type = Console appender.console.name = console appender.console.layout.type = PatternLayout -appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%m%n +appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %m%n # Do not log at all if it is not really critical - we're in a benchmark rootLogger.level = error diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy index 5a962a5138b5f..306a2bcb58bd1 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy @@ -87,6 +87,8 @@ class BuildPlugin implements Plugin { project.pluginManager.apply('nebula.info-scm') project.pluginManager.apply('nebula.info-jar') + project.getTasks().create("buildResources", ExportElasticsearchBuildResourcesTask) + globalBuildInfo(project) configureRepositories(project) configureConfigurations(project) @@ -101,6 +103,7 @@ class BuildPlugin implements Plugin { configureDependenciesInfo(project) } + /** Performs checks on the build environment and prints information about the build environment. */ static void globalBuildInfo(Project project) { if (project.rootProject.ext.has('buildChecksDone') == false) { @@ -116,12 +119,14 @@ class BuildPlugin implements Plugin { final Map javaVersions = [:] for (int version = 7; version <= Integer.parseInt(minimumCompilerVersion.majorVersion); version++) { - javaVersions.put(version, findJavaHome(version)); + if(System.getenv(getJavaHomeEnvVarName(version.toString())) != null) { + javaVersions.put(version, findJavaHome(version.toString())); + } } String javaVendor = System.getProperty('java.vendor') - String javaVersion = System.getProperty('java.version') - String gradleJavaVersionDetails = "${javaVendor} ${javaVersion}" + + String gradleJavaVersion = System.getProperty('java.version') + String gradleJavaVersionDetails = "${javaVendor} ${gradleJavaVersion}" + " [${System.getProperty('java.vm.name')} ${System.getProperty('java.vm.version')}]" String compilerJavaVersionDetails = gradleJavaVersionDetails @@ -144,33 +149,33 @@ class BuildPlugin implements Plugin { // Build debugging info println '=======================================' println 'Elasticsearch Build Hamster says Hello!' - println '=======================================' println " Gradle Version : ${project.gradle.gradleVersion}" println " OS Info : ${System.getProperty('os.name')} ${System.getProperty('os.version')} (${System.getProperty('os.arch')})" if (gradleJavaVersionDetails != compilerJavaVersionDetails || gradleJavaVersionDetails != runtimeJavaVersionDetails) { - println " JDK Version (gradle) : ${gradleJavaVersionDetails}" - println " JAVA_HOME (gradle) : ${gradleJavaHome}" - println " JDK Version (compile) : ${compilerJavaVersionDetails}" - println " JAVA_HOME (compile) : ${compilerJavaHome}" - println " JDK Version (runtime) : ${runtimeJavaVersionDetails}" - println " JAVA_HOME (runtime) : ${runtimeJavaHome}" + println " Compiler JDK Version : ${getPaddedMajorVersion(compilerJavaVersionEnum)} (${compilerJavaVersionDetails})" + println " Compiler java.home : ${compilerJavaHome}" + println " Runtime JDK Version : ${getPaddedMajorVersion(runtimeJavaVersionEnum)} (${runtimeJavaVersionDetails})" + println " Runtime java.home : ${runtimeJavaHome}" + println " Gradle JDK Version : ${getPaddedMajorVersion(JavaVersion.toVersion(gradleJavaVersion))} (${gradleJavaVersionDetails})" + println " Gradle java.home : ${gradleJavaHome}" } else { - println " JDK Version : ${gradleJavaVersionDetails}" + println " JDK Version : ${getPaddedMajorVersion(JavaVersion.toVersion(gradleJavaVersion))} (${gradleJavaVersionDetails})" println " JAVA_HOME : ${gradleJavaHome}" } println " Random Testing Seed : ${project.testSeed}" + println '=======================================' // enforce Java version if (compilerJavaVersionEnum < minimumCompilerVersion) { final String message = - "the environment variable JAVA_HOME must be set to a JDK installation directory for Java ${minimumCompilerVersion}" + + "the compiler java.home must be set to a JDK installation directory for Java ${minimumCompilerVersion}" + " but is [${compilerJavaHome}] corresponding to [${compilerJavaVersionEnum}]" throw new GradleException(message) } if (runtimeJavaVersionEnum < minimumRuntimeVersion) { final String message = - "the environment variable RUNTIME_JAVA_HOME must be set to a JDK installation directory for Java ${minimumRuntimeVersion}" + + "the runtime java.home must be set to a JDK installation directory for Java ${minimumRuntimeVersion}" + " but is [${runtimeJavaHome}] corresponding to [${runtimeJavaVersionEnum}]" throw new GradleException(message) } @@ -205,6 +210,7 @@ class BuildPlugin implements Plugin { project.rootProject.ext.minimumCompilerVersion = minimumCompilerVersion project.rootProject.ext.minimumRuntimeVersion = minimumRuntimeVersion project.rootProject.ext.inFipsJvm = inFipsJvm + project.rootProject.ext.gradleJavaVersion = JavaVersion.toVersion(gradleJavaVersion) } project.targetCompatibility = project.rootProject.ext.minimumRuntimeVersion @@ -217,11 +223,20 @@ class BuildPlugin implements Plugin { project.ext.runtimeJavaVersion = project.rootProject.ext.runtimeJavaVersion project.ext.javaVersions = project.rootProject.ext.javaVersions project.ext.inFipsJvm = project.rootProject.ext.inFipsJvm + project.ext.gradleJavaVersion = project.rootProject.ext.gradleJavaVersion + } + + private static String getPaddedMajorVersion(JavaVersion compilerJavaVersionEnum) { + compilerJavaVersionEnum.getMajorVersion().toString().padLeft(2) } private static String findCompilerJavaHome() { - final String javaHome = System.getenv('JAVA_HOME') - if (javaHome == null) { + final String compilerJavaHome = System.getenv('JAVA_HOME') + final String compilerJavaProperty = System.getProperty('compiler.java') + if (compilerJavaProperty != null) { + compilerJavaHome = findJavaHome(compilerJavaProperty) + } + if (compilerJavaHome == null) { if (System.getProperty("idea.active") != null || System.getProperty("eclipse.launcher") != null) { // IntelliJ does not set JAVA_HOME, so we use the JDK that Gradle was run with return Jvm.current().javaHome @@ -233,11 +248,24 @@ class BuildPlugin implements Plugin { ) } } - return javaHome + return compilerJavaHome } - private static String findJavaHome(int version) { - return System.getenv('JAVA' + version + '_HOME') + private static String findJavaHome(String version) { + String versionedVarName = getJavaHomeEnvVarName(version) + String versionedJavaHome = System.getenv(versionedVarName); + if (versionedJavaHome == null) { + throw new GradleException( + "$versionedVarName must be set to build Elasticsearch. " + + "Note that if the variable was just set you might have to run `./gradlew --stop` for " + + "it to be picked up. See https://github.com/elastic/elasticsearch/issues/31399 details." + ) + } + return versionedJavaHome + } + + private static String getJavaHomeEnvVarName(String version) { + return 'JAVA' + version + '_HOME' } /** Add a check before gradle execution phase which ensures java home for the given java version is set. */ @@ -271,7 +299,10 @@ class BuildPlugin implements Plugin { } private static String findRuntimeJavaHome(final String compilerJavaHome) { - assert compilerJavaHome != null + String runtimeJavaProperty = System.getProperty("runtime.java") + if (runtimeJavaProperty != null) { + return findJavaHome(runtimeJavaProperty) + } return System.getenv('RUNTIME_JAVA_HOME') ?: compilerJavaHome } @@ -405,6 +436,10 @@ class BuildPlugin implements Plugin { repos.mavenLocal() } repos.mavenCentral() + repos.maven { + name "elastic" + url "https://artifacts.elastic.co/maven" + } String luceneVersion = VersionProperties.lucene if (luceneVersion.contains('-snapshot')) { // extract the revision number from the version with a regex matcher @@ -764,6 +799,12 @@ class BuildPlugin implements Plugin { systemProperty 'tests.task', path systemProperty 'tests.security.manager', 'true' systemProperty 'jna.nosys', 'true' + systemProperty 'compiler.java', project.ext.compilerJavaVersion.getMajorVersion() + if (project.ext.inFipsJvm) { + systemProperty 'runtime.java', project.ext.runtimeJavaVersion.getMajorVersion() + "FIPS" + } else { + systemProperty 'runtime.java', project.ext.runtimeJavaVersion.getMajorVersion() + } // TODO: remove setting logging level via system property systemProperty 'tests.logger.level', 'WARN' for (Map.Entry property : System.properties.entrySet()) { @@ -777,11 +818,19 @@ class BuildPlugin implements Plugin { systemProperty property.getKey(), property.getValue() } } + + // TODO: remove this once joda time is removed from scripting in 7.0 + systemProperty 'es.scripting.use_java_time', 'true' + + // TODO: remove this once ctx isn't added to update script params in 7.0 + systemProperty 'es.scripting.update.ctx_in_params', 'false' + // Set the system keystore/truststore password if we're running tests in a FIPS-140 JVM if (project.inFipsJvm) { systemProperty 'javax.net.ssl.trustStorePassword', 'password' systemProperty 'javax.net.ssl.keyStorePassword', 'password' } + boolean assertionsEnabled = Boolean.parseBoolean(System.getProperty('tests.asserts', 'true')) enableSystemAssertions assertionsEnabled enableAssertions assertionsEnabled diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/SnippetsTask.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/SnippetsTask.groovy index ec012633f0893..8c0eedeb6f547 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/SnippetsTask.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/doc/SnippetsTask.groovy @@ -284,6 +284,10 @@ public class SnippetsTask extends DefaultTask { contents.append(line).append('\n') return } + // Allow line continuations for console snippets within lists + if (snippet != null && line.trim() == '+') { + return + } // Just finished emit() } diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy index 3709805680d7a..598d82d5d9aa7 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy @@ -20,10 +20,14 @@ package org.elasticsearch.gradle.precommit import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis import de.thetaphi.forbiddenapis.gradle.ForbiddenApisPlugin +import org.elasticsearch.gradle.ExportElasticsearchBuildResourcesTask import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.FileCollection import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.plugins.quality.Checkstyle +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.StopExecutionException /** * Validation tasks which should be run before committing. These run before tests. @@ -40,7 +44,11 @@ class PrecommitTasks { project.tasks.create('licenseHeaders', LicenseHeadersTask.class), project.tasks.create('filepermissions', FilePermissionsTask.class), project.tasks.create('jarHell', JarHellTask.class), - project.tasks.create('thirdPartyAudit', ThirdPartyAuditTask.class)] + project.tasks.create('thirdPartyAudit', ThirdPartyAuditTask.class) + ] + + // Configure it but don't add it as a dependency yet + configureForbiddenApisCli(project) // tasks with just tests don't need dependency licenses, so this flag makes adding // the task optional @@ -96,9 +104,58 @@ class PrecommitTasks { } Task forbiddenApis = project.tasks.findByName('forbiddenApis') forbiddenApis.group = "" // clear group, so this does not show up under verification tasks + return forbiddenApis } + private static Task configureForbiddenApisCli(Project project) { + project.configurations.create("forbiddenApisCliJar") + project.dependencies { + forbiddenApisCliJar 'de.thetaphi:forbiddenapis:2.5' + } + Task forbiddenApisCli = project.tasks.create('forbiddenApisCli') + + project.sourceSets.forEach { sourceSet -> + forbiddenApisCli.dependsOn( + project.tasks.create(sourceSet.getTaskName('forbiddenApisCli', null), JavaExec) { + ExportElasticsearchBuildResourcesTask buildResources = project.tasks.getByName('buildResources') + dependsOn(buildResources) + classpath = project.files( + project.configurations.forbiddenApisCliJar, + sourceSet.compileClasspath, + sourceSet.runtimeClasspath + ) + main = 'de.thetaphi.forbiddenapis.cli.CliMain' + executable = "${project.runtimeJavaHome}/bin/java" + args "-b", 'jdk-unsafe-1.8' + args "-b", 'jdk-deprecated-1.8' + args "-b", 'jdk-non-portable' + args "-b", 'jdk-system-out' + args "-f", buildResources.copy("forbidden/jdk-signatures.txt") + args "-f", buildResources.copy("forbidden/es-all-signatures.txt") + args "--suppressannotation", '**.SuppressForbidden' + if (sourceSet.name == 'test') { + args "-f", buildResources.copy("forbidden/es-test-signatures.txt") + args "-f", buildResources.copy("forbidden/http-signatures.txt") + } else { + args "-f", buildResources.copy("forbidden/es-server-signatures.txt") + } + dependsOn sourceSet.classesTaskName + doFirst { + // Forbidden APIs expects only existing dirs, and requires at least one + FileCollection existingOutputs = sourceSet.output.classesDirs + .filter { it.exists() } + if (existingOutputs.isEmpty()) { + throw new StopExecutionException("${sourceSet.name} has no outputs") + } + existingOutputs.forEach { args "-d", it } + } + } + ) + } + return forbiddenApisCli + } + private static Task configureCheckstyle(Project project) { // Always copy the checkstyle configuration files to 'buildDir/checkstyle' since the resources could be located in a jar // file. If the resources are located in a jar, Gradle will fail when it tries to turn the URL into a file diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/StandaloneRestTestPlugin.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/StandaloneRestTestPlugin.groovy index 390821c80ff39..a2484e9c5fce0 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/StandaloneRestTestPlugin.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/test/StandaloneRestTestPlugin.groovy @@ -22,15 +22,14 @@ package org.elasticsearch.gradle.test import com.carrotsearch.gradle.junit4.RandomizedTestingPlugin import org.elasticsearch.gradle.BuildPlugin +import org.elasticsearch.gradle.ExportElasticsearchBuildResourcesTask import org.elasticsearch.gradle.VersionProperties import org.elasticsearch.gradle.precommit.PrecommitTasks import org.gradle.api.InvalidUserDataException import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.Task import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.tasks.compile.JavaCompile - /** * Configures the build to compile tests against Elasticsearch's test framework * and run REST tests. Use BuildPlugin if you want to build main code as well @@ -48,6 +47,7 @@ public class StandaloneRestTestPlugin implements Plugin { project.pluginManager.apply(JavaBasePlugin) project.pluginManager.apply(RandomizedTestingPlugin) + project.getTasks().create("buildResources", ExportElasticsearchBuildResourcesTask) BuildPlugin.globalBuildInfo(project) BuildPlugin.configureRepositories(project) diff --git a/buildSrc/src/main/java/org/elasticsearch/gradle/ExportElasticsearchBuildResourcesTask.java b/buildSrc/src/main/java/org/elasticsearch/gradle/ExportElasticsearchBuildResourcesTask.java new file mode 100644 index 0000000000000..03c18f54e67ef --- /dev/null +++ b/buildSrc/src/main/java/org/elasticsearch/gradle/ExportElasticsearchBuildResourcesTask.java @@ -0,0 +1,115 @@ +/* + * 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.gradle; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.StopExecutionException; +import org.gradle.api.tasks.TaskAction; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Export Elasticsearch build resources to configurable paths + *

+ * Wil overwrite existing files and create missing directories. + * Useful for resources that that need to be passed to other processes trough the filesystem or otherwise can't be + * consumed from the classpath. + */ +public class ExportElasticsearchBuildResourcesTask extends DefaultTask { + + private final Logger logger = Logging.getLogger(ExportElasticsearchBuildResourcesTask.class); + + private final Set resources = new HashSet<>(); + + private DirectoryProperty outputDir; + + public ExportElasticsearchBuildResourcesTask() { + outputDir = getProject().getLayout().directoryProperty( + getProject().getLayout().getBuildDirectory().dir("build-tools-exported") + ); + } + + @OutputDirectory + public DirectoryProperty getOutputDir() { + return outputDir; + } + + @Input + @SkipWhenEmpty + public Set getResources() { + return Collections.unmodifiableSet(resources); + } + + @Classpath + public String getResourcesClasspath() { + // This will make sure the task is not considered up to date if the resources are changed. + logger.info("Classpath: {}", System.getProperty("java.class.path")); + return System.getProperty("java.class.path"); + } + + public void setOutputDir(DirectoryProperty outputDir) { + this.outputDir = outputDir; + } + + public File copy(String resource) { + if (getState().getExecuted() || getState().getExecuting()) { + throw new GradleException("buildResources can't be configured after the task ran. " + + "Make sure task is not used after configuration time" + ); + } + resources.add(resource); + return outputDir.file(resource).get().getAsFile(); + } + + @TaskAction + public void doExport() { + if (resources.isEmpty()) { + throw new StopExecutionException(); + } + resources.stream().parallel() + .forEach(resourcePath -> { + Path destination = outputDir.get().file(resourcePath).getAsFile().toPath(); + try (InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath)) { + Files.createDirectories(destination.getParent()); + if (is == null) { + throw new GradleException("Can't export `" + resourcePath + "` from build-tools: not found"); + } + Files.copy(is, destination); + } catch (IOException e) { + throw new GradleException("Can't write resource `" + resourcePath + "` to " + destination, e); + } + }); + } + +} diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml index 8ab68b40c0a6b..420ed3b10b4c9 100644 --- a/buildSrc/src/main/resources/checkstyle_suppressions.xml +++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml @@ -686,6 +686,7 @@ + diff --git a/buildSrc/src/test/java/org/elasticsearch/gradle/ExportElasticsearchBuildResourcesTaskIT.java b/buildSrc/src/test/java/org/elasticsearch/gradle/ExportElasticsearchBuildResourcesTaskIT.java new file mode 100644 index 0000000000000..98fea2ea15ab6 --- /dev/null +++ b/buildSrc/src/test/java/org/elasticsearch/gradle/ExportElasticsearchBuildResourcesTaskIT.java @@ -0,0 +1,91 @@ +package org.elasticsearch.gradle; + +/* + * 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. + */ + +import org.elasticsearch.gradle.test.GradleIntegrationTestCase; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; + + +public class ExportElasticsearchBuildResourcesTaskIT extends GradleIntegrationTestCase { + + public static final String PROJECT_NAME = "elasticsearch-build-resources"; + + public void testUpToDateWithSourcesConfigured() { + GradleRunner.create() + .withProjectDir(getProjectDir(PROJECT_NAME)) + .withArguments("clean", "-s") + .withPluginClasspath() + .build(); + + BuildResult result = GradleRunner.create() + .withProjectDir(getProjectDir(PROJECT_NAME)) + .withArguments("buildResources", "-s", "-i") + .withPluginClasspath() + .build(); + assertTaskSuccessfull(result, ":buildResources"); + assertBuildFileExists(result, PROJECT_NAME, "build-tools-exported/checkstyle.xml"); + assertBuildFileExists(result, PROJECT_NAME, "build-tools-exported/checkstyle_suppressions.xml"); + + result = GradleRunner.create() + .withProjectDir(getProjectDir(PROJECT_NAME)) + .withArguments("buildResources", "-s", "-i") + .withPluginClasspath() + .build(); + assertTaskUpToDate(result, ":buildResources"); + assertBuildFileExists(result, PROJECT_NAME, "build-tools-exported/checkstyle.xml"); + assertBuildFileExists(result, PROJECT_NAME, "build-tools-exported/checkstyle_suppressions.xml"); + } + + public void testImplicitTaskDependencyCopy() { + BuildResult result = GradleRunner.create() + .withProjectDir(getProjectDir(PROJECT_NAME)) + .withArguments("clean", "sampleCopyAll", "-s", "-i") + .withPluginClasspath() + .build(); + + assertTaskSuccessfull(result, ":buildResources"); + assertTaskSuccessfull(result, ":sampleCopyAll"); + assertBuildFileExists(result, PROJECT_NAME, "sampleCopyAll/checkstyle.xml"); + // This is a side effect of compile time reference + assertBuildFileExists(result, PROJECT_NAME, "sampleCopyAll/checkstyle_suppressions.xml"); + } + + public void testImplicitTaskDependencyInputFileOfOther() { + BuildResult result = GradleRunner.create() + .withProjectDir(getProjectDir(PROJECT_NAME)) + .withArguments("clean", "sample", "-s", "-i") + .withPluginClasspath() + .build(); + + assertTaskSuccessfull(result, ":sample"); + assertBuildFileExists(result, PROJECT_NAME, "build-tools-exported/checkstyle.xml"); + assertBuildFileExists(result, PROJECT_NAME, "build-tools-exported/checkstyle_suppressions.xml"); + } + + public void testIncorrectUsage() { + BuildResult result = GradleRunner.create() + .withProjectDir(getProjectDir(PROJECT_NAME)) + .withArguments("noConfigAfterExecution", "-s", "-i") + .withPluginClasspath() + .buildAndFail(); + assertOutputContains("buildResources can't be configured after the task ran"); + } +} diff --git a/buildSrc/src/test/java/org/elasticsearch/gradle/test/GradleIntegrationTestCase.java b/buildSrc/src/test/java/org/elasticsearch/gradle/test/GradleIntegrationTestCase.java index 5c36fa61550d8..f00ab406a6c10 100644 --- a/buildSrc/src/test/java/org/elasticsearch/gradle/test/GradleIntegrationTestCase.java +++ b/buildSrc/src/test/java/org/elasticsearch/gradle/test/GradleIntegrationTestCase.java @@ -1,8 +1,13 @@ package org.elasticsearch.gradle.test; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -15,7 +20,7 @@ protected File getProjectDir(String name) { throw new RuntimeException("Could not find resources dir for integration tests. " + "Note that these tests can only be ran by Gradle and are not currently supported by the IDE"); } - return new File(root, name); + return new File(root, name).getAbsoluteFile(); } protected GradleRunner getGradleRunner(String sampleProject) { @@ -61,4 +66,47 @@ protected void assertOutputDoesNotContain(String output, String... lines) { } } + protected void assertTaskSuccessfull(BuildResult result, String taskName) { + BuildTask task = result.task(taskName); + if (task == null) { + fail("Expected task `" + taskName + "` to be successful, but it did not run"); + } + assertEquals( + "Expected task to be successful but it was: " + task.getOutcome() + + "\n\nOutput is:\n" + result.getOutput() , + TaskOutcome.SUCCESS, + task.getOutcome() + ); + } + + protected void assertTaskUpToDate(BuildResult result, String taskName) { + BuildTask task = result.task(taskName); + if (task == null) { + fail("Expected task `" + taskName + "` to be up-to-date, but it did not run"); + } + assertEquals( + "Expected task to be up to date but it was: " + task.getOutcome() + + "\n\nOutput is:\n" + result.getOutput() , + TaskOutcome.UP_TO_DATE, + task.getOutcome() + ); + } + + protected void assertBuildFileExists(BuildResult result, String projectName, String path) { + Path absPath = getBuildDir(projectName).toPath().resolve(path); + assertTrue( + result.getOutput() + "\n\nExpected `" + absPath + "` to exists but it did not" + + "\n\nOutput is:\n" + result.getOutput(), + Files.exists(absPath) + ); + } + + protected void assertBuildFileDoesNotExists(BuildResult result, String projectName, String path) { + Path absPath = getBuildDir(projectName).toPath().resolve(path); + assertFalse( + result.getOutput() + "\n\nExpected `" + absPath + "` bo to exists but it did" + + "\n\nOutput is:\n" + result.getOutput(), + Files.exists(absPath) + ); + } } diff --git a/buildSrc/src/testKit/elasticsearch-build-resources/build.gradle b/buildSrc/src/testKit/elasticsearch-build-resources/build.gradle new file mode 100644 index 0000000000000..95d1453025e92 --- /dev/null +++ b/buildSrc/src/testKit/elasticsearch-build-resources/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'elasticsearch.build' +} + +ext.licenseFile = file("$buildDir/dummy/license") +ext.noticeFile = file("$buildDir/dummy/notice") + +buildResources { + copy 'checkstyle.xml' +} + +task sampleCopyAll(type: Sync) { + /** Note: no explicit dependency. This works with tasks that use the Provider API a.k.a "Lazy Configuration" **/ + from buildResources + into "$buildDir/sampleCopyAll" +} + +task sample { + // This does not work, task dependencies can't be providers + // dependsOn buildResources.resource('minimumRuntimeVersion') + // Nor does this, despite https://github.com/gradle/gradle/issues/3811 + // dependsOn buildResources.outputDir + // for now it's just + dependsOn buildResources + // we have to refference it at configuration time in order to be picked up + ext.checkstyle_suppressions = buildResources.copy('checkstyle_suppressions.xml') + doLast { + println "This task is using ${file(checkstyle_suppressions)}" + } +} + +task noConfigAfterExecution { + dependsOn buildResources + doLast { + println "This should cause an error because we are refferencing " + + "${buildResources.copy('checkstyle_suppressions.xml')} after the `buildResources` task has ran." + } +} \ No newline at end of file diff --git a/buildSrc/version.properties b/buildSrc/version.properties index 023d5d5b8dc2b..34c266913d0a2 100644 --- a/buildSrc/version.properties +++ b/buildSrc/version.properties @@ -1,13 +1,15 @@ elasticsearch = 7.0.0-alpha1 -lucene = 7.5.0-snapshot-608f0277b0 +lucene = 7.5.0-snapshot-13b9e28f9d # optional dependencies spatial4j = 0.7 jts = 1.15.0 -jackson = 2.8.10 +jackson = 2.8.11 snakeyaml = 1.17 +icu4j = 62.1 +supercsv = 2.4.0 # when updating log4j, please update also docs/java-api/index.asciidoc -log4j = 2.9.1 +log4j = 2.11.1 slf4j = 1.6.2 # when updating the JNA version, also update the version in buildSrc/build.gradle diff --git a/client/benchmark/README.md b/client/benchmark/README.md index 06211b9d8fe8c..68a910468e0cd 100644 --- a/client/benchmark/README.md +++ b/client/benchmark/README.md @@ -2,10 +2,18 @@ 1. Build `client-benchmark-noop-api-plugin` with `gradle :client:client-benchmark-noop-api-plugin:assemble` 2. Install it on the target host with `bin/elasticsearch-plugin install file:///full/path/to/client-benchmark-noop-api-plugin.zip` -3. Start Elasticsearch on the target host (ideally *not* on the same machine) -4. Build an uberjar with `gradle :client:benchmark:shadowJar` and execute it. +3. Start Elasticsearch on the target host (ideally *not* on the machine +that runs the benchmarks) +4. Run the benchmark with +``` +./gradlew -p client/benchmark run --args ' params go here' +``` -Repeat all steps above for the other benchmark candidate. +Everything in the `'` gets sent on the command line to JMH. The leading ` ` +inside the `'`s is important. Without it parameters are sometimes sent to +gradle. + +See below for some example invocations. ### Example benchmark @@ -13,32 +21,35 @@ In general, you should define a few GC-related settings `-Xms8192M -Xmx8192M -XX #### Bulk indexing -Download benchmark data from http://benchmarks.elastic.co/corpora/geonames/documents.json.bz2 and decompress them. +Download benchmark data from http://benchmarks.elasticsearch.org.s3.amazonaws.com/corpora/geonames and decompress them. -Example command line parameters: +Example invocation: ``` -rest bulk 192.168.2.2 ./documents.json geonames type 8647880 5000 +wget http://benchmarks.elasticsearch.org.s3.amazonaws.com/corpora/geonames/documents-2.json.bz2 +bzip2 -d documents-2.json.bz2 +mv documents-2.json client/benchmark/build +gradlew -p client/benchmark run --args ' rest bulk localhost build/documents-2.json geonames type 8647880 5000' ``` -The parameters are in order: +The parameters are all in the `'`s and are in order: * Client type: Use either "rest" or "transport" * Benchmark type: Use either "bulk" or "search" * Benchmark target host IP (the host where Elasticsearch is running) * full path to the file that should be bulk indexed * name of the index -* name of the (sole) type in the index +* name of the (sole) type in the index * number of documents in the file * bulk size -#### Bulk indexing +#### Search -Example command line parameters: +Example invocation: ``` -rest search 192.168.2.2 geonames "{ \"query\": { \"match_phrase\": { \"name\": \"Sankt Georgen\" } } }\"" 500,1000,1100,1200 +gradlew -p client/benchmark run --args ' rest search localhost geonames {"query":{"match_phrase":{"name":"Sankt Georgen"}}} 500,1000,1100,1200' ``` The parameters are in order: @@ -49,5 +60,3 @@ The parameters are in order: * name of the index * a search request body (remember to escape double quotes). The `TransportClientBenchmark` uses `QueryBuilders.wrapperQuery()` internally which automatically adds a root key `query`, so it must not be present in the command line parameter. * A comma-separated list of target throughput rates - - diff --git a/client/benchmark/build.gradle b/client/benchmark/build.gradle index 0c3238d985346..c67120c7cf59b 100644 --- a/client/benchmark/build.gradle +++ b/client/benchmark/build.gradle @@ -18,9 +18,6 @@ */ apply plugin: 'elasticsearch.build' -// build an uberjar with all benchmarks -apply plugin: 'com.github.johnrengelman.shadow' -// have the shadow plugin provide the runShadow task apply plugin: 'application' group = 'org.elasticsearch.client' @@ -32,7 +29,6 @@ build.dependsOn.remove('assemble') archivesBaseName = 'client-benchmarks' mainClassName = 'org.elasticsearch.client.benchmark.BenchmarkMain' - // never try to invoke tests on the benchmark project - there aren't any test.enabled = false diff --git a/client/benchmark/src/main/resources/log4j2.properties b/client/benchmark/src/main/resources/log4j2.properties index 8652131bf4916..a68c7f5653f87 100644 --- a/client/benchmark/src/main/resources/log4j2.properties +++ b/client/benchmark/src/main/resources/log4j2.properties @@ -1,7 +1,7 @@ appender.console.type = Console appender.console.name = console appender.console.layout.type = PatternLayout -appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%m%n +appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %m%n rootLogger.level = info rootLogger.appenderRef.console.ref = console diff --git a/client/rest-high-level/build.gradle b/client/rest-high-level/build.gradle index 65c5d094c7170..6f5eab6e1db1e 100644 --- a/client/rest-high-level/build.gradle +++ b/client/rest-high-level/build.gradle @@ -30,6 +30,14 @@ apply plugin: 'com.github.johnrengelman.shadow' group = 'org.elasticsearch.client' archivesBaseName = 'elasticsearch-rest-high-level-client' +publishing { + publications { + nebula { + artifactId = archivesBaseName + } + } +} + //we need to copy the yaml spec so we can check naming (see RestHighlevelClientTests#testApiNamingConventions) Task copyRestSpec = RestIntegTestTask.createCopyRestSpecTask(project, Providers.FALSE) test.dependsOn(copyRestSpec) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IngestClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IngestClient.java index e889ec5beba80..99d50f6b46b7e 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IngestClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IngestClient.java @@ -26,7 +26,7 @@ import org.elasticsearch.action.ingest.PutPipelineRequest; import org.elasticsearch.action.ingest.SimulatePipelineRequest; import org.elasticsearch.action.ingest.SimulatePipelineResponse; -import org.elasticsearch.action.ingest.WritePipelineResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; import java.io.IOException; @@ -54,9 +54,9 @@ public final class IngestClient { * @return the response * @throws IOException in case there is a problem sending the request or parsing back the response */ - public WritePipelineResponse putPipeline(PutPipelineRequest request, RequestOptions options) throws IOException { + public AcknowledgedResponse putPipeline(PutPipelineRequest request, RequestOptions options) throws IOException { return restHighLevelClient.performRequestAndParseEntity( request, RequestConverters::putPipeline, options, - WritePipelineResponse::fromXContent, emptySet()); + AcknowledgedResponse::fromXContent, emptySet()); } /** @@ -67,9 +67,9 @@ public WritePipelineResponse putPipeline(PutPipelineRequest request, RequestOpti * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @param listener the listener to be notified upon request completion */ - public void putPipelineAsync(PutPipelineRequest request, RequestOptions options, ActionListener listener) { + public void putPipelineAsync(PutPipelineRequest request, RequestOptions options, ActionListener listener) { restHighLevelClient.performRequestAsyncAndParseEntity( request, RequestConverters::putPipeline, options, - WritePipelineResponse::fromXContent, listener, emptySet()); + AcknowledgedResponse::fromXContent, listener, emptySet()); } /** @@ -109,9 +109,9 @@ public void getPipelineAsync(GetPipelineRequest request, RequestOptions options, * @return the response * @throws IOException in case there is a problem sending the request or parsing back the response */ - public WritePipelineResponse deletePipeline(DeletePipelineRequest request, RequestOptions options) throws IOException { + public AcknowledgedResponse deletePipeline(DeletePipelineRequest request, RequestOptions options) throws IOException { return restHighLevelClient.performRequestAndParseEntity( request, RequestConverters::deletePipeline, options, - WritePipelineResponse::fromXContent, emptySet()); + AcknowledgedResponse::fromXContent, emptySet()); } /** @@ -123,9 +123,9 @@ public WritePipelineResponse deletePipeline(DeletePipelineRequest request, Reque * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @param listener the listener to be notified upon request completion */ - public void deletePipelineAsync(DeletePipelineRequest request, RequestOptions options, ActionListener listener) { + public void deletePipelineAsync(DeletePipelineRequest request, RequestOptions options, ActionListener listener) { restHighLevelClient.performRequestAsyncAndParseEntity( request, RequestConverters::deletePipeline, options, - WritePipelineResponse::fromXContent, listener, emptySet()); + AcknowledgedResponse::fromXContent, listener, emptySet()); } /** diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java index 587578f3b35e1..589f187f397aa 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java @@ -19,11 +19,27 @@ package org.elasticsearch.client; +import org.apache.http.HttpEntity; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.xcontent.DeprecationHandler; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.protocol.xpack.license.DeleteLicenseRequest; +import org.elasticsearch.protocol.xpack.license.DeleteLicenseResponse; +import org.elasticsearch.protocol.xpack.license.GetLicenseRequest; +import org.elasticsearch.protocol.xpack.license.GetLicenseResponse; import org.elasticsearch.protocol.xpack.license.PutLicenseRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import static java.util.Collections.emptySet; @@ -34,7 +50,7 @@ * See the * X-Pack Licensing APIs on elastic.co for more information. */ -public class LicenseClient { +public final class LicenseClient { private final RestHighLevelClient restHighLevelClient; @@ -54,7 +70,7 @@ public PutLicenseResponse putLicense(PutLicenseRequest request, RequestOptions o } /** - * Asynchronously updates license for the cluster cluster. + * Asynchronously updates license for the cluster. * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @param listener the listener to be notified upon request completion */ @@ -63,4 +79,79 @@ public void putLicenseAsync(PutLicenseRequest request, RequestOptions options, A PutLicenseResponse::fromXContent, listener, emptySet()); } + /** + * Returns the current license for the cluster. + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public GetLicenseResponse getLicense(GetLicenseRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequest(request, RequestConverters::getLicense, options, + response -> new GetLicenseResponse(convertResponseToJson(response)), emptySet()); + } + + /** + * Asynchronously returns the current license for the cluster cluster. + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public void getLicenseAsync(GetLicenseRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsync(request, RequestConverters::getLicense, options, + response -> new GetLicenseResponse(convertResponseToJson(response)), listener, emptySet()); + } + + /** + * Deletes license from the cluster. + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public DeleteLicenseResponse deleteLicense(DeleteLicenseRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, RequestConverters::deleteLicense, options, + DeleteLicenseResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously deletes license from the cluster. + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public void deleteLicenseAsync(DeleteLicenseRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, RequestConverters::deleteLicense, options, + DeleteLicenseResponse::fromXContent, listener, emptySet()); + } + + /** + * Converts an entire response into a json string + * + * This is useful for responses that we don't parse on the client side, but instead work as string + * such as in case of the license JSON + */ + static String convertResponseToJson(Response response) throws IOException { + HttpEntity entity = response.getEntity(); + if (entity == null) { + throw new IllegalStateException("Response body expected but not returned"); + } + if (entity.getContentType() == null) { + throw new IllegalStateException("Elasticsearch didn't return the [Content-Type] header, unable to parse response body"); + } + XContentType xContentType = XContentType.fromMediaTypeOrFormat(entity.getContentType().getValue()); + if (xContentType == null) { + throw new IllegalStateException("Unsupported Content-Type: " + entity.getContentType().getValue()); + } + if (xContentType == XContentType.JSON) { + // No changes is required + return Streams.copyToString(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8)); + } else { + // Need to convert into JSON + try (InputStream stream = response.getEntity().getContent(); + XContentParser parser = XContentFactory.xContent(xContentType).createParser(NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, stream)) { + parser.nextToken(); + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.copyCurrentStructure(parser); + return Strings.toString(builder); + } + } + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java new file mode 100644 index 0000000000000..5244432a89407 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java @@ -0,0 +1,80 @@ +/* + * 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.client; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.protocol.xpack.ml.PutJobRequest; +import org.elasticsearch.protocol.xpack.ml.PutJobResponse; + +import java.io.IOException; +import java.util.Collections; + +/** + * Machine Learning API client wrapper for the {@link RestHighLevelClient} + * + *

+ * See the + * X-Pack Machine Learning APIs for additional information. + */ +public final class MachineLearningClient { + + private final RestHighLevelClient restHighLevelClient; + + MachineLearningClient(RestHighLevelClient restHighLevelClient) { + this.restHighLevelClient = restHighLevelClient; + } + + /** + * Creates a new Machine Learning Job + *

+ * For additional info + * see ML PUT job documentation + * + * @param request the PutJobRequest containing the {@link org.elasticsearch.protocol.xpack.ml.job.config.Job} settings + * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return PutJobResponse with enclosed {@link org.elasticsearch.protocol.xpack.ml.job.config.Job} object + * @throws IOException when there is a serialization issue sending the request or receiving the response + */ + public PutJobResponse putJob(PutJobRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, + RequestConverters::putMachineLearningJob, + options, + PutJobResponse::fromXContent, + Collections.emptySet()); + } + + /** + * Creates a new Machine Learning Job asynchronously and notifies listener on completion + *

+ * For additional info + * see ML PUT job documentation + * + * @param request the request containing the {@link org.elasticsearch.protocol.xpack.ml.job.config.Job} settings + * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener Listener to be notified upon request completion + */ + public void putJobAsync(PutJobRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, + RequestConverters::putMachineLearningJob, + options, + PutJobResponse::fromXContent, + listener, + Collections.emptySet()); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MigrationClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MigrationClient.java new file mode 100644 index 0000000000000..7da3832994768 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MigrationClient.java @@ -0,0 +1,55 @@ +/* + * 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.client; + +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoRequest; +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoResponse; + +import java.io.IOException; +import java.util.Collections; + +/** + * A wrapper for the {@link RestHighLevelClient} that provides methods for + * accessing the Elastic License-related methods + *

+ * See the + * X-Pack Migration APIs on elastic.co for more information. + */ +public final class MigrationClient { + + private final RestHighLevelClient restHighLevelClient; + + MigrationClient(RestHighLevelClient restHighLevelClient) { + this.restHighLevelClient = restHighLevelClient; + } + + /** + * Get Migration Assistance for one or more indices + * + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public IndexUpgradeInfoResponse getAssistance(IndexUpgradeInfoRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, RequestConverters::getMigrationAssistance, options, + IndexUpgradeInfoResponse::fromXContent, Collections.emptySet()); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index b9360877dfcad..45c70593fe826 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -39,12 +39,12 @@ import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; +import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; -import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest; -import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest; @@ -78,8 +78,8 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.ingest.DeletePipelineRequest; import org.elasticsearch.action.ingest.GetPipelineRequest; -import org.elasticsearch.action.ingest.SimulatePipelineRequest; import org.elasticsearch.action.ingest.PutPipelineRequest; +import org.elasticsearch.action.ingest.SimulatePipelineRequest; import org.elasticsearch.action.search.ClearScrollRequest; import org.elasticsearch.action.search.MultiSearchRequest; import org.elasticsearch.action.search.SearchRequest; @@ -96,7 +96,7 @@ import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContent; @@ -107,9 +107,14 @@ import org.elasticsearch.index.VersionType; import org.elasticsearch.index.rankeval.RankEvalRequest; import org.elasticsearch.protocol.xpack.XPackInfoRequest; -import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.protocol.xpack.XPackUsageRequest; +import org.elasticsearch.protocol.xpack.license.DeleteLicenseRequest; +import org.elasticsearch.protocol.xpack.license.GetLicenseRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseRequest; +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoRequest; +import org.elasticsearch.protocol.xpack.ml.PutJobRequest; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest; +import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.script.mustache.MultiSearchTemplateRequest; import org.elasticsearch.script.mustache.SearchTemplateRequest; @@ -421,8 +426,14 @@ static Request bulk(BulkRequest bulkRequest) throws IOException { BytesReference indexSource = indexRequest.source(); XContentType indexXContentType = indexRequest.getContentType(); - try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, indexSource, indexXContentType)) { + try (XContentParser parser = XContentHelper.createParser( + /* + * EMPTY and THROW are fine here because we just call + * copyCurrentStructure which doesn't touch the + * registry or deprecation. + */ + NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + indexSource, indexXContentType)) { try (XContentBuilder builder = XContentBuilder.builder(bulkContentType.xContent())) { builder.copyCurrentStructure(parser); source = BytesReference.bytes(builder).toBytesRef(); @@ -1133,6 +1144,18 @@ static Request xPackWatcherPutWatch(PutWatchRequest putWatchRequest) { return request; } + static Request xPackWatcherDeleteWatch(DeleteWatchRequest deleteWatchRequest) { + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("watcher") + .addPathPartAsIs("watch") + .addPathPart(deleteWatchRequest.getId()) + .build(); + + Request request = new Request(HttpDelete.METHOD_NAME, endpoint); + return request; + } + static Request xpackUsage(XPackUsageRequest usageRequest) { Request request = new Request(HttpGet.METHOD_NAME, "/_xpack/usage"); Params parameters = new Params(request); @@ -1141,7 +1164,11 @@ static Request xpackUsage(XPackUsageRequest usageRequest) { } static Request putLicense(PutLicenseRequest putLicenseRequest) { - Request request = new Request(HttpPut.METHOD_NAME, "/_xpack/license"); + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("license") + .build(); + Request request = new Request(HttpPut.METHOD_NAME, endpoint); Params parameters = new Params(request); parameters.withTimeout(putLicenseRequest.timeout()); parameters.withMasterTimeout(putLicenseRequest.masterNodeTimeout()); @@ -1152,6 +1179,48 @@ static Request putLicense(PutLicenseRequest putLicenseRequest) { return request; } + static Request getLicense(GetLicenseRequest getLicenseRequest) { + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("license") + .build(); + Request request = new Request(HttpGet.METHOD_NAME, endpoint); + Params parameters = new Params(request); + parameters.withLocal(getLicenseRequest.local()); + return request; + } + + static Request deleteLicense(DeleteLicenseRequest deleteLicenseRequest) { + Request request = new Request(HttpDelete.METHOD_NAME, "/_xpack/license"); + Params parameters = new Params(request); + parameters.withTimeout(deleteLicenseRequest.timeout()); + parameters.withMasterTimeout(deleteLicenseRequest.masterNodeTimeout()); + return request; + } + + static Request putMachineLearningJob(PutJobRequest putJobRequest) throws IOException { + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("ml") + .addPathPartAsIs("anomaly_detectors") + .addPathPart(putJobRequest.getJob().getId()) + .build(); + Request request = new Request(HttpPut.METHOD_NAME, endpoint); + request.setEntity(createEntity(putJobRequest, REQUEST_BODY_CONTENT_TYPE)); + return request; + } + + static Request getMigrationAssistance(IndexUpgradeInfoRequest indexUpgradeInfoRequest) { + EndpointBuilder endpointBuilder = new EndpointBuilder() + .addPathPartAsIs("_xpack/migration/assistance") + .addCommaSeparatedPathParts(indexUpgradeInfoRequest.indices()); + String endpoint = endpointBuilder.build(); + Request request = new Request(HttpGet.METHOD_NAME, endpoint); + Params parameters = new Params(request); + parameters.withIndicesOptions(indexUpgradeInfoRequest.indicesOptions()); + return request; + } + private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException { BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef(); return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType)); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index c71bebf6903ca..50d7465ae334a 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -59,7 +59,7 @@ import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.xcontent.ContextParser; -import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; @@ -163,8 +163,11 @@ import org.elasticsearch.search.aggregations.pipeline.derivative.ParsedDerivative; import org.elasticsearch.search.suggest.Suggest; import org.elasticsearch.search.suggest.completion.CompletionSuggestion; +import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder; import org.elasticsearch.search.suggest.phrase.PhraseSuggestion; +import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder; import org.elasticsearch.search.suggest.term.TermSuggestion; +import org.elasticsearch.search.suggest.term.TermSuggestionBuilder; import java.io.Closeable; import java.io.IOException; @@ -205,6 +208,10 @@ public class RestHighLevelClient implements Closeable { private final SnapshotClient snapshotClient = new SnapshotClient(this); private final TasksClient tasksClient = new TasksClient(this); private final XPackClient xPackClient = new XPackClient(this); + private final WatcherClient watcherClient = new WatcherClient(this); + private final LicenseClient licenseClient = new LicenseClient(this); + private final MigrationClient migrationClient = new MigrationClient(this); + private final MachineLearningClient machineLearningClient = new MachineLearningClient(this); /** * Creates a {@link RestHighLevelClient} given the low level {@link RestClientBuilder} that allows to build the @@ -296,18 +303,64 @@ public final TasksClient tasks() { } /** - * A wrapper for the {@link RestHighLevelClient} that provides methods for - * accessing the Elastic Licensed X-Pack APIs that are shipped with the - * default distribution of Elasticsearch. All of these APIs will 404 if run - * against the OSS distribution of Elasticsearch. + * Provides methods for accessing the Elastic Licensed X-Pack Info + * and Usage APIs that are shipped with the default distribution of + * Elasticsearch. All of these APIs will 404 if run against the OSS + * distribution of Elasticsearch. *

- * See the - * X-Pack APIs on elastic.co for more information. + * See the + * Info APIs on elastic.co for more information. */ public final XPackClient xpack() { return xPackClient; } + /** + * Provides methods for accessing the Elastic Licensed Watcher APIs that + * are shipped with the default distribution of Elasticsearch. All of + * these APIs will 404 if run against the OSS distribution of Elasticsearch. + *

+ * See the + * Watcher APIs on elastic.co for more information. + */ + public WatcherClient watcher() { return watcherClient; } + + /** + * Provides methods for accessing the Elastic Licensed Licensing APIs that + * are shipped with the default distribution of Elasticsearch. All of + * these APIs will 404 if run against the OSS distribution of Elasticsearch. + *

+ * See the + * Licensing APIs on elastic.co for more information. + */ + public LicenseClient license() { return licenseClient; } + + /** + * Provides methods for accessing the Elastic Licensed Licensing APIs that + * are shipped with the default distribution of Elasticsearch. All of + * these APIs will 404 if run against the OSS distribution of Elasticsearch. + *

+ * See the + * Migration APIs on elastic.co for more information. + */ + public MigrationClient migration() { + return migrationClient; + } + + /** + * Provides methods for accessing the Elastic Licensed Machine Learning APIs that + * are shipped with the Elastic Stack distribution of Elasticsearch. All of + * these APIs will 404 if run against the OSS distribution of Elasticsearch. + *

+ * See the + * Machine Learning APIs on elastic.co for more information. + * + * @return the client wrapper for making Machine Learning API calls + */ + public MachineLearningClient machineLearning() { + return machineLearningClient; + } + /** * Executes a bulk request using the Bulk API. * See Bulk API on elastic.co @@ -1050,8 +1103,7 @@ protected final Resp parseEntity(final HttpEntity entity, if (xContentType == null) { throw new IllegalStateException("Unsupported Content-Type: " + entity.getContentType().getValue()); } - try (XContentParser parser = xContentType.xContent().createParser(registry, - LoggingDeprecationHandler.INSTANCE, entity.getContent())) { + try (XContentParser parser = xContentType.xContent().createParser(registry, DEPRECATION_HANDLER, entity.getContent())) { return entityParser.apply(parser); } } @@ -1069,6 +1121,19 @@ static boolean convertExistsResponse(Response response) { return response.getStatusLine().getStatusCode() == 200; } + /** + * Ignores deprecation warnings. This is appropriate because it is only + * used to parse responses from Elasticsearch. Any deprecation warnings + * emitted there just mean that you are talking to an old version of + * Elasticsearch. There isn't anything you can do about the deprecation. + */ + private static final DeprecationHandler DEPRECATION_HANDLER = new DeprecationHandler() { + @Override + public void usedDeprecatedName(String usedName, String modernName) {} + @Override + public void usedDeprecatedField(String usedName, String replacedWith) {} + }; + static List getDefaultNamedXContents() { Map> map = new HashMap<>(); map.put(CardinalityAggregationBuilder.NAME, (p, c) -> ParsedCardinality.fromXContent(p, (String) c)); @@ -1119,11 +1184,11 @@ static List getDefaultNamedXContents() { List entries = map.entrySet().stream() .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); - entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestion.NAME), + entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestionBuilder.SUGGESTION_NAME), (parser, context) -> TermSuggestion.fromXContent(parser, (String)context))); - entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(PhraseSuggestion.NAME), + entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(PhraseSuggestionBuilder.SUGGESTION_NAME), (parser, context) -> PhraseSuggestion.fromXContent(parser, (String)context))); - entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(CompletionSuggestion.NAME), + entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(CompletionSuggestionBuilder.SUGGESTION_NAME), (parser, context) -> CompletionSuggestion.fromXContent(parser, (String)context))); return entries; } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java index 73c92ba5c45d5..48487926f024b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java @@ -19,12 +19,15 @@ package org.elasticsearch.client; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchResponse; import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.protocol.xpack.watcher.PutWatchResponse; import java.io.IOException; import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; public final class WatcherClient { @@ -61,4 +64,31 @@ public void putWatchAsync(PutWatchRequest request, RequestOptions options, restHighLevelClient.performRequestAsyncAndParseEntity(request, RequestConverters::xPackWatcherPutWatch, options, PutWatchResponse::fromXContent, listener, emptySet()); } + + /** + * Deletes a watch from the cluster + * See + * the docs for more. + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public DeleteWatchResponse deleteWatch(DeleteWatchRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, RequestConverters::xPackWatcherDeleteWatch, options, + DeleteWatchResponse::fromXContent, singleton(404)); + } + + /** + * Asynchronously deletes a watch from the cluster + * See + * the docs for more. + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public void deleteWatchAsync(DeleteWatchRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, RequestConverters::xPackWatcherDeleteWatch, options, + DeleteWatchResponse::fromXContent, listener, singleton(404)); + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/XPackClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/XPackClient.java index 1401376527df2..2af49ba1a1b73 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/XPackClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/XPackClient.java @@ -41,17 +41,9 @@ public final class XPackClient { private final RestHighLevelClient restHighLevelClient; - private final WatcherClient watcherClient; - private final LicenseClient licenseClient; XPackClient(RestHighLevelClient restHighLevelClient) { this.restHighLevelClient = restHighLevelClient; - this.watcherClient = new WatcherClient(restHighLevelClient); - this.licenseClient = new LicenseClient(restHighLevelClient); - } - - public WatcherClient watcher() { - return watcherClient; } /** @@ -102,15 +94,4 @@ public void usageAsync(XPackUsageRequest request, RequestOptions options, Action restHighLevelClient.performRequestAsyncAndParseEntity(request, RequestConverters::xpackUsage, options, XPackUsageResponse::fromXContent, listener, emptySet()); } - - /** - * A wrapper for the {@link RestHighLevelClient} that provides methods for - * accessing the Elastic Licensing APIs. - *

- * See the - * X-Pack APIs on elastic.co for more information. - */ - public LicenseClient license() { - return licenseClient; - } } diff --git a/client/rest-high-level/src/main/resources/forbidden/rest-high-level-signatures.txt b/client/rest-high-level/src/main/resources/forbidden/rest-high-level-signatures.txt index fb2330f3f083c..33e136a66f44f 100644 --- a/client/rest-high-level/src/main/resources/forbidden/rest-high-level-signatures.txt +++ b/client/rest-high-level/src/main/resources/forbidden/rest-high-level-signatures.txt @@ -19,3 +19,6 @@ org.apache.http.entity.ContentType#create(java.lang.String) org.apache.http.entity.ContentType#create(java.lang.String,java.lang.String) org.apache.http.entity.ContentType#create(java.lang.String,java.nio.charset.Charset) org.apache.http.entity.ContentType#create(java.lang.String,org.apache.http.NameValuePair[]) + +@defaultMessage We can't rely on log4j2 being on the classpath so don't log deprecations! +org.elasticsearch.common.xcontent.LoggingDeprecationHandler diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/IngestClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/IngestClientIT.java index 1f5914f392cf4..7068529619232 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/IngestClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/IngestClientIT.java @@ -28,7 +28,7 @@ import org.elasticsearch.action.ingest.SimulateDocumentVerboseResult; import org.elasticsearch.action.ingest.SimulatePipelineRequest; import org.elasticsearch.action.ingest.SimulatePipelineResponse; -import org.elasticsearch.action.ingest.WritePipelineResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; @@ -50,7 +50,7 @@ public void testPutPipeline() throws IOException { BytesReference.bytes(pipelineBuilder), pipelineBuilder.contentType()); - WritePipelineResponse putPipelineResponse = + AcknowledgedResponse putPipelineResponse = execute(request, highLevelClient().ingest()::putPipeline, highLevelClient().ingest()::putPipelineAsync); assertTrue(putPipelineResponse.isAcknowledged()); } @@ -86,7 +86,7 @@ public void testDeletePipeline() throws IOException { DeletePipelineRequest request = new DeletePipelineRequest(id); - WritePipelineResponse response = + AcknowledgedResponse response = execute(request, highLevelClient().ingest()::deletePipeline, highLevelClient().ingest()::deletePipelineAsync); assertTrue(response.isAcknowledged()); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java new file mode 100644 index 0000000000000..f86eb5b5dca87 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java @@ -0,0 +1,74 @@ +/* + * 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.client; + +import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.protocol.xpack.ml.PutJobRequest; +import org.elasticsearch.protocol.xpack.ml.PutJobResponse; +import org.elasticsearch.protocol.xpack.ml.job.config.AnalysisConfig; +import org.elasticsearch.protocol.xpack.ml.job.config.DataDescription; +import org.elasticsearch.protocol.xpack.ml.job.config.Detector; +import org.elasticsearch.protocol.xpack.ml.job.config.Job; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.is; + +public class MachineLearningIT extends ESRestHighLevelClientTestCase { + + public void testPutJob() throws Exception { + String jobId = randomValidJobId(); + Job job = buildJob(jobId); + MachineLearningClient machineLearningClient = highLevelClient().machineLearning(); + + PutJobResponse putJobResponse = execute(new PutJobRequest(job), machineLearningClient::putJob, machineLearningClient::putJobAsync); + Job createdJob = putJobResponse.getResponse(); + + assertThat(createdJob.getId(), is(jobId)); + assertThat(createdJob.getJobType(), is(Job.ANOMALY_DETECTOR_JOB_TYPE)); + } + + public static String randomValidJobId() { + CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray()); + return generator.ofCodePointsLength(random(), 10, 10); + } + + private static Job buildJob(String jobId) { + Job.Builder builder = new Job.Builder(jobId); + builder.setDescription(randomAlphaOfLength(10)); + + Detector detector = new Detector.Builder() + .setFieldName("total") + .setFunction("sum") + .setDetectorDescription(randomAlphaOfLength(10)) + .build(); + AnalysisConfig.Builder configBuilder = new AnalysisConfig.Builder(Arrays.asList(detector)); + configBuilder.setBucketSpan(new TimeValue(randomIntBetween(1, 10), TimeUnit.SECONDS)); + builder.setAnalysisConfig(configBuilder); + + DataDescription.Builder dataDescription = new DataDescription.Builder(); + dataDescription.setTimeFormat(randomFrom(DataDescription.EPOCH_MS, DataDescription.EPOCH)); + dataDescription.setTimeField(randomAlphaOfLength(10)); + builder.setDataDescription(dataDescription); + + return builder.build(); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MigrationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MigrationIT.java new file mode 100644 index 0000000000000..03614537bfe78 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MigrationIT.java @@ -0,0 +1,43 @@ +/* + * 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.client; + +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoRequest; +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoResponse; + +import java.io.IOException; + +public class MigrationIT extends ESRestHighLevelClientTestCase { + + public void testGetAssistance() throws IOException { + RestHighLevelClient client = highLevelClient(); + { + IndexUpgradeInfoResponse response = client.migration().getAssistance(new IndexUpgradeInfoRequest(), RequestOptions.DEFAULT); + assertEquals(0, response.getActions().size()); + } + { + client.indices().create(new CreateIndexRequest("test"), RequestOptions.DEFAULT); + IndexUpgradeInfoResponse response = client.migration().getAssistance( + new IndexUpgradeInfoRequest("test"), RequestOptions.DEFAULT); + assertEquals(0, response.getActions().size()); + } + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index 0415d363c54cd..47195f0bb2aba 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -126,6 +126,8 @@ import org.elasticsearch.index.rankeval.RatedRequest; import org.elasticsearch.index.rankeval.RestRankEvalAction; import org.elasticsearch.protocol.xpack.XPackInfoRequest; +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoRequest; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest; import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.repositories.fs.FsRepository; import org.elasticsearch.rest.action.search.RestSearchAction; @@ -2551,6 +2553,23 @@ public void testXPackInfo() { assertEquals(expectedParams, request.getParameters()); } + public void testGetMigrationAssistance() { + IndexUpgradeInfoRequest upgradeInfoRequest = new IndexUpgradeInfoRequest(); + String expectedEndpoint = "/_xpack/migration/assistance"; + if (randomBoolean()) { + String[] indices = randomIndicesNames(1, 5); + upgradeInfoRequest.indices(indices); + expectedEndpoint += "/" + String.join(",", indices); + } + Map expectedParams = new HashMap<>(); + setRandomIndicesOptions(upgradeInfoRequest::indicesOptions, upgradeInfoRequest::indicesOptions, expectedParams); + Request request = RequestConverters.getMigrationAssistance(upgradeInfoRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals(expectedEndpoint, request.getEndpoint()); + assertNull(request.getEntity()); + assertEquals(expectedParams, request.getParameters()); + } + public void testXPackPutWatch() throws Exception { PutWatchRequest putWatchRequest = new PutWatchRequest(); String watchId = randomAlphaOfLength(10); @@ -2580,6 +2599,17 @@ public void testXPackPutWatch() throws Exception { assertThat(bos.toString("UTF-8"), is(body)); } + public void testXPackDeleteWatch() { + DeleteWatchRequest deleteWatchRequest = new DeleteWatchRequest(); + String watchId = randomAlphaOfLength(10); + deleteWatchRequest.setId(watchId); + + Request request = RequestConverters.xPackWatcherDeleteWatch(deleteWatchRequest); + assertEquals(HttpDelete.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/watcher/watch/" + watchId, request.getEndpoint()); + assertThat(request.getEntity(), nullValue()); + } + /** * Randomize the {@link FetchSourceContext} request parameters. */ diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java index 5cf3b35275620..b5d8dbb628eb9 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java @@ -20,7 +20,6 @@ package org.elasticsearch.client; import com.fasterxml.jackson.core.JsonParseException; - import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; @@ -755,7 +754,11 @@ public void testApiNamingConventions() throws Exception { method.isAnnotationPresent(Deprecated.class)); } else { //TODO xpack api are currently ignored, we need to load xpack yaml spec too - if (apiName.startsWith("xpack.") == false) { + if (apiName.startsWith("xpack.") == false && + apiName.startsWith("license.") == false && + apiName.startsWith("machine_learning.") == false && + apiName.startsWith("watcher.") == false && + apiName.startsWith("migration.") == false) { apiNotFound.add(apiName); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java index 9c9c5425f0006..3151e9badb6de 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java @@ -59,6 +59,9 @@ import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.bucket.range.Range; import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder; +import org.elasticsearch.search.aggregations.bucket.significant.SignificantTerms; +import org.elasticsearch.search.aggregations.bucket.significant.SignificantTermsAggregationBuilder; +import org.elasticsearch.search.aggregations.bucket.significant.heuristics.PercentageScore; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.aggregations.matrix.stats.MatrixStats; @@ -267,6 +270,33 @@ public void testSearchWithTermsAgg() throws IOException { assertEquals(2, type2.getDocCount()); assertEquals(0, type2.getAggregations().asList().size()); } + + public void testSearchWithSignificantTermsAgg() throws IOException { + SearchRequest searchRequest = new SearchRequest(); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(new MatchQueryBuilder("num","50")); + searchSourceBuilder.aggregation(new SignificantTermsAggregationBuilder("agg1", ValueType.STRING) + .field("type.keyword") + .minDocCount(1) + .significanceHeuristic(new PercentageScore())); + searchSourceBuilder.size(0); + searchRequest.source(searchSourceBuilder); + SearchResponse searchResponse = execute(searchRequest, highLevelClient()::search, highLevelClient()::searchAsync); + assertSearchHeader(searchResponse); + assertNull(searchResponse.getSuggest()); + assertEquals(Collections.emptyMap(), searchResponse.getProfileResults()); + assertEquals(0, searchResponse.getHits().getHits().length); + assertEquals(0f, searchResponse.getHits().getMaxScore(), 0f); + SignificantTerms significantTermsAgg = searchResponse.getAggregations().get("agg1"); + assertEquals("agg1", significantTermsAgg.getName()); + assertEquals(1, significantTermsAgg.getBuckets().size()); + SignificantTerms.Bucket type1 = significantTermsAgg.getBucketByKey("type1"); + assertEquals(1, type1.getDocCount()); + assertEquals(1, type1.getSubsetDf()); + assertEquals(1, type1.getSubsetSize()); + assertEquals(3, type1.getSupersetDf()); + assertEquals(1d/3d, type1.getSignificanceScore(), 0d); + } public void testSearchWithRangeAgg() throws IOException { { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SyncedFlushResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SyncedFlushResponseTests.java index 0756cfa6bab10..8057a92b3f279 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SyncedFlushResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SyncedFlushResponseTests.java @@ -24,7 +24,7 @@ import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.TestShardRouting; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; @@ -63,7 +63,7 @@ public void testXContentSerialization() throws IOException { .xContent() .createParser( xContentRegistry(), - LoggingDeprecationHandler.INSTANCE, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, BytesReference.bytes(serverResponsebuilder).streamInput() ).map() ); @@ -74,7 +74,7 @@ public void testXContentSerialization() throws IOException { .xContent() .createParser( xContentRegistry(), - LoggingDeprecationHandler.INSTANCE, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, BytesReference.bytes(clientResponsebuilder).streamInput() ) .map() @@ -94,7 +94,9 @@ public void testXContentDeserialization() throws IOException { .contentType() .xContent() .createParser( - xContentRegistry(), LoggingDeprecationHandler.INSTANCE, BytesReference.bytes(builder).streamInput() + xContentRegistry(), + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + BytesReference.bytes(builder).streamInput() ); SyncedFlushResponse originalResponse = plan.clientResult; SyncedFlushResponse parsedResponse = SyncedFlushResponse.fromXContent(parser); @@ -175,7 +177,8 @@ TestPlan createTestPlan() throws IOException { .contentType() .xContent() .createParser( - xContentRegistry(), LoggingDeprecationHandler.INSTANCE, + xContentRegistry(), + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, BytesReference.bytes(builder).streamInput() ) .map(); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherIT.java index dec438a47ab18..491992735afbf 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherIT.java @@ -21,6 +21,8 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchResponse; import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.protocol.xpack.watcher.PutWatchResponse; @@ -30,6 +32,13 @@ public class WatcherIT extends ESRestHighLevelClientTestCase { public void testPutWatch() throws Exception { String watchId = randomAlphaOfLength(10); + PutWatchResponse putWatchResponse = createWatch(watchId); + assertThat(putWatchResponse.isCreated(), is(true)); + assertThat(putWatchResponse.getId(), is(watchId)); + assertThat(putWatchResponse.getVersion(), is(1L)); + } + + private PutWatchResponse createWatch(String watchId) throws Exception { String json = "{ \n" + " \"trigger\": { \"schedule\": { \"interval\": \"10h\" } },\n" + " \"input\": { \"none\": {} },\n" + @@ -37,10 +46,30 @@ public void testPutWatch() throws Exception { "}"; BytesReference bytesReference = new BytesArray(json); PutWatchRequest putWatchRequest = new PutWatchRequest(watchId, bytesReference, XContentType.JSON); - PutWatchResponse putWatchResponse = highLevelClient().xpack().watcher().putWatch(putWatchRequest, RequestOptions.DEFAULT); - assertThat(putWatchResponse.isCreated(), is(true)); - assertThat(putWatchResponse.getId(), is(watchId)); - assertThat(putWatchResponse.getVersion(), is(1L)); + return highLevelClient().watcher().putWatch(putWatchRequest, RequestOptions.DEFAULT); + } + + public void testDeleteWatch() throws Exception { + // delete watch that exists + { + String watchId = randomAlphaOfLength(10); + createWatch(watchId); + DeleteWatchResponse deleteWatchResponse = highLevelClient().watcher().deleteWatch(new DeleteWatchRequest(watchId), + RequestOptions.DEFAULT); + assertThat(deleteWatchResponse.getId(), is(watchId)); + assertThat(deleteWatchResponse.getVersion(), is(2L)); + assertThat(deleteWatchResponse.isFound(), is(true)); + } + + // delete watch that does not exist + { + String watchId = randomAlphaOfLength(10); + DeleteWatchResponse deleteWatchResponse = highLevelClient().watcher().deleteWatch(new DeleteWatchRequest(watchId), + RequestOptions.DEFAULT); + assertThat(deleteWatchResponse.getId(), is(watchId)); + assertThat(deleteWatchResponse.getVersion(), is(1L)); + assertThat(deleteWatchResponse.isFound(), is(false)); + } } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IngestClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IngestClientDocumentationIT.java index 98502e3668af1..4702c34c6de3d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IngestClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IngestClientDocumentationIT.java @@ -31,7 +31,7 @@ import org.elasticsearch.action.ingest.SimulatePipelineRequest; import org.elasticsearch.action.ingest.SimulatePipelineResponse; import org.elasticsearch.action.ingest.SimulateProcessorResult; -import org.elasticsearch.action.ingest.WritePipelineResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; @@ -93,7 +93,7 @@ public void testPutPipeline() throws IOException { // end::put-pipeline-request-masterTimeout // tag::put-pipeline-execute - WritePipelineResponse response = client.ingest().putPipeline(request, RequestOptions.DEFAULT); // <1> + AcknowledgedResponse response = client.ingest().putPipeline(request, RequestOptions.DEFAULT); // <1> // end::put-pipeline-execute // tag::put-pipeline-response @@ -117,10 +117,10 @@ public void testPutPipelineAsync() throws Exception { ); // tag::put-pipeline-execute-listener - ActionListener listener = - new ActionListener() { + ActionListener listener = + new ActionListener() { @Override - public void onResponse(WritePipelineResponse response) { + public void onResponse(AcknowledgedResponse response) { // <1> } @@ -236,7 +236,7 @@ public void testDeletePipeline() throws IOException { // end::delete-pipeline-request-masterTimeout // tag::delete-pipeline-execute - WritePipelineResponse response = client.ingest().deletePipeline(request, RequestOptions.DEFAULT); // <1> + AcknowledgedResponse response = client.ingest().deletePipeline(request, RequestOptions.DEFAULT); // <1> // end::delete-pipeline-execute // tag::delete-pipeline-response @@ -257,10 +257,10 @@ public void testDeletePipelineAsync() throws Exception { DeletePipelineRequest request = new DeletePipelineRequest("my-pipeline-id"); // tag::delete-pipeline-execute-listener - ActionListener listener = - new ActionListener() { + ActionListener listener = + new ActionListener() { @Override - public void onResponse(WritePipelineResponse response) { + public void onResponse(AcknowledgedResponse response) { // <1> } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java index d620adb71312b..6146030999c56 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java @@ -19,11 +19,17 @@ package org.elasticsearch.client.documentation; +import org.elasticsearch.Build; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.LatchedActionListener; import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.common.Booleans; +import org.elasticsearch.protocol.xpack.license.DeleteLicenseRequest; +import org.elasticsearch.protocol.xpack.license.DeleteLicenseResponse; +import org.elasticsearch.protocol.xpack.license.GetLicenseRequest; +import org.elasticsearch.protocol.xpack.license.GetLicenseResponse; import org.elasticsearch.protocol.xpack.license.LicensesStatus; import org.elasticsearch.protocol.xpack.license.PutLicenseRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; @@ -32,6 +38,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.startsWith; @@ -42,7 +50,8 @@ */ public class LicensingDocumentationIT extends ESRestHighLevelClientTestCase { - public void testPutLicense() throws Exception { + public void testLicense() throws Exception { + assumeTrue("License is only valid when tested against snapshot/test builds", Build.CURRENT.isSnapshot()); RestHighLevelClient client = highLevelClient(); String license = "{\"license\": {\"uid\":\"893361dc-9749-4997-93cb-802e3d7fa4a8\",\"type\":\"gold\"," + "\"issue_date_in_millis\":1411948800000,\"expiry_date_in_millis\":1914278399999,\"max_nodes\":1,\"issued_to\":\"issued_to\"," + @@ -60,7 +69,7 @@ public void testPutLicense() throws Exception { request.setLicenseDefinition(license); // <1> request.setAcknowledge(false); // <2> - PutLicenseResponse response = client.xpack().license().putLicense(request, RequestOptions.DEFAULT); + PutLicenseResponse response = client.license().putLicense(request, RequestOptions.DEFAULT); //end::put-license-execute //tag::put-license-response @@ -80,7 +89,7 @@ public void testPutLicense() throws Exception { // tag::put-license-execute-listener ActionListener listener = new ActionListener() { @Override - public void onResponse(PutLicenseResponse indexResponse) { + public void onResponse(PutLicenseResponse putLicenseResponse) { // <1> } @@ -96,11 +105,114 @@ public void onFailure(Exception e) { listener = new LatchedActionListener<>(listener, latch); // tag::put-license-execute-async - client.xpack().license().putLicenseAsync( + client.license().putLicenseAsync( request, RequestOptions.DEFAULT, listener); // <1> // end::put-license-execute-async assertTrue(latch.await(30L, TimeUnit.SECONDS)); } + + // we cannot actually delete the license, otherwise the remaining tests won't work + if (Booleans.isTrue("true")) { + return; + } + { + //tag::delete-license-execute + DeleteLicenseRequest request = new DeleteLicenseRequest(); + + DeleteLicenseResponse response = client.license().deleteLicense(request, RequestOptions.DEFAULT); + //end::delete-license-execute + + //tag::delete-license-response + boolean acknowledged = response.isAcknowledged(); // <1> + //end::delete-license-response + + assertTrue(acknowledged); + } + { + DeleteLicenseRequest request = new DeleteLicenseRequest(); + // tag::delete-license-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(DeleteLicenseResponse deleteLicenseResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::delete-license-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::delete-license-execute-async + client.license().deleteLicenseAsync( + request, RequestOptions.DEFAULT, listener); // <1> + // end::delete-license-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } + + public void testGetLicense() throws Exception { + RestHighLevelClient client = highLevelClient(); + { + //tag::get-license-execute + GetLicenseRequest request = new GetLicenseRequest(); + + GetLicenseResponse response = client.license().getLicense(request, RequestOptions.DEFAULT); + //end::get-license-execute + + //tag::get-license-response + String currentLicense = response.getLicenseDefinition(); // <1> + //end::get-license-response + + assertThat(currentLicense, containsString("trial")); + assertThat(currentLicense, containsString("client_rest-high-level_integTestCluster")); + } + { + GetLicenseRequest request = new GetLicenseRequest(); + // tag::get-license-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(GetLicenseResponse indexResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::get-license-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::get-license-execute-async + client.license().getLicenseAsync( + request, RequestOptions.DEFAULT, listener); // <1> + // end::get-license-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + { + GetLicenseRequest request = new GetLicenseRequest(); + RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); + // Make sure that it still works in other formats + builder.addHeader("Accept", randomFrom("application/smile", "application/cbor")); + RequestOptions options = builder.build(); + GetLicenseResponse response = client.license().getLicense(request, options); + String currentLicense = response.getLicenseDefinition(); + assertThat(currentLicense, startsWith("{")); + assertThat(currentLicense, containsString("trial")); + assertThat(currentLicense, containsString("client_rest-high-level_integTestCluster")); + assertThat(currentLicense, endsWith("}")); + } } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MigrationClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MigrationClientDocumentationIT.java new file mode 100644 index 0000000000000..c8310be8053b2 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MigrationClientDocumentationIT.java @@ -0,0 +1,83 @@ +/* + * 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.client.documentation; + +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.client.ESRestHighLevelClientTestCase; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.common.Strings; +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoRequest; +import org.elasticsearch.protocol.xpack.migration.IndexUpgradeInfoResponse; +import org.elasticsearch.protocol.xpack.migration.UpgradeActionRequired; + +import java.io.IOException; +import java.util.Map; + +/** + * This class is used to generate the Java Migration API documentation. + * You need to wrap your code between two tags like: + * // tag::example + * // end::example + * + * Where example is your tag name. + * + * Then in the documentation, you can extract what is between tag and end tags with + * ["source","java",subs="attributes,callouts,macros"] + * -------------------------------------------------- + * include-tagged::{doc-tests}/MigrationClientDocumentationIT.java[example] + * -------------------------------------------------- + * + * The column width of the code block is 84. If the code contains a line longer + * than 84, the line will be cut and a horizontal scroll bar will be displayed. + * (the code indentation of the tag is not included in the width) + */ +public class MigrationClientDocumentationIT extends ESRestHighLevelClientTestCase { + + public void testGetAssistance() throws IOException { + RestHighLevelClient client = highLevelClient(); + + // tag::get-assistance-request + IndexUpgradeInfoRequest request = new IndexUpgradeInfoRequest(); // <1> + // end::get-assistance-request + + // tag::get-assistance-request-indices + request.indices("index1", "index2"); // <1> + // end::get-assistance-request-indices + + request.indices(Strings.EMPTY_ARRAY); + + // tag::get-assistance-request-indices-options + request.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1> + // end::get-assistance-request-indices-options + + // tag::get-assistance-execute + IndexUpgradeInfoResponse response = client.migration().getAssistance(request, RequestOptions.DEFAULT); + // end::get-assistance-execute + + // tag::get-assistance-response + Map actions = response.getActions(); + for (Map.Entry entry : actions.entrySet()) { + String index = entry.getKey(); // <1> + UpgradeActionRequired actionRequired = entry.getValue(); // <2> + } + // end::get-assistance-response + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java index df51d896cda70..707997d1f3103 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java @@ -26,6 +26,8 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest; +import org.elasticsearch.protocol.xpack.watcher.DeleteWatchResponse; import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.protocol.xpack.watcher.PutWatchResponse; @@ -34,7 +36,7 @@ public class WatcherDocumentationIT extends ESRestHighLevelClientTestCase { - public void testPutWatch() throws Exception { + public void testWatcher() throws Exception { RestHighLevelClient client = highLevelClient(); { @@ -47,7 +49,7 @@ public void testPutWatch() throws Exception { "}"); PutWatchRequest request = new PutWatchRequest("my_watch_id", watch, XContentType.JSON); request.setActive(false); // <1> - PutWatchResponse response = client.xpack().watcher().putWatch(request, RequestOptions.DEFAULT); + PutWatchResponse response = client.watcher().putWatch(request, RequestOptions.DEFAULT); //end::x-pack-put-watch-execute //tag::x-pack-put-watch-response @@ -83,10 +85,51 @@ public void onFailure(Exception e) { listener = new LatchedActionListener<>(listener, latch); // tag::x-pack-put-watch-execute-async - client.xpack().watcher().putWatchAsync(request, RequestOptions.DEFAULT, listener); // <1> + client.watcher().putWatchAsync(request, RequestOptions.DEFAULT, listener); // <1> // end::x-pack-put-watch-execute-async assertTrue(latch.await(30L, TimeUnit.SECONDS)); } + + { + //tag::x-pack-delete-watch-execute + DeleteWatchRequest request = new DeleteWatchRequest("my_watch_id"); + DeleteWatchResponse response = client.watcher().deleteWatch(request, RequestOptions.DEFAULT); + //end::x-pack-delete-watch-execute + + //tag::x-pack-delete-watch-response + String watchId = response.getId(); // <1> + boolean found = response.isFound(); // <2> + long version = response.getVersion(); // <3> + //end::x-pack-delete-watch-response + } + + { + DeleteWatchRequest request = new DeleteWatchRequest("my_other_watch_id"); + // tag::x-pack-delete-watch-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(DeleteWatchResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::x-pack-delete-watch-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::x-pack-delete-watch-execute-async + client.watcher().deleteWatchAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::x-pack-delete-watch-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } } + } diff --git a/client/sniffer/licenses/jackson-core-2.8.10.jar.sha1 b/client/sniffer/licenses/jackson-core-2.8.10.jar.sha1 deleted file mode 100644 index a322d371e265e..0000000000000 --- a/client/sniffer/licenses/jackson-core-2.8.10.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -eb21a035c66ad307e66ec8fce37f5d50fd62d039 \ No newline at end of file diff --git a/client/sniffer/licenses/jackson-core-2.8.11.jar.sha1 b/client/sniffer/licenses/jackson-core-2.8.11.jar.sha1 new file mode 100644 index 0000000000000..e7ad1e74ed6b8 --- /dev/null +++ b/client/sniffer/licenses/jackson-core-2.8.11.jar.sha1 @@ -0,0 +1 @@ +876ead1db19f0c9e79c9789273a3ef8c6fd6c29b \ No newline at end of file diff --git a/distribution/packages/src/common/systemd/elasticsearch.service b/distribution/packages/src/common/systemd/elasticsearch.service index 409f04f76d058..a4d67d8830a56 100644 --- a/distribution/packages/src/common/systemd/elasticsearch.service +++ b/distribution/packages/src/common/systemd/elasticsearch.service @@ -6,6 +6,7 @@ After=network-online.target [Service] RuntimeDirectory=elasticsearch +PrivateTmp=true Environment=ES_HOME=/usr/share/elasticsearch Environment=ES_PATH_CONF=${path.conf} Environment=PID_DIR=/var/run/elasticsearch diff --git a/distribution/src/config/log4j2.properties b/distribution/src/config/log4j2.properties index 589f529e83eaa..6de21cd48f67b 100644 --- a/distribution/src/config/log4j2.properties +++ b/distribution/src/config/log4j2.properties @@ -7,13 +7,13 @@ logger.action.level = debug appender.console.type = Console appender.console.name = console appender.console.layout.type = PatternLayout -appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n +appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] [%node_name]%marker %m%n appender.rolling.type = RollingFile appender.rolling.name = rolling appender.rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}.log appender.rolling.layout.type = PatternLayout -appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%.-10000m%n +appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] [%node_name]%marker %.-10000m%n appender.rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}-%d{yyyy-MM-dd}-%i.log.gz appender.rolling.policies.type = Policies appender.rolling.policies.time.type = TimeBasedTriggeringPolicy @@ -38,7 +38,7 @@ appender.deprecation_rolling.type = RollingFile appender.deprecation_rolling.name = deprecation_rolling appender.deprecation_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_deprecation.log appender.deprecation_rolling.layout.type = PatternLayout -appender.deprecation_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%.-10000m%n +appender.deprecation_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] [%node_name]%marker %.-10000m%n appender.deprecation_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_deprecation-%i.log.gz appender.deprecation_rolling.policies.type = Policies appender.deprecation_rolling.policies.size.type = SizeBasedTriggeringPolicy @@ -55,7 +55,7 @@ appender.index_search_slowlog_rolling.type = RollingFile appender.index_search_slowlog_rolling.name = index_search_slowlog_rolling appender.index_search_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog.log appender.index_search_slowlog_rolling.layout.type = PatternLayout -appender.index_search_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%n +appender.index_search_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %.-10000m%n appender.index_search_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog-%i.log.gz appender.index_search_slowlog_rolling.policies.type = Policies appender.index_search_slowlog_rolling.policies.size.type = SizeBasedTriggeringPolicy @@ -72,7 +72,7 @@ appender.index_indexing_slowlog_rolling.type = RollingFile appender.index_indexing_slowlog_rolling.name = index_indexing_slowlog_rolling appender.index_indexing_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog.log appender.index_indexing_slowlog_rolling.layout.type = PatternLayout -appender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%n +appender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %.-10000m%n appender.index_indexing_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog-%i.log.gz appender.index_indexing_slowlog_rolling.policies.type = Policies appender.index_indexing_slowlog_rolling.policies.size.type = SizeBasedTriggeringPolicy diff --git a/docs/build.gradle b/docs/build.gradle index a67c0217490b3..029147bba2ff0 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -37,6 +37,10 @@ integTestCluster { extraConfigFile 'hunspell/en_US/en_US.dic', '../server/src/test/resources/indices/analyze/conf_dir/hunspell/en_US/en_US.dic' // Whitelist reindexing from the local node so we can test it. setting 'reindex.remote.whitelist', '127.0.0.1:*' + + // TODO: remove this for 7.0, this exists to allow the doc examples in 6.x to continue using the defaults + systemProperty 'es.scripting.use_java_time', 'false' + systemProperty 'es.scripting.update.ctx_in_params', 'false' } // remove when https://github.com/elastic/elasticsearch/issues/31305 is fixed diff --git a/docs/java-api/index.asciidoc b/docs/java-api/index.asciidoc index 4fb7db4c4abf6..5c3a94d57f41c 100644 --- a/docs/java-api/index.asciidoc +++ b/docs/java-api/index.asciidoc @@ -81,7 +81,7 @@ You need to also include Log4j 2 dependencies: org.apache.logging.log4j log4j-core - 2.9.1 + 2.11.1 -------------------------------------------------- @@ -109,7 +109,7 @@ If you want to use another logger than Log4j 2, you can use http://www.slf4j.org org.apache.logging.log4j log4j-to-slf4j - 2.9.1 + 2.11.1 org.slf4j diff --git a/docs/java-api/query-dsl/percolate-query.asciidoc b/docs/java-api/query-dsl/percolate-query.asciidoc index e1968ae456a5c..9afce0842b9ae 100644 --- a/docs/java-api/query-dsl/percolate-query.asciidoc +++ b/docs/java-api/query-dsl/percolate-query.asciidoc @@ -49,7 +49,7 @@ XContentBuilder docBuilder = XContentFactory.jsonBuilder().startObject(); docBuilder.field("content", "This is amazing!"); docBuilder.endObject(); //End of the JSON root object -PercolateQueryBuilder percolateQuery = new PercolateQueryBuilder("query", "docs", docBuilder.bytes()); +PercolateQueryBuilder percolateQuery = new PercolateQueryBuilder("query", "docs", BytesReference.bytes(docBuilder)); // Percolate, by executing the percolator query in the query dsl: SearchResponse response = client().prepareSearch("myIndexName") diff --git a/docs/java-rest/high-level/licensing/delete-license.asciidoc b/docs/java-rest/high-level/licensing/delete-license.asciidoc new file mode 100644 index 0000000000000..d9aec6e57a14a --- /dev/null +++ b/docs/java-rest/high-level/licensing/delete-license.asciidoc @@ -0,0 +1,51 @@ +[[java-rest-high-delete-license]] +=== Delete License + +[[java-rest-high-delete-license-execution]] +==== Execution + +The license can be deleted using the `deleteLicense()` method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[delete-license-execute] +-------------------------------------------------- + +[[java-rest-high-delete-license-response]] +==== Response + +The returned `DeleteLicenseResponse` contains the `acknowledged` flag, which +returns true if the request was processed by all nodes. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[delete-license-response] +-------------------------------------------------- +<1> Check the acknowledge flag. It should be true if license deletion is acknowledged. + +[[java-rest-high-delete-license-async]] +==== Asynchronous Execution + +This request can be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[delete-license-execute-async] +-------------------------------------------------- +<1> The `DeleteLicenseRequest` to execute and the `ActionListener` to use when +the execution completes + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for `DeleteLicenseResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[delete-license-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument diff --git a/docs/java-rest/high-level/licensing/get-license.asciidoc b/docs/java-rest/high-level/licensing/get-license.asciidoc new file mode 100644 index 0000000000000..17eb89450fb15 --- /dev/null +++ b/docs/java-rest/high-level/licensing/get-license.asciidoc @@ -0,0 +1,50 @@ +[[java-rest-high-get-license]] +=== Get License + +[[java-rest-high-get-license-execution]] +==== Execution + +The license can be added or updated using the `getLicense()` method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[get-license-execute] +-------------------------------------------------- + +[[java-rest-high-get-license-response]] +==== Response + +The returned `GetLicenseResponse` contains the license in the JSON format. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[get-license-response] +-------------------------------------------------- +<1> The text of the license. + +[[java-rest-high-get-license-async]] +==== Asynchronous Execution + +This request can be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[get-license-execute-async] +-------------------------------------------------- +<1> The `GetLicenseRequest` to execute and the `ActionListener` to use when +the execution completes + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for `GetLicenseResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[get-license-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument diff --git a/docs/java-rest/high-level/migration/get-assistance.asciidoc b/docs/java-rest/high-level/migration/get-assistance.asciidoc new file mode 100644 index 0000000000000..20f857eb1fb41 --- /dev/null +++ b/docs/java-rest/high-level/migration/get-assistance.asciidoc @@ -0,0 +1,49 @@ +[[java-rest-high-migration-get-assistance]] +=== Migration Get Assistance + +[[java-rest-high-migraton-get-assistance-request]] +==== Index Upgrade Info Request + +An `IndexUpgradeInfoRequest` does not require any argument: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MigrationClientDocumentationIT.java[get-assistance-request] +-------------------------------------------------- +<1> Create a new request instance + +==== Optional arguments +The following arguments can optionally be provided: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MigrationClientDocumentationIT.java[get-assistance-request-indices] +-------------------------------------------------- +<1> Set the indices to the request + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MigrationClientDocumentationIT.java[get-assistance-request-indices-options] +-------------------------------------------------- +<1> Set the `IndicesOptions` to control how unavailable indices are resolved and +how wildcard expressions are expanded + +[[java-rest-high-migration-get-assistance-execution]] +==== Execution + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MigrationClientDocumentationIT.java[get-assistance-execute] +-------------------------------------------------- + +[[java-rest-high-migration-get-assistance-response]] +==== Response + +The returned `IndexUpgradeInfoResponse` contains the actions required for each index. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MigrationClientDocumentationIT.java[get-assistance-response] +-------------------------------------------------- +<1> Retrieve the index +<2> Retrieve the action required for the migration of the current index diff --git a/docs/java-rest/high-level/x-pack/x-pack-info.asciidoc b/docs/java-rest/high-level/miscellaneous/x-pack-info.asciidoc similarity index 100% rename from docs/java-rest/high-level/x-pack/x-pack-info.asciidoc rename to docs/java-rest/high-level/miscellaneous/x-pack-info.asciidoc diff --git a/docs/java-rest/high-level/x-pack/x-pack-usage.asciidoc b/docs/java-rest/high-level/miscellaneous/x-pack-usage.asciidoc similarity index 98% rename from docs/java-rest/high-level/x-pack/x-pack-usage.asciidoc rename to docs/java-rest/high-level/miscellaneous/x-pack-usage.asciidoc index 0927ae71c0bf5..c1e5ccf13e222 100644 --- a/docs/java-rest/high-level/x-pack/x-pack-usage.asciidoc +++ b/docs/java-rest/high-level/miscellaneous/x-pack-usage.asciidoc @@ -12,7 +12,7 @@ retrieved using the `usage()` method: include-tagged::{doc-tests}/MiscellaneousDocumentationIT.java[x-pack-usage-execute] -------------------------------------------------- -[[java-rest-high-x-pack-info-response]] +[[java-rest-high-x-pack-usage-response]] ==== Response The returned `XPackUsageResponse` contains a `Map` keyed by feature name. diff --git a/docs/java-rest/high-level/search/explain.asciidoc b/docs/java-rest/high-level/search/explain.asciidoc index 9e55ad77ea203..4d8c04448b1ae 100644 --- a/docs/java-rest/high-level/search/explain.asciidoc +++ b/docs/java-rest/high-level/search/explain.asciidoc @@ -80,7 +80,7 @@ A typical listener for `ExplainResponse` is constructed as follows: include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-execute-listener] -------------------------------------------------- <1> Called when the execution is successfully completed. -<2> Called when the whole `FieldCapabilitiesRequest` fails. +<2> Called when the whole `ExplainRequest` fails. [[java-rest-high-explain-response]] ==== ExplainResponse diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 25fbcaaaeaa73..9d7d66434f7e7 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -54,11 +54,12 @@ The Java High Level REST Client supports the following Miscellaneous APIs: * <> * <> * <> +* <> include::miscellaneous/main.asciidoc[] include::miscellaneous/ping.asciidoc[] -include::x-pack/x-pack-info.asciidoc[] -include::x-pack/watcher/put-watch.asciidoc[] +include::miscellaneous/x-pack-info.asciidoc[] +include::miscellaneous/x-pack-usage.asciidoc[] == Indices APIs @@ -187,11 +188,32 @@ The Java High Level REST Client supports the following Scripts APIs: include::script/get_script.asciidoc[] include::script/delete_script.asciidoc[] - == Licensing APIs The Java High Level REST Client supports the following Licensing APIs: * <> +* <> +* <> include::licensing/put-license.asciidoc[] +include::licensing/get-license.asciidoc[] +include::licensing/delete-license.asciidoc[] + +== Migration APIs + +The Java High Level REST Client supports the following Migration APIs: + +* <> + +include::migration/get-assistance.asciidoc[] + +== Watcher APIs + +The Java High Level REST Client supports the following Watcher APIs: + +* <> +* <> + +include::watcher/put-watch.asciidoc[] +include::watcher/delete-watch.asciidoc[] diff --git a/docs/java-rest/high-level/watcher/delete-watch.asciidoc b/docs/java-rest/high-level/watcher/delete-watch.asciidoc new file mode 100644 index 0000000000000..615337ba317bf --- /dev/null +++ b/docs/java-rest/high-level/watcher/delete-watch.asciidoc @@ -0,0 +1,53 @@ +[[java-rest-high-x-pack-watcher-delete-watch]] +=== Delete Watch API + +[[java-rest-high-x-pack-watcher-delete-watch-execution]] +==== Execution + +A watch can be deleted as follows: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-delete-watch-execute] +-------------------------------------------------- + +[[java-rest-high-x-pack-watcher-delete-watch-response]] +==== Response + +The returned `DeleteWatchResponse` contains `found`, `id`, +and `version` information. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-put-watch-response] +-------------------------------------------------- +<1> `_id` contains id of the watch +<2> `found` is a boolean indicating whether the watch was found +<3> `_version` returns the version of the deleted watch + +[[java-rest-high-x-pack-watcher-delete-watch-async]] +==== Asynchronous Execution + +This request can be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-delete-watch-execute-async] +-------------------------------------------------- +<1> The `DeleteWatchRequest` to execute and the `ActionListener` to use when +the execution completes + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for `DeleteWatchResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-delete-watch-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument diff --git a/docs/java-rest/high-level/x-pack/watcher/put-watch.asciidoc b/docs/java-rest/high-level/watcher/put-watch.asciidoc similarity index 90% rename from docs/java-rest/high-level/x-pack/watcher/put-watch.asciidoc rename to docs/java-rest/high-level/watcher/put-watch.asciidoc index c803c54eb5e92..e5ee87bea34a6 100644 --- a/docs/java-rest/high-level/x-pack/watcher/put-watch.asciidoc +++ b/docs/java-rest/high-level/watcher/put-watch.asciidoc @@ -1,5 +1,5 @@ [[java-rest-high-x-pack-watcher-put-watch]] -=== X-Pack Info API +=== Put Watch API [[java-rest-high-x-pack-watcher-put-watch-execution]] ==== Execution @@ -16,7 +16,7 @@ include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-put-watch-execute [[java-rest-high-x-pack-watcher-put-watch-response]] ==== Response -The returned `XPackPutWatchResponse` contain `created`, `id`, +The returned `PutWatchResponse` contains `created`, `id`, and `version` information. ["source","java",subs="attributes,callouts,macros"] @@ -36,7 +36,7 @@ This request can be executed asynchronously: -------------------------------------------------- include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-put-watch-execute-async] -------------------------------------------------- -<1> The `XPackPutWatchRequest` to execute and the `ActionListener` to use when +<1> The `PutWatchRequest` to execute and the `ActionListener` to use when the execution completes The asynchronous method does not block and returns immediately. Once it is @@ -44,7 +44,7 @@ completed the `ActionListener` is called back using the `onResponse` method if the execution successfully completed or using the `onFailure` method if it failed. -A typical listener for `XPackPutWatchResponse` looks like: +A typical listener for `PutWatchResponse` looks like: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- diff --git a/docs/painless/painless-contexts.asciidoc b/docs/painless/painless-contexts.asciidoc index 8b8a3b0eec6b4..cc7bc752ec6d9 100644 --- a/docs/painless/painless-contexts.asciidoc +++ b/docs/painless/painless-contexts.asciidoc @@ -14,6 +14,8 @@ specialized code may define new ways to use a Painless script. |==== | Name | Painless Documentation | Elasticsearch Documentation +| Ingest processor | <> + | {ref}/script-processor.html[Elasticsearch Documentation] | Update | <> | {ref}/docs-update.html[Elasticsearch Documentation] | Update by query | <> @@ -44,12 +46,12 @@ specialized code may define new ways to use a Painless script. | {ref}/search-aggregations-metrics-scripted-metric-aggregation.html[Elasticsearch Documentation] | Bucket aggregation | <> | {ref}/search-aggregations-pipeline-bucket-script-aggregation.html[Elasticsearch Documentation] -| Ingest processor | <> - | {ref}/script-processor.html[Elasticsearch Documentation] | Watcher condition | <> | {xpack-ref}/condition-script.html[Elasticsearch Documentation] | Watcher transform | <> | {xpack-ref}/transform-script.html[Elasticsearch Documentation] |==== +include::painless-contexts/painless-context-examples.asciidoc[] + include::painless-contexts/index.asciidoc[] diff --git a/docs/painless/painless-contexts/index.asciidoc b/docs/painless/painless-contexts/index.asciidoc index 64e4326e052f2..a9d3982133e1b 100644 --- a/docs/painless/painless-contexts/index.asciidoc +++ b/docs/painless/painless-contexts/index.asciidoc @@ -1,3 +1,5 @@ +include::painless-ingest-processor-context.asciidoc[] + include::painless-update-context.asciidoc[] include::painless-update-by-query-context.asciidoc[] @@ -28,8 +30,6 @@ include::painless-metric-agg-reduce-context.asciidoc[] include::painless-bucket-agg-context.asciidoc[] -include::painless-ingest-processor-context.asciidoc[] - include::painless-watcher-condition-context.asciidoc[] include::painless-watcher-transform-context.asciidoc[] diff --git a/docs/painless/painless-contexts/painless-context-examples.asciidoc b/docs/painless/painless-contexts/painless-context-examples.asciidoc new file mode 100644 index 0000000000000..469f425d1d89f --- /dev/null +++ b/docs/painless/painless-contexts/painless-context-examples.asciidoc @@ -0,0 +1,80 @@ +[[painless-context-examples]] +=== Context examples + +To run the examples, index the sample seat data into Elasticsearch. The examples +must be run sequentially to work correctly. + +. Download the +https://download.elastic.co/demos/painless/contexts/seats.json[seat data]. This +data set contains booking information for a collection of plays. Each document +represents a single seat for a play at a particular theater on a specific date +and time. ++ +Each document contains the following fields: ++ +`theatre` ({ref}/keyword.html[`keyword`]):: + The name of the theater the play is in. +`play` ({ref}/text.html[`text`]):: + The name of the play. +`actors` ({ref}/text.html[`text`]):: + A list of actors in the play. +`row` ({ref}/number.html[`integer`]):: + The row of the seat. +`number` ({ref}/number.html[`integer`]):: + The number of the seat within a row. +`cost` ({ref}/number.html[`double`]):: + The cost of the ticket for the seat. +`sold` ({ref}/boolean.html[`boolean`]):: + Whether or not the seat is sold. +`datetime` ({ref}/date.html[`date`]):: + The date and time of the play as a date object. +`date` ({ref}/keyword.html[`keyword`]):: + The date of the play as a keyword. +`time` ({ref}/keyword.html[`keyword`]):: + The time of the play as a keyword. + +. {defguide}/running-elasticsearch.html[Start] Elasticsearch. Note these +examples assume Elasticsearch and Kibana are running locally. To use the Console +editor with a remote Kibana instance, click the settings icon and enter the +Console URL. To submit a cURL request to a remote Elasticsearch instance, edit +the request URL. + +. Create {ref}/mapping.html[mappings] for the sample data: ++ +[source,js] +---- +PUT /seats +{ + "mappings": { + "seat": { + "properties": { + "theatre": { "type": "keyword" }, + "play": { "type": "text" }, + "actors": { "type": "text" }, + "row": { "type": "integer" }, + "number": { "type": "integer" }, + "cost": { "type": "double" }, + "sold": { "type": "boolean" }, + "datetime": { "type": "date" }, + "date": { "type": "keyword" }, + "time": { "type": "keyword" } + } + } + } +} +---- ++ +// CONSOLE + +. Run the <> +example. This sets up a script ingest processor used on each document as the +seat data is indexed. + +. Index the seat data: ++ +[source,js] +---- +curl -XPOST localhost:9200/seats/seat/_bulk?pipeline=seats -H "Content-Type: application/x-ndjson" --data-binary "@//seats.json" +---- +// NOTCONSOLE + diff --git a/docs/painless/painless-contexts/painless-ingest-processor-context.asciidoc b/docs/painless/painless-contexts/painless-ingest-processor-context.asciidoc index 5d451268dedcd..546057ab1a0b8 100644 --- a/docs/painless/painless-contexts/painless-ingest-processor-context.asciidoc +++ b/docs/painless/painless-contexts/painless-ingest-processor-context.asciidoc @@ -27,7 +27,7 @@ to modify documents upon insertion. {ref}/mapping-type-field.html[`ctx['_type']`]:: Modify this to change the type for the current document. -`ctx` (`Map`, read-only):: +`ctx` (`Map`):: Modify the values in the `Map/List` structure to add, modify, or delete the fields of a document. @@ -38,4 +38,158 @@ void:: *API* -The standard <> is available. \ No newline at end of file +The standard <> is available. + +*Example* + +To run this example, first follow the steps in +<>. + +The seat data contains: + +* A date in the format `YYYY-MM-DD` where the second digit of both month and day + is optional. +* A time in the format HH:MM* where the second digit of both hours and minutes + is optional. The star (*) represents either the `String` `AM` or `PM`. + +The following ingest script processes the date and time `Strings` and stores the +result in a `datetime` field. + +[source,Painless] +---- +String[] split(String s, char d) { <1> + int count = 0; + + for (char c : s.toCharArray()) { <2> + if (c == d) { + ++count; + } + } + + if (count == 0) { + return new String[] {s}; <3> + } + + String[] r = new String[count + 1]; <4> + int i0 = 0, i1 = 0; + count = 0; + + for (char c : s.toCharArray()) { <5> + if (c == d) { + r[count++] = s.substring(i0, i1); + i0 = i1 + 1; + } + + ++i1; + } + + r[count] = s.substring(i0, i1); <6> + + return r; +} + +String[] dateSplit = split(ctx.date, (char)"-"); <7> +String year = dateSplit[0].trim(); +String month = dateSplit[1].trim(); + +if (month.length() == 1) { <8> + month = "0" + month; +} + +String day = dateSplit[2].trim(); + +if (day.length() == 1) { <9> + day = "0" + day; +} + +boolean pm = ctx.time.substring(ctx.time.length() - 2).equals("PM"); <10> +String[] timeSplit = split( + ctx.time.substring(0, ctx.time.length() - 2), (char)":"); <11> +int hours = Integer.parseInt(timeSplit[0].trim()); +int minutes = Integer.parseInt(timeSplit[1].trim()); + +if (pm) { <12> + hours += 12; +} + +String dts = year + "-" + month + "-" + day + "T" + + (hours < 10 ? "0" + hours : "" + hours) + ":" + + (minutes < 10 ? "0" + minutes : "" + minutes) + + ":00+08:00"; <13> + +ZonedDateTime dt = ZonedDateTime.parse( + dts, DateTimeFormatter.ISO_OFFSET_DATE_TIME); <14> +ctx.datetime = dt.getLong(ChronoField.INSTANT_SECONDS)*1000L; <15> +---- +<1> Creates a `split` <> to split a + <> type value using a <> + type value as the delimiter. This is useful for handling the necessity of + pulling out the individual pieces of the date and time `Strings` from the + original seat data. +<2> The first pass through each `char` in the `String` collects how many new + `Strings` the original is split into. +<3> Returns the original `String` if there are no instances of the delimiting + `char`. +<4> Creates an <> value to collect the split `Strings` + into based on the number of `char` delimiters found in the first pass. +<5> The second pass through each `char` in the `String` collects each split + substring into an array type value of `Strings`. +<6> Collects the last substring into the array type value of `Strings`. +<7> Uses the `split` function to separate the date `String` from the seat data + into year, month, and day `Strings`. + Note:: + * The use of a `String` type value to `char` type value + <> as part of the second argument since + character literals do not exist. + * The use of the `ctx` ingest processor context variable to retrieve the + data from the `date` field. +<8> Appends the <> `"0"` value to a single + digit month since the format of the seat data allows for this case. +<9> Appends the <> `"0"` value to a single + digit day since the format of the seat data allows for this case. +<10> Sets the <> + <> to `true` if the time `String` is a time + in the afternoon or evening. + Note:: + * The use of the `ctx` ingest processor context variable to retrieve the + data from the `time` field. +<11> Uses the `split` function to separate the time `String` from the seat data + into hours and minutes `Strings`. + Note:: + * The use of the `substring` method to remove the `AM` or `PM` portion of + the time `String`. + * The use of a `String` type value to `char` type value + <> as part of the second argument since + character literals do not exist. + * The use of the `ctx` ingest processor context variable to retrieve the + data from the `date` field. +<12> If the time `String` is an afternoon or evening value adds the + <> `12` to the existing hours to move to + a 24-hour based time. +<13> Builds a new time `String` that is parsable using existing API methods. +<14> Creates a `ZonedDateTime` <> value by using + the API method `parse` to parse the new time `String`. +<15> Sets the datetime field `datetime` to the number of milliseconds retrieved + from the API method `getLong`. + Note:: + * The use of the `ctx` ingest processor context variable to set the field + `datetime`. Manipulate each document's fields with the `ctx` variable as + each document is indexed. + +Submit the following request: + +[source,js] +---- +PUT /_ingest/pipeline/seats +{ + "description": "update datetime for seats", + "processors": [ + { + "script": { + "source": "String[] split(String s, char d) { int count = 0; for (char c : s.toCharArray()) { if (c == d) { ++count; } } if (count == 0) { return new String[] {s}; } String[] r = new String[count + 1]; int i0 = 0, i1 = 0; count = 0; for (char c : s.toCharArray()) { if (c == d) { r[count++] = s.substring(i0, i1); i0 = i1 + 1; } ++i1; } r[count] = s.substring(i0, i1); return r; } String[] dateSplit = split(ctx.date, (char)\"-\"); String year = dateSplit[0].trim(); String month = dateSplit[1].trim(); if (month.length() == 1) { month = \"0\" + month; } String day = dateSplit[2].trim(); if (day.length() == 1) { day = \"0\" + day; } boolean pm = ctx.time.substring(ctx.time.length() - 2).equals(\"PM\"); String[] timeSplit = split(ctx.time.substring(0, ctx.time.length() - 2), (char)\":\"); int hours = Integer.parseInt(timeSplit[0].trim()); int minutes = Integer.parseInt(timeSplit[1].trim()); if (pm) { hours += 12; } String dts = year + \"-\" + month + \"-\" + day + \"T\" + (hours < 10 ? \"0\" + hours : \"\" + hours) + \":\" + (minutes < 10 ? \"0\" + minutes : \"\" + minutes) + \":00+08:00\"; ZonedDateTime dt = ZonedDateTime.parse(dts, DateTimeFormatter.ISO_OFFSET_DATE_TIME); ctx.datetime = dt.getLong(ChronoField.INSTANT_SECONDS)*1000L;" + } + } + ] +} +---- +// CONSOLE \ No newline at end of file diff --git a/docs/painless/painless-contexts/painless-metric-agg-combine-context.asciidoc b/docs/painless/painless-contexts/painless-metric-agg-combine-context.asciidoc index 31cb596ae8167..5cc9ad8ecbb93 100644 --- a/docs/painless/painless-contexts/painless-metric-agg-combine-context.asciidoc +++ b/docs/painless/painless-contexts/painless-metric-agg-combine-context.asciidoc @@ -12,7 +12,7 @@ optional as part of a full metric aggregation. `params` (`Map`, read-only):: User-defined parameters passed in as part of the query. -`params['_agg']` (`Map`):: +`state` (`Map`):: `Map` with values available from the prior map script. *Return* diff --git a/docs/painless/painless-contexts/painless-metric-agg-init-context.asciidoc b/docs/painless/painless-contexts/painless-metric-agg-init-context.asciidoc index 1503e3abb5891..8c0fddfa33961 100644 --- a/docs/painless/painless-contexts/painless-metric-agg-init-context.asciidoc +++ b/docs/painless/painless-contexts/painless-metric-agg-init-context.asciidoc @@ -12,13 +12,13 @@ full metric aggregation. `params` (`Map`, read-only):: User-defined parameters passed in as part of the query. -`params['_agg']` (`Map`):: +`state` (`Map`):: Empty `Map` used to add values for use in a <>. *Side Effects* -`params['_agg']` (`Map`):: +`state` (`Map`):: Add values to this `Map` to for use in a map. Additional values must be of the type `Map`, `List`, `String` or primitive. diff --git a/docs/painless/painless-contexts/painless-metric-agg-map-context.asciidoc b/docs/painless/painless-contexts/painless-metric-agg-map-context.asciidoc index 16016d1cf8171..a34308aa93887 100644 --- a/docs/painless/painless-contexts/painless-metric-agg-map-context.asciidoc +++ b/docs/painless/painless-contexts/painless-metric-agg-map-context.asciidoc @@ -13,10 +13,9 @@ part of a full metric aggregation. `params` (`Map`, read-only):: User-defined parameters passed in as part of the query. -`params['_agg']` (`Map`):: +`state` (`Map`):: `Map` used to add values for processing in a - <> or returned - directly. + <> or to be returned from the aggregation. `doc` (`Map`, read-only):: Contains the fields of the current document where each field is a @@ -27,15 +26,16 @@ part of a full metric aggregation. *Side Effects* -`params['_agg']` (`Map`):: +`state` (`Map`):: Use this `Map` to add values for processing in a combine script. Additional values must be of the type `Map`, `List`, `String` or - primitive. If an initialization script is provided as part the + primitive. The same `state` `Map` is shared between all aggregated documents + on a given shard. If an initialization script is provided as part of the aggregation then values added from the initialization script are - available as well. If no combine script is specified, values must be - directly stored in `_agg`. If no combine script and no + available. If no combine script is specified, values must be + directly stored in `state` in a usable form. If no combine script and no <> are specified, the - values are used as the result. + `state` values are used as the result. *Return* diff --git a/docs/painless/painless-contexts/painless-metric-agg-reduce-context.asciidoc b/docs/painless/painless-contexts/painless-metric-agg-reduce-context.asciidoc index b76e02b1b0499..b492207ef4468 100644 --- a/docs/painless/painless-contexts/painless-metric-agg-reduce-context.asciidoc +++ b/docs/painless/painless-contexts/painless-metric-agg-reduce-context.asciidoc @@ -14,7 +14,7 @@ specified) and is optional as part of a full metric aggregation. `params` (`Map`, read-only):: User-defined parameters passed in as part of the query. -`params['_aggs']` (`Map`):: +`states` (`Map`):: `Map` with values available from the prior combine script (or a map script if no combine script is specified). diff --git a/docs/painless/painless-getting-started.asciidoc b/docs/painless/painless-getting-started.asciidoc index 1dec4a33bb583..8cff207ab04d5 100644 --- a/docs/painless/painless-getting-started.asciidoc +++ b/docs/painless/painless-getting-started.asciidoc @@ -198,7 +198,7 @@ POST hockey/player/1/_update ==== Dates Date fields are exposed as -`ReadableDateTime` +`ReadableDateTime` or so they support methods like `getYear`, and `getDayOfWeek`. @@ -220,6 +220,11 @@ GET hockey/_search } ---------------------------------------------------------------- // CONSOLE +// TEST[warning:The joda time api for doc values is deprecated. Use -Des.scripting.use_java_time=true to use the java time api for date field doc values] + +NOTE: Date fields are changing in 7.0 to be exposed as `ZonedDateTime` +from Java 8's time API. To switch to this functionality early, +add `-Des.scripting.use_java_time=true` to `jvm.options`. [float] [[modules-scripting-painless-regex]] diff --git a/docs/painless/painless-keywords.asciidoc b/docs/painless/painless-keywords.asciidoc index 9463902c8d346..24371d3713c0b 100644 --- a/docs/painless/painless-keywords.asciidoc +++ b/docs/painless/painless-keywords.asciidoc @@ -5,7 +5,7 @@ Keywords are reserved tokens for built-in language features. *Errors* -If a keyword is used as an <>. +* If a keyword is used as an <>. *Keywords* diff --git a/docs/plugins/discovery-ec2.asciidoc b/docs/plugins/discovery-ec2.asciidoc index 5cdc568e16250..f79e73fcf55db 100644 --- a/docs/plugins/discovery-ec2.asciidoc +++ b/docs/plugins/discovery-ec2.asciidoc @@ -37,7 +37,6 @@ bin/elasticsearch-keystore add discovery.ec2.secret_key The following are the available discovery settings. All should be prefixed with `discovery.ec2.`. Those that must be stored in the keystore are marked as `Secure`. - `access_key`:: An ec2 access key. The `secret_key` setting must also be specified. (Secure) @@ -122,6 +121,10 @@ Defaults to `private_ip`. How long the list of hosts is cached to prevent further requests to the AWS API. Defaults to `10s`. +*All* secure settings of this plugin are {ref}/secure-settings.html#reloadable-secure-settings[reloadable]. +After you reload the settings, an aws sdk client with the latest settings +from the keystore will be used. + [IMPORTANT] .Binding the network host ============================================== diff --git a/docs/plugins/repository-azure.asciidoc b/docs/plugins/repository-azure.asciidoc index 893acf05a4bad..13c6a7b62ccbd 100644 --- a/docs/plugins/repository-azure.asciidoc +++ b/docs/plugins/repository-azure.asciidoc @@ -11,7 +11,7 @@ include::install_remove.asciidoc[] ==== Azure Repository To enable Azure repositories, you have first to define your azure storage settings as -{ref}/secure-settings.html[secured settings]: +{ref}/secure-settings.html[secure settings], before starting up the node: [source,sh] ---------------------------------------------------------------- @@ -20,6 +20,7 @@ bin/elasticsearch-keystore add azure.client.default.key ---------------------------------------------------------------- Where `account` is the azure account name and `key` the azure secret key. +These settings are used by the repository's internal azure client. Note that you can also define more than one account: @@ -31,7 +32,18 @@ bin/elasticsearch-keystore add azure.client.secondary.account bin/elasticsearch-keystore add azure.client.secondary.key ---------------------------------------------------------------- -`default` is the default account name which will be used by a repository unless you set an explicit one. +`default` is the default account name which will be used by a repository, +unless you set an explicit one in the +<>. + +Both `account` and `key` storage settings are +{ref}/secure-settings.html#reloadable-secure-settings[reloadable]. After you +reload the settings, the internal azure clients, which are used to transfer the +snapshot, will utilize the latest settings from the keystore. + +NOTE: In progress snapshot/restore jobs will not be preempted by a *reload* +of the storage secure settings. They will complete using the client as it was built +when the operation started. You can set the client side timeout to use when making any single request. It can be defined globally, per account or both. It's not set by default which means that Elasticsearch is using the diff --git a/docs/plugins/repository-gcs.asciidoc b/docs/plugins/repository-gcs.asciidoc index 84fb47d1761e4..8292325738506 100644 --- a/docs/plugins/repository-gcs.asciidoc +++ b/docs/plugins/repository-gcs.asciidoc @@ -112,6 +112,15 @@ PUT _snapshot/my_gcs_repository // CONSOLE // TEST[skip:we don't have gcs setup while testing this] +The `credentials_file` settings are {ref}/secure-settings.html#reloadable-secure-settings[reloadable]. +After you reload the settings, the internal `gcs` clients, used to transfer the +snapshot contents, will utilize the latest settings from the keystore. + + +NOTE: In progress snapshot/restore jobs will not be preempted by a *reload* +of the client's `credentials_file` settings. They will complete using the client +as it was built when the operation started. + [[repository-gcs-client]] ==== Client Settings diff --git a/docs/plugins/repository-s3.asciidoc b/docs/plugins/repository-s3.asciidoc index 19ead367204ba..b065951856a45 100644 --- a/docs/plugins/repository-s3.asciidoc +++ b/docs/plugins/repository-s3.asciidoc @@ -35,9 +35,9 @@ PUT _snapshot/my_s3_repository ==== Client Settings The client used to connect to S3 has a number of settings available. Client setting names are of -the form `s3.client.CLIENT_NAME.SETTING_NAME` and specified inside `elasticsearch.yml`. The -default client name looked up by a `s3` repository is called `default`, but can be customized -with the repository setting `client`. For example: +the form `s3.client.CLIENT_NAME.SETTING_NAME`. The default client name, which is looked up by +an `s3` repository, is called `default`. It can be modified using the +<> `client`. For example: [source,js] ---- @@ -53,8 +53,11 @@ PUT _snapshot/my_s3_repository // CONSOLE // TEST[skip:we don't have s3 setup while testing this] -Some settings are sensitive and must be stored in the {ref}/secure-settings.html[elasticsearch keystore]. -For example, to use explicit AWS access keys: +Most client settings are specified inside `elasticsearch.yml`, but some are +sensitive and must be stored in the {ref}/secure-settings.html[elasticsearch keystore]. + +For example, before you start the node, run these commands to add AWS access +key settings to the keystore: [source,sh] ---- @@ -62,8 +65,19 @@ bin/elasticsearch-keystore add s3.client.default.access_key bin/elasticsearch-keystore add s3.client.default.secret_key ---- -The following are the available client settings. Those that must be stored in the keystore -are marked as `Secure`. +*All* client secure settings of this plugin are +{ref}/secure-settings.html#reloadable-secure-settings[reloadable]. After you +reload the settings, the internal `s3` clients, used to transfer the snapshot +contents, will utilize the latest settings from the keystore. Any existing `s3` +repositories, as well as any newly created ones, will pick up the new values +stored in the keystore. + +NOTE: In progress snapshot/restore tasks will not be preempted by a *reload* +of the client's secure settings. The task will complete using the client as it +was built when the operation started. + +The following is the list of all the available client settings. +Those that must be stored in the keystore are marked as `Secure` and are *reloadable*. `access_key`:: diff --git a/docs/reference/aggregations/bucket/datehistogram-aggregation.asciidoc b/docs/reference/aggregations/bucket/datehistogram-aggregation.asciidoc index efbd8ef7389bb..e19ecac462d14 100644 --- a/docs/reference/aggregations/bucket/datehistogram-aggregation.asciidoc +++ b/docs/reference/aggregations/bucket/datehistogram-aggregation.asciidoc @@ -425,6 +425,7 @@ POST /sales/_search?size=0 -------------------------------------------------- // CONSOLE // TEST[setup:sales] +// TEST[warning:The joda time api for doc values is deprecated. Use -Des.scripting.use_java_time=true to use the java time api for date field doc values] Response: diff --git a/docs/reference/aggregations/bucket/terms-aggregation.asciidoc b/docs/reference/aggregations/bucket/terms-aggregation.asciidoc index 551c082a6629e..f42d176aea06c 100644 --- a/docs/reference/aggregations/bucket/terms-aggregation.asciidoc +++ b/docs/reference/aggregations/bucket/terms-aggregation.asciidoc @@ -653,7 +653,7 @@ GET /_search // CONSOLE In the above example, buckets will be created for all the tags that has the word `sport` in them, except those starting -with `water_` (so the tag `water_sports` will no be aggregated). The `include` regular expression will determine what +with `water_` (so the tag `water_sports` will not be aggregated). The `include` regular expression will determine what values are "allowed" to be aggregated, while the `exclude` determines the values that should not be aggregated. When both are defined, the `exclude` has precedence, meaning, the `include` is evaluated first and only then the `exclude`. diff --git a/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc b/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc index 3da1c60db0552..a998e6b11f9ab 100644 --- a/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc +++ b/docs/reference/analysis/charfilters/pattern-replace-charfilter.asciidoc @@ -87,7 +87,7 @@ The above example produces the following term: [source,text] --------------------------- -[ My, credit, card, is 123_456_789 ] +[ My, credit, card, is, 123_456_789 ] --------------------------- WARNING: Using a replacement string that changes the length of the original diff --git a/docs/reference/cluster/stats.asciidoc b/docs/reference/cluster/stats.asciidoc index 191da2660d668..3de850418719d 100644 --- a/docs/reference/cluster/stats.asciidoc +++ b/docs/reference/cluster/stats.asciidoc @@ -22,6 +22,7 @@ Will return, for example: "successful" : 1, "failed" : 0 }, + "cluster_uuid": "YjAvIhsCQ9CbjWZb2qJw3Q", "cluster_name": "elasticsearch", "timestamp": 1459427693515, "status": "green", diff --git a/docs/reference/index-modules/slowlog.asciidoc b/docs/reference/index-modules/slowlog.asciidoc index 1dc84f380f509..235256bdce7c0 100644 --- a/docs/reference/index-modules/slowlog.asciidoc +++ b/docs/reference/index-modules/slowlog.asciidoc @@ -50,7 +50,7 @@ appender.index_search_slowlog_rolling.type = RollingFile appender.index_search_slowlog_rolling.name = index_search_slowlog_rolling appender.index_search_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog.log appender.index_search_slowlog_rolling.layout.type = PatternLayout -appender.index_search_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%n +appender.index_search_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %.-10000m%n appender.index_search_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_search_slowlog-%i.log.gz appender.index_search_slowlog_rolling.policies.type = Policies appender.index_search_slowlog_rolling.policies.size.type = SizeBasedTriggeringPolicy @@ -103,7 +103,7 @@ appender.index_indexing_slowlog_rolling.type = RollingFile appender.index_indexing_slowlog_rolling.name = index_indexing_slowlog_rolling appender.index_indexing_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog.log appender.index_indexing_slowlog_rolling.layout.type = PatternLayout -appender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%n +appender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %.-10000m%n appender.index_indexing_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog-%i.log.gz appender.index_indexing_slowlog_rolling.policies.type = Policies appender.index_indexing_slowlog_rolling.policies.size.type = SizeBasedTriggeringPolicy diff --git a/docs/reference/indices/aliases.asciidoc b/docs/reference/indices/aliases.asciidoc index 8e0e84c6657be..81a96510e46cd 100644 --- a/docs/reference/indices/aliases.asciidoc +++ b/docs/reference/indices/aliases.asciidoc @@ -257,6 +257,9 @@ and there are multiple indices referenced by an alias, then writes will not be a It is possible to specify an index associated with an alias as a write index using both the aliases API and index creation API. +Setting an index to be the write index with an alias also affects how the alias is manipulated during +Rollover (see <>). + [source,js] -------------------------------------------------- POST /_aliases diff --git a/docs/reference/indices/rollover-index.asciidoc b/docs/reference/indices/rollover-index.asciidoc index 1e3a361f1b14e..1730c95e0dd24 100644 --- a/docs/reference/indices/rollover-index.asciidoc +++ b/docs/reference/indices/rollover-index.asciidoc @@ -4,10 +4,19 @@ The rollover index API rolls an alias over to a new index when the existing index is considered to be too large or too old. -The API accepts a single alias name and a list of `conditions`. The alias -must point to a single index only. If the index satisfies the specified -conditions then a new index is created and the alias is switched to point to -the new index. +The API accepts a single alias name and a list of `conditions`. The alias must point to a write index for +a Rollover request to be valid. There are two ways this can be achieved, and depending on the configuration, the +alias metadata will be updated differently. The two scenarios are as follows: + + - The alias only points to a single index with `is_write_index` not configured (defaults to `null`). + +In this scenario, the original index will have their rollover alias will be added to the newly created index, and removed +from the original (rolled-over) index. + + - The alias points to one or more indices with `is_write_index` set to `true` on the index to be rolled over (the write index). + +In this scenario, the write index will have its rollover alias' `is_write_index` set to `false`, while the newly created index +will now have the rollover alias pointing to it as the write index with `is_write_index` as `true`. [source,js] @@ -231,3 +240,98 @@ POST /logs_write/_rollover?dry_run Because the rollover operation creates a new index to rollover to, the <> setting on index creation applies to the rollover action as well. + +[[indices-rollover-is-write-index]] +[float] +=== Write Index Alias Behavior + +The rollover alias when rolling over a write index that has `is_write_index` explicitly set to `true` is not +swapped during rollover actions. Since having an alias point to multiple indices is ambiguous in distinguishing +which is the correct write index to roll over, it is not valid to rollover an alias that points to multiple indices. +For this reason, the default behavior is to swap which index is being pointed to by the write-oriented alias. This +was `logs_write` in some of the above examples. Since setting `is_write_index` enables an alias to point to multiple indices +while also being explicit as to which is the write index that rollover should target, removing the alias from the rolled over +index is not necessary. This simplifies things by allowing for one alias to behave both as the write and read aliases for +indices that are being managed with Rollover. + +Look at the behavior of the aliases in the following example where `is_write_index` is set on the rolled over index. + +[source,js] +-------------------------------------------------- +PUT my_logs_index-000001 +{ + "aliases": { + "logs": { "is_write_index": true } <1> + } +} + +PUT logs/_doc/1 +{ + "message": "a dummy log" +} + +POST logs/_refresh + +POST /logs/_rollover +{ + "conditions": { + "max_docs": "1" + } +} + +PUT logs/_doc/2 <2> +{ + "message": "a newer log" +} +-------------------------------------------------- +// CONSOLE +<1> configures `my_logs_index` as the write index for the `logs` alias +<2> newly indexed documents against the `logs` alias will write to the new index + +[source,js] +-------------------------------------------------- +{ + "_index" : "my_logs_index-000002", + "_type" : "_doc", + "_id" : "2", + "_version" : 1, + "result" : "created", + "_shards" : { + "total" : 2, + "successful" : 1, + "failed" : 0 + }, + "_seq_no" : 0, + "_primary_term" : 1 +} +-------------------------------------------------- +// TESTRESPONSE + +////////////////////////// +[source,js] +-------------------------------------------------- +GET _alias +-------------------------------------------------- +// CONSOLE +// TEST[continued] +////////////////////////// + +After the rollover, the alias metadata for the two indices will have the `is_write_index` setting +reflect each index's role, with the newly created index as the write index. + +[source,js] +-------------------------------------------------- +{ + "my_logs_index-000002": { + "aliases": { + "logs": { "is_write_index": true } + } + }, + "my_logs_index-000001": { + "aliases": { + "logs": { "is_write_index" : false } + } + } +} +-------------------------------------------------- +// TESTRESPONSE diff --git a/docs/reference/ingest/ingest-node.asciidoc b/docs/reference/ingest/ingest-node.asciidoc index 79277d22e8128..37c616b234991 100644 --- a/docs/reference/ingest/ingest-node.asciidoc +++ b/docs/reference/ingest/ingest-node.asciidoc @@ -1049,6 +1049,125 @@ understands this to mean `2016-04-01` as is explained in the <> can't be accessed by any processor. + +[[dot-expender-options]] +.Dot Expand Options +[options="header"] +|====== +| Name | Required | Default | Description +| `field` | yes | - | The field to expand into an object field +| `path` | no | - | The field that contains the field to expand. Only required if the field to expand is part another object field, because the `field` option can only understand leaf fields. +|====== + +[source,js] +-------------------------------------------------- +{ + "dot_expander": { + "field": "foo.bar" + } +} +-------------------------------------------------- +// NOTCONSOLE + +For example the dot expand processor would turn this document: + +[source,js] +-------------------------------------------------- +{ + "foo.bar" : "value" +} +-------------------------------------------------- +// NOTCONSOLE + +into: + +[source,js] +-------------------------------------------------- +{ + "foo" : { + "bar" : "value" + } +} +-------------------------------------------------- +// NOTCONSOLE + +If there is already a `bar` field nested under `foo` then +this processor merges the `foo.bar` field into it. If the field is +a scalar value then it will turn that field into an array field. + +For example, the following document: + +[source,js] +-------------------------------------------------- +{ + "foo.bar" : "value2", + "foo" : { + "bar" : "value1" + } +} +-------------------------------------------------- +// NOTCONSOLE + +is transformed by the `dot_expander` processor into: + +[source,js] +-------------------------------------------------- +{ + "foo" : { + "bar" : ["value1", "value2"] + } +} +-------------------------------------------------- +// NOTCONSOLE + +If any field outside of the leaf field conflicts with a pre-existing field of the same name, +then that field needs to be renamed first. + +Consider the following document: + +[source,js] +-------------------------------------------------- +{ + "foo": "value1", + "foo.bar": "value2" +} +-------------------------------------------------- +// NOTCONSOLE + +Then the `foo` needs to be renamed first before the `dot_expander` +processor is applied. So in order for the `foo.bar` field to properly +be expanded into the `bar` field under the `foo` field the following +pipeline should be used: + +[source,js] +-------------------------------------------------- +{ + "processors" : [ + { + "rename" : { + "field" : "foo", + "target_field" : "foo.bar"" + } + }, + { + "dot_expander": { + "field": "foo.bar" + } + } + ] +} +-------------------------------------------------- +// NOTCONSOLE + +The reason for this is that Ingest doesn't know how to automatically cast +a scalar field to an object field. + [[fail-processor]] === Fail Processor Raises an exception. This is useful for when @@ -2058,125 +2177,6 @@ Converts a string to its uppercase equivalent. -------------------------------------------------- // NOTCONSOLE -[[dot-expand-processor]] -=== Dot Expander Processor - -Expands a field with dots into an object field. This processor allows fields -with dots in the name to be accessible by other processors in the pipeline. -Otherwise these <> can't be accessed by any processor. - -[[dot-expender-options]] -.Dot Expand Options -[options="header"] -|====== -| Name | Required | Default | Description -| `field` | yes | - | The field to expand into an object field -| `path` | no | - | The field that contains the field to expand. Only required if the field to expand is part another object field, because the `field` option can only understand leaf fields. -|====== - -[source,js] --------------------------------------------------- -{ - "dot_expander": { - "field": "foo.bar" - } -} --------------------------------------------------- -// NOTCONSOLE - -For example the dot expand processor would turn this document: - -[source,js] --------------------------------------------------- -{ - "foo.bar" : "value" -} --------------------------------------------------- -// NOTCONSOLE - -into: - -[source,js] --------------------------------------------------- -{ - "foo" : { - "bar" : "value" - } -} --------------------------------------------------- -// NOTCONSOLE - -If there is already a `bar` field nested under `foo` then -this processor merges the `foo.bar` field into it. If the field is -a scalar value then it will turn that field into an array field. - -For example, the following document: - -[source,js] --------------------------------------------------- -{ - "foo.bar" : "value2", - "foo" : { - "bar" : "value1" - } -} --------------------------------------------------- -// NOTCONSOLE - -is transformed by the `dot_expander` processor into: - -[source,js] --------------------------------------------------- -{ - "foo" : { - "bar" : ["value1", "value2"] - } -} --------------------------------------------------- -// NOTCONSOLE - -If any field outside of the leaf field conflicts with a pre-existing field of the same name, -then that field needs to be renamed first. - -Consider the following document: - -[source,js] --------------------------------------------------- -{ - "foo": "value1", - "foo.bar": "value2" -} --------------------------------------------------- -// NOTCONSOLE - -Then the `foo` needs to be renamed first before the `dot_expander` -processor is applied. So in order for the `foo.bar` field to properly -be expanded into the `bar` field under the `foo` field the following -pipeline should be used: - -[source,js] --------------------------------------------------- -{ - "processors" : [ - { - "rename" : { - "field" : "foo", - "target_field" : "foo.bar"" - } - }, - { - "dot_expander": { - "field": "foo.bar" - } - } - ] -} --------------------------------------------------- -// NOTCONSOLE - -The reason for this is that Ingest doesn't know how to automatically cast -a scalar field to an object field. - [[urldecode-processor]] === URL Decode Processor URL-decodes a string diff --git a/docs/reference/query-dsl/fuzzy-query.asciidoc b/docs/reference/query-dsl/fuzzy-query.asciidoc index fa11cb257d4b4..4be546916240f 100644 --- a/docs/reference/query-dsl/fuzzy-query.asciidoc +++ b/docs/reference/query-dsl/fuzzy-query.asciidoc @@ -5,10 +5,10 @@ The fuzzy query uses similarity based on Levenshtein edit distance. ==== String fields -The `fuzzy` query generates all possible matching terms that are within the +The `fuzzy` query generates matching terms that are within the maximum edit distance specified in `fuzziness` and then checks the term dictionary to find out which of those generated terms actually exist in the -index. +index. The final query uses up to `max_expansions` matching terms. Here is a simple example: diff --git a/docs/reference/release-notes/7.0.0-alpha1.asciidoc b/docs/reference/release-notes/7.0.0-alpha1.asciidoc index cf2e1e30be050..c3a03d77f8118 100644 --- a/docs/reference/release-notes/7.0.0-alpha1.asciidoc +++ b/docs/reference/release-notes/7.0.0-alpha1.asciidoc @@ -21,4 +21,10 @@ Aggregations:: * The Percentiles and PercentileRanks aggregations now return `null` in the REST response, instead of `NaN`. This makes it consistent with the rest of the aggregations. Note: this only applies to the REST response, the java objects continue to return `NaN` (also - consistent with other aggregations) \ No newline at end of file + consistent with other aggregations) + +Suggesters:: +* Plugins that register suggesters can now define their own types of suggestions and must + explicitly indicate the type of suggestion that they produce. Existing plugins will + require changes to their plugin registration. See the `custom-suggester` example + plugin {pull}30284[#30284] \ No newline at end of file diff --git a/docs/reference/setup/important-settings.asciidoc b/docs/reference/setup/important-settings.asciidoc index b9b99b708031e..8a9b59480a09b 100644 --- a/docs/reference/setup/important-settings.asciidoc +++ b/docs/reference/setup/important-settings.asciidoc @@ -14,6 +14,7 @@ The following settings *must* be considered before going to production: * <> * <> * <> +* <> include::important-settings/path-settings.asciidoc[] @@ -31,4 +32,6 @@ include::important-settings/heap-dump-path.asciidoc[] include::important-settings/gc-logging.asciidoc[] +include::important-settings/es-tmpdir.asciidoc[] + include::important-settings/error-file.asciidoc[] diff --git a/docs/reference/setup/important-settings/es-tmpdir.asciidoc b/docs/reference/setup/important-settings/es-tmpdir.asciidoc new file mode 100644 index 0000000000000..20959d969b879 --- /dev/null +++ b/docs/reference/setup/important-settings/es-tmpdir.asciidoc @@ -0,0 +1,23 @@ +[[es-tmpdir]] +=== Temp directory + +By default, Elasticsearch uses a private temporary directory that the startup +script creates immediately below the system temporary directory. + +On some Linux distributions a system utility will clean files and directories +from `/tmp` if they have not been recently accessed. This can lead to the +private temporary directory being removed while Elasticsearch is running if +features that require the temporary directory are not used for a long time. +This causes problems if a feature that requires the temporary directory is +subsequently used. + +If you install Elasticsearch using the `.deb` or `.rpm` packages and run it +under `systemd` then the private temporary directory that Elasticsearch uses +is excluded from periodic cleanup. + +However, if you intend to run the `.tar.gz` distribution on Linux for an +extended period then you should consider creating a dedicated temporary +directory for Elasticsearch that is not under a path that will have old files +and directories cleaned from it. This directory should have permissions set +so that only the user that Elasticsearch runs as can access it. Then set the +`$ES_TMPDIR` environment variable to point to it before starting Elasticsearch. diff --git a/docs/reference/setup/important-settings/node-name.asciidoc b/docs/reference/setup/important-settings/node-name.asciidoc index fab7ddcf11898..5980d8e284e1c 100644 --- a/docs/reference/setup/important-settings/node-name.asciidoc +++ b/docs/reference/setup/important-settings/node-name.asciidoc @@ -2,7 +2,7 @@ === `node.name` By default, Elasticsearch will use the first seven characters of the randomly -generated UUID as the node id.Note that the node id is persisted and does +generated UUID as the node id. Note that the node id is persisted and does not change when a node restarts and therefore the default node name will also not change. @@ -19,4 +19,4 @@ The `node.name` can also be set to the server's HOSTNAME as follows: [source,yaml] -------------------------------------------------- node.name: ${HOSTNAME} --------------------------------------------------- \ No newline at end of file +-------------------------------------------------- diff --git a/docs/reference/setup/install/docker.asciidoc b/docs/reference/setup/install/docker.asciidoc index 523217b921a43..e2e5c6ab70b62 100644 --- a/docs/reference/setup/install/docker.asciidoc +++ b/docs/reference/setup/install/docker.asciidoc @@ -4,9 +4,9 @@ {es} is also available as Docker images. The images use https://hub.docker.com/_/centos/[centos:7] as the base image. -A list of all published Docker images and tags can be found in -https://www.docker.elastic.co[www.docker.elastic.co]. The source code can be found -on https://github.com/elastic/elasticsearch-docker/tree/{branch}[GitHub]. +A list of all published Docker images and tags is available at +https://www.docker.elastic.co[www.docker.elastic.co]. The source code is in +https://github.com/elastic/elasticsearch-docker/tree/{branch}[GitHub]. These images are free to use under the Elastic license. They contain open source and free commercial features and access to paid commercial features. @@ -29,15 +29,13 @@ endif::[] ifeval::["{release-state}"!="unreleased"] -For example, the Docker image can be retrieved with the following command: - ["source","sh",subs="attributes"] -------------------------------------------- docker pull {docker-repo}:{version} -------------------------------------------- Alternatively, you can download other Docker images that contain only features -that are available under the Apache 2.0 license from +available under the Apache 2.0 license. To download the images, go to https://www.docker.elastic.co[www.docker.elastic.co]. endif::[] diff --git a/docs/reference/setup/logging-config.asciidoc b/docs/reference/setup/logging-config.asciidoc index d35b3db2df7f3..ac949bc941a9c 100644 --- a/docs/reference/setup/logging-config.asciidoc +++ b/docs/reference/setup/logging-config.asciidoc @@ -25,7 +25,7 @@ appender.rolling.type = RollingFile <1> appender.rolling.name = rolling appender.rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}.log <2> appender.rolling.layout.type = PatternLayout -appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%.-10000m%n +appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] [%node_name]%marker %.-10000m%n appender.rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}-%d{yyyy-MM-dd}-%i.log.gz <3> appender.rolling.policies.type = Policies appender.rolling.policies.time.type = TimeBasedTriggeringPolicy <4> diff --git a/docs/reference/setup/secure-settings.asciidoc b/docs/reference/setup/secure-settings.asciidoc index 1474ba137697d..2177440457acf 100644 --- a/docs/reference/setup/secure-settings.asciidoc +++ b/docs/reference/setup/secure-settings.asciidoc @@ -75,3 +75,34 @@ To remove a setting from the keystore, use the `remove` command: bin/elasticsearch-keystore remove the.setting.name.to.remove ---------------------------------------------------------------- +[float] +[[reloadable-secure-settings]] +=== Reloadable secure settings + +Just like the settings values in `elasticsearch.yml`, changes to the +keystore contents are not automatically applied to the running +elasticsearch node. Re-reading settings requires a node restart. +However, certain secure settings are marked as *reloadable*. Such settings +can be re-read and applied on a running node. + +The values of all secure settings, *reloadable* or not, must be identical +across all cluster nodes. After making the desired secure settings changes, +using the `bin/elasticsearch-keystore add` command, call: +[source,js] +---- +POST _nodes/reload_secure_settings +{ + "secure_settings_password": "" +} +---- +// CONSOLE +This API will decrypt and re-read the entire keystore, on every cluster node, +but only the *reloadable* secure settings will be applied. Changes to other +settings will not go into effect until the next restart. Once the call returns, +the reload has been completed, meaning that all internal datastructures dependent +on these settings have been changed. Everything should look as if the settings +had the new value from the start. + +When changing multiple *reloadable* secure settings, modify all of them, on +each cluster node, and then issue a `reload_secure_settings` call, instead +of reloading after each modification. diff --git a/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java b/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java index a8dd91e8b6de2..dbda453e5f9fb 100644 --- a/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java +++ b/docs/src/test/java/org/elasticsearch/smoketest/DocsClientYamlTestSuiteIT.java @@ -92,8 +92,7 @@ protected ClientYamlTestClient initClientYamlTestClient( final List hosts, final Version esVersion, final Version masterVersion) { - return new ClientYamlDocsTestClient(restSpec, restClient, hosts, esVersion, masterVersion, - restClientBuilder -> configureClient(restClientBuilder, restClientSettings())); + return new ClientYamlDocsTestClient(restSpec, restClient, hosts, esVersion, masterVersion, this::getClientBuilderWithSniffedHosts); } /** diff --git a/libs/grok/src/main/java/org/elasticsearch/grok/ThreadWatchdog.java b/libs/grok/src/main/java/org/elasticsearch/grok/ThreadWatchdog.java index d0de7637d2c08..f3515fcfe83b0 100644 --- a/libs/grok/src/main/java/org/elasticsearch/grok/ThreadWatchdog.java +++ b/libs/grok/src/main/java/org/elasticsearch/grok/ThreadWatchdog.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.LongSupplier; @@ -104,6 +106,8 @@ class Default implements ThreadWatchdog { private final long maxExecutionTime; private final LongSupplier relativeTimeSupplier; private final BiFunction> scheduler; + private final AtomicInteger registered = new AtomicInteger(0); + private final AtomicBoolean running = new AtomicBoolean(false); final ConcurrentHashMap registry = new ConcurrentHashMap<>(); private Default(long interval, @@ -114,11 +118,14 @@ private Default(long interval, this.maxExecutionTime = maxExecutionTime; this.relativeTimeSupplier = relativeTimeSupplier; this.scheduler = scheduler; - scheduler.apply(interval, this::interruptLongRunningExecutions); } public void register() { + registered.getAndIncrement(); Long previousValue = registry.put(Thread.currentThread(), relativeTimeSupplier.getAsLong()); + if (running.compareAndSet(false, true) == true) { + scheduler.apply(interval, this::interruptLongRunningExecutions); + } assert previousValue == null; } @@ -129,6 +136,7 @@ public long maxExecutionTimeInMillis() { public void unregister() { Long previousValue = registry.remove(Thread.currentThread()); + registered.decrementAndGet(); assert previousValue != null; } @@ -140,7 +148,11 @@ private void interruptLongRunningExecutions() { // not removing the entry here, this happens in the unregister() method. } } - scheduler.apply(interval, this::interruptLongRunningExecutions); + if (registered.get() > 0) { + scheduler.apply(interval, this::interruptLongRunningExecutions); + } else { + running.set(false); + } } } diff --git a/libs/grok/src/test/java/org/elasticsearch/grok/ThreadWatchdogTests.java b/libs/grok/src/test/java/org/elasticsearch/grok/ThreadWatchdogTests.java index 46faa4ae05d38..29e2351215f60 100644 --- a/libs/grok/src/test/java/org/elasticsearch/grok/ThreadWatchdogTests.java +++ b/libs/grok/src/test/java/org/elasticsearch/grok/ThreadWatchdogTests.java @@ -18,15 +18,25 @@ */ package org.elasticsearch.grok; -import org.elasticsearch.test.ESTestCase; - import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.elasticsearch.test.ESTestCase; +import org.mockito.Mockito; import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; public class ThreadWatchdogTests extends ESTestCase { - + public void testInterrupt() throws Exception { AtomicBoolean run = new AtomicBoolean(true); // to avoid a lingering thread when test has completed ThreadWatchdog watchdog = ThreadWatchdog.newInstance(10, 100, System::currentTimeMillis, (delay, command) -> { @@ -43,7 +53,7 @@ public void testInterrupt() throws Exception { thread.start(); return null; }); - + Map registry = ((ThreadWatchdog.Default) watchdog).registry; assertThat(registry.size(), is(0)); // need to call #register() method on a different thread, assertBusy() fails if current thread gets interrupted @@ -66,5 +76,39 @@ public void testInterrupt() throws Exception { assertThat(registry.size(), is(0)); }); } - + + public void testIdleIfNothingRegistered() throws Exception { + long interval = 1L; + ScheduledExecutorService threadPool = mock(ScheduledExecutorService.class); + ThreadWatchdog watchdog = ThreadWatchdog.newInstance(interval, Long.MAX_VALUE, System::currentTimeMillis, + (delay, command) -> threadPool.schedule(command, delay, TimeUnit.MILLISECONDS)); + // Periodic action is not scheduled because no thread is registered + verifyZeroInteractions(threadPool); + CompletableFuture commandFuture = new CompletableFuture<>(); + // Periodic action is scheduled because a thread is registered + doAnswer(invocationOnMock -> { + commandFuture.complete((Runnable) invocationOnMock.getArguments()[0]); + return null; + }).when(threadPool).schedule( + any(Runnable.class), eq(interval), eq(TimeUnit.MILLISECONDS) + ); + watchdog.register(); + // Registering the first thread should have caused the command to get scheduled again + Runnable command = commandFuture.get(1L, TimeUnit.MILLISECONDS); + Mockito.reset(threadPool); + watchdog.unregister(); + command.run(); + // Periodic action is not scheduled again because no thread is registered + verifyZeroInteractions(threadPool); + watchdog.register(); + Thread otherThread = new Thread(watchdog::register); + try { + verify(threadPool).schedule(any(Runnable.class), eq(interval), eq(TimeUnit.MILLISECONDS)); + // Registering a second thread does not cause the command to get scheduled twice + verifyNoMoreInteractions(threadPool); + otherThread.start(); + } finally { + otherThread.join(); + } + } } diff --git a/libs/x-content/licenses/jackson-core-2.8.10.jar.sha1 b/libs/x-content/licenses/jackson-core-2.8.10.jar.sha1 deleted file mode 100644 index a322d371e265e..0000000000000 --- a/libs/x-content/licenses/jackson-core-2.8.10.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -eb21a035c66ad307e66ec8fce37f5d50fd62d039 \ No newline at end of file diff --git a/libs/x-content/licenses/jackson-core-2.8.11.jar.sha1 b/libs/x-content/licenses/jackson-core-2.8.11.jar.sha1 new file mode 100644 index 0000000000000..e7ad1e74ed6b8 --- /dev/null +++ b/libs/x-content/licenses/jackson-core-2.8.11.jar.sha1 @@ -0,0 +1 @@ +876ead1db19f0c9e79c9789273a3ef8c6fd6c29b \ No newline at end of file diff --git a/libs/x-content/licenses/jackson-dataformat-cbor-2.8.10.jar.sha1 b/libs/x-content/licenses/jackson-dataformat-cbor-2.8.10.jar.sha1 deleted file mode 100644 index 1d3e18e21a694..0000000000000 --- a/libs/x-content/licenses/jackson-dataformat-cbor-2.8.10.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -1c58cc9313ddf19f0900cd61ed044874278ce320 \ No newline at end of file diff --git a/libs/x-content/licenses/jackson-dataformat-cbor-2.8.11.jar.sha1 b/libs/x-content/licenses/jackson-dataformat-cbor-2.8.11.jar.sha1 new file mode 100644 index 0000000000000..378ba524422bc --- /dev/null +++ b/libs/x-content/licenses/jackson-dataformat-cbor-2.8.11.jar.sha1 @@ -0,0 +1 @@ +8b9826e16c3366764bfb7ad7362554f0471046c3 \ No newline at end of file diff --git a/libs/x-content/licenses/jackson-dataformat-smile-2.8.10.jar.sha1 b/libs/x-content/licenses/jackson-dataformat-smile-2.8.10.jar.sha1 deleted file mode 100644 index 4f4cacde22079..0000000000000 --- a/libs/x-content/licenses/jackson-dataformat-smile-2.8.10.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -e853081fadaad3e98ed801937acc3d8f77580686 \ No newline at end of file diff --git a/libs/x-content/licenses/jackson-dataformat-smile-2.8.11.jar.sha1 b/libs/x-content/licenses/jackson-dataformat-smile-2.8.11.jar.sha1 new file mode 100644 index 0000000000000..510afb3df53e6 --- /dev/null +++ b/libs/x-content/licenses/jackson-dataformat-smile-2.8.11.jar.sha1 @@ -0,0 +1 @@ +d9d1c49c5d9d5e46e2aee55f3cdd119286fe0fc1 \ No newline at end of file diff --git a/libs/x-content/licenses/jackson-dataformat-yaml-2.8.10.jar.sha1 b/libs/x-content/licenses/jackson-dataformat-yaml-2.8.10.jar.sha1 deleted file mode 100644 index 40bcb05f69795..0000000000000 --- a/libs/x-content/licenses/jackson-dataformat-yaml-2.8.10.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -1e08caf1d787c825307d8cc6362452086020d853 \ No newline at end of file diff --git a/libs/x-content/licenses/jackson-dataformat-yaml-2.8.11.jar.sha1 b/libs/x-content/licenses/jackson-dataformat-yaml-2.8.11.jar.sha1 new file mode 100644 index 0000000000000..78a68d715ec3d --- /dev/null +++ b/libs/x-content/licenses/jackson-dataformat-yaml-2.8.11.jar.sha1 @@ -0,0 +1 @@ +2e77c6ff7342cd61ab1ae7cb14ed16aebfc8a72a \ No newline at end of file diff --git a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentFactory.java b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentFactory.java index fb871590df7fd..38bc251be41dd 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentFactory.java +++ b/libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentFactory.java @@ -35,7 +35,7 @@ */ public class XContentFactory { - private static final int GUESS_HEADER_LENGTH = 20; + static final int GUESS_HEADER_LENGTH = 20; /** * Returns a content builder using JSON format ({@link org.elasticsearch.common.xcontent.XContentType#JSON}. @@ -153,8 +153,10 @@ public static XContentType xContentType(CharSequence content) { return XContentType.JSON; } // Should we throw a failure here? Smile idea is to use it in bytes.... - if (length > 2 && first == SmileConstants.HEADER_BYTE_1 && content.charAt(1) == SmileConstants.HEADER_BYTE_2 && - content.charAt(2) == SmileConstants.HEADER_BYTE_3) { + if (length > 2 + && first == SmileConstants.HEADER_BYTE_1 + && content.charAt(1) == SmileConstants.HEADER_BYTE_2 + && content.charAt(2) == SmileConstants.HEADER_BYTE_3) { return XContentType.SMILE; } if (length > 2 && first == '-' && content.charAt(1) == '-' && content.charAt(2) == '-') { @@ -227,13 +229,29 @@ public static XContent xContent(byte[] data, int offset, int length) { */ @Deprecated public static XContentType xContentType(InputStream si) throws IOException { + /* + * We need to guess the content type. To do this, we look for the first non-whitespace character and then try to guess the content + * type on the GUESS_HEADER_LENGTH bytes that follow. We do this in a way that does not modify the initial read position in the + * underlying input stream. This is why the input stream must support mark/reset and why we repeatedly mark the read position and + * reset. + */ if (si.markSupported() == false) { throw new IllegalArgumentException("Cannot guess the xcontent type without mark/reset support on " + si.getClass()); } - si.mark(GUESS_HEADER_LENGTH); + si.mark(Integer.MAX_VALUE); try { + // scan until we find the first non-whitespace character or the end of the stream + int current; + do { + current = si.read(); + if (current == -1) { + return null; + } + } while (Character.isWhitespace((char) current)); + // now guess the content type off the next GUESS_HEADER_LENGTH bytes including the current byte final byte[] firstBytes = new byte[GUESS_HEADER_LENGTH]; - int read = 0; + firstBytes[0] = (byte) current; + int read = 1; while (read < GUESS_HEADER_LENGTH) { final int r = si.read(firstBytes, read, GUESS_HEADER_LENGTH - read); if (r == -1) { @@ -245,6 +263,7 @@ public static XContentType xContentType(InputStream si) throws IOException { } finally { si.reset(); } + } /** @@ -278,15 +297,17 @@ public static XContentType xContentType(byte[] bytes, int offset, int length) { if (first == '{') { return XContentType.JSON; } - if (length > 2 && first == SmileConstants.HEADER_BYTE_1 && bytes[offset + 1] == SmileConstants.HEADER_BYTE_2 && - bytes[offset + 2] == SmileConstants.HEADER_BYTE_3) { + if (length > 2 + && first == SmileConstants.HEADER_BYTE_1 + && bytes[offset + 1] == SmileConstants.HEADER_BYTE_2 + && bytes[offset + 2] == SmileConstants.HEADER_BYTE_3) { return XContentType.SMILE; } if (length > 2 && first == '-' && bytes[offset + 1] == '-' && bytes[offset + 2] == '-') { return XContentType.YAML; } // CBOR logic similar to CBORFactory#hasCBORFormat - if (first == CBORConstants.BYTE_OBJECT_INDEFINITE && length > 1){ + if (first == CBORConstants.BYTE_OBJECT_INDEFINITE && length > 1) { return XContentType.CBOR; } if (CBORConstants.hasMajorType(CBORConstants.MAJOR_TYPE_TAG, first) && length > 2) { diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/GrokProcessor.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/GrokProcessor.java index 7bb3ebfba6e36..88cba512b86cb 100644 --- a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/GrokProcessor.java +++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/GrokProcessor.java @@ -68,8 +68,7 @@ public void execute(IngestDocument ingestDocument) throws Exception { throw new IllegalArgumentException("Provided Grok expressions do not match field value: [" + fieldValue + "]"); } - matches.entrySet().stream() - .forEach((e) -> ingestDocument.setFieldValue(e.getKey(), e.getValue())); + matches.forEach(ingestDocument::setFieldValue); if (traceMatch) { if (matchPatterns.size() > 1) { diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/ConvertProcessorTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/ConvertProcessorTests.java index 500f1aa1719c3..34fd4ec27a3b3 100644 --- a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/ConvertProcessorTests.java +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/ConvertProcessorTests.java @@ -67,10 +67,9 @@ public void testConvertIntLeadingZero() throws Exception { assertThat(ingestDocument.getFieldValue(fieldName, Integer.class), equalTo(10)); } - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/32370") public void testConvertIntHexError() { IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); - String value = "0x" + randomAlphaOfLengthBetween(1, 10); + String value = "0xnotanumber"; String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, value); Processor processor = new ConvertProcessor(randomAlphaOfLength(10), fieldName, fieldName, Type.INTEGER, false); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> processor.execute(ingestDocument)); @@ -138,10 +137,9 @@ public void testConvertLongLeadingZero() throws Exception { assertThat(ingestDocument.getFieldValue(fieldName, Long.class), equalTo(10L)); } - @AwaitsFix( bugUrl = "https://github.com/elastic/elasticsearch/issues/32370") public void testConvertLongHexError() { IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random()); - String value = "0x" + randomAlphaOfLengthBetween(1, 10); + String value = "0xnotanumber"; String fieldName = RandomDocumentPicks.addRandomField(random(), ingestDocument, value); Processor processor = new ConvertProcessor(randomAlphaOfLength(10), fieldName, fieldName, Type.LONG, false); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> processor.execute(ingestDocument)); diff --git a/modules/ingest-common/src/test/resources/rest-api-spec/test/ingest/200_default_pipeline.yml b/modules/ingest-common/src/test/resources/rest-api-spec/test/ingest/200_default_pipeline.yml new file mode 100644 index 0000000000000..c20d7698131bf --- /dev/null +++ b/modules/ingest-common/src/test/resources/rest-api-spec/test/ingest/200_default_pipeline.yml @@ -0,0 +1,73 @@ +--- +teardown: + - do: + ingest.delete_pipeline: + id: "my_pipeline" + ignore: 404 + +--- +"Test index with default pipeline": + - do: + ingest.put_pipeline: + id: "my_pipeline" + body: > + { + "description": "_description", + "processors": [ + { + "bytes" : { + "field" : "bytes_source_field", + "target_field" : "bytes_target_field" + } + } + ] + } + - match: { acknowledged: true } + + - do: + indices.create: + index: test + body: + settings: + index: + default_pipeline: "my_pipeline" + + - do: + index: + index: test + type: test + id: 1 + body: {bytes_source_field: "1kb"} + + - do: + get: + index: test + type: test + id: 1 + - match: { _source.bytes_source_field: "1kb" } + - match: { _source.bytes_target_field: 1024 } + + - do: + index: + index: test + type: test + id: 2 + pipeline: "_none" + body: {bytes_source_field: "1kb"} + + - do: + get: + index: test + type: test + id: 2 + - match: { _source.bytes_source_field: "1kb" } + - is_false: _source.bytes_target_field + + - do: + catch: bad_request + index: + index: test + type: test + id: 3 + pipeline: "" + body: {bytes_source_field: "1kb"} diff --git a/modules/lang-expression/licenses/lucene-expressions-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/modules/lang-expression/licenses/lucene-expressions-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..0ebdddcc5f1b5 --- /dev/null +++ b/modules/lang-expression/licenses/lucene-expressions-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +fded6bb485b8b01bb2a9280162fd14d4d3ce4510 \ No newline at end of file diff --git a/modules/lang-expression/licenses/lucene-expressions-7.5.0-snapshot-608f0277b0.jar.sha1 b/modules/lang-expression/licenses/lucene-expressions-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index 908f70131b39d..0000000000000 --- a/modules/lang-expression/licenses/lucene-expressions-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -bd7d8078a2d0ad11a24f54156cc015630c96858a \ No newline at end of file diff --git a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java index 1cde9c258b4f1..23dc0fd276cbe 100644 --- a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java +++ b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java @@ -34,10 +34,12 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData; -import org.elasticsearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType; import org.elasticsearch.index.mapper.DateFieldMapper; +import org.elasticsearch.index.mapper.GeoPointFieldMapper.GeoPointFieldType; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.script.BucketAggregationScript; +import org.elasticsearch.script.BucketAggregationSelectorScript; import org.elasticsearch.script.ClassPermission; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.FilterScript; @@ -54,6 +56,7 @@ import java.security.PrivilegedAction; import java.text.ParseException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -112,6 +115,17 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE } else if (context.instanceClazz.equals(ExecutableScript.class)) { ExecutableScript.Factory factory = (p) -> new ExpressionExecutableScript(expr, p); return context.factoryClazz.cast(factory); + } else if (context.instanceClazz.equals(BucketAggregationScript.class)) { + return context.factoryClazz.cast(newBucketAggregationScriptFactory(expr)); + } else if (context.instanceClazz.equals(BucketAggregationSelectorScript.class)) { + BucketAggregationScript.Factory factory = newBucketAggregationScriptFactory(expr); + BucketAggregationSelectorScript.Factory wrappedFactory = parameters -> new BucketAggregationSelectorScript(parameters) { + @Override + public boolean execute() { + return factory.newInstance(getParams()).execute() == 1.0; + } + }; + return context.factoryClazz.cast(wrappedFactory); } else if (context.instanceClazz.equals(FilterScript.class)) { FilterScript.Factory factory = (p, lookup) -> newFilterScript(expr, lookup, p); return context.factoryClazz.cast(factory); @@ -122,6 +136,37 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]"); } + private static BucketAggregationScript.Factory newBucketAggregationScriptFactory(Expression expr) { + return parameters -> { + ReplaceableConstDoubleValues[] functionValuesArray = + new ReplaceableConstDoubleValues[expr.variables.length]; + Map functionValuesMap = new HashMap<>(); + for (int i = 0; i < expr.variables.length; ++i) { + functionValuesArray[i] = new ReplaceableConstDoubleValues(); + functionValuesMap.put(expr.variables[i], functionValuesArray[i]); + } + return new BucketAggregationScript(parameters) { + @Override + public Double execute() { + getParams().forEach((name, value) -> { + ReplaceableConstDoubleValues placeholder = functionValuesMap.get(name); + if (placeholder == null) { + throw new IllegalArgumentException("Error using " + expr + ". " + + "The variable [" + name + "] does not exist in the executable expressions script."); + } else if (value instanceof Number == false) { + throw new IllegalArgumentException("Error using " + expr + ". " + + "Executable expressions scripts can only process numbers." + + " The variable [" + name + "] is not a number."); + } else { + placeholder.setValue(((Number) value).doubleValue()); + } + }); + return expr.evaluate(functionValuesArray); + } + }; + }; + } + private SearchScript.LeafFactory newSearchScript(Expression expr, SearchLookup lookup, @Nullable Map vars) { MapperService mapper = lookup.doc().mapperService(); // NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings, @@ -267,7 +312,7 @@ public void setDocument(int docid) { }; }; } - + private ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup lookup, @Nullable Map vars) { SearchScript.LeafFactory searchLeafFactory = newSearchScript(expr, lookup, vars); return new ScoreScript.LeafFactory() { @@ -284,17 +329,17 @@ public ScoreScript newInstance(LeafReaderContext ctx) throws IOException { public double execute() { return script.runAsDouble(); } - + @Override public void setDocument(int docid) { script.setDocument(docid); } - + @Override public void setScorer(Scorer scorer) { script.setScorer(scorer); } - + @Override public double get_score() { return script.getScore(); diff --git a/modules/lang-painless/build.gradle b/modules/lang-painless/build.gradle index fb1ea441a9dd5..ed4b1d631e064 100644 --- a/modules/lang-painless/build.gradle +++ b/modules/lang-painless/build.gradle @@ -17,8 +17,6 @@ * under the License. */ - - esplugin { description 'An easy, safe and fast scripting language for Elasticsearch' classname 'org.elasticsearch.painless.PainlessPlugin' @@ -26,6 +24,8 @@ esplugin { integTestCluster { module project.project(':modules:mapper-extras') + systemProperty 'es.scripting.use_java_time', 'true' + systemProperty 'es.scripting.update.ctx_in_params', 'false' } dependencies { diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java index 55b64b0420df1..c38325edd1424 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java @@ -28,7 +28,7 @@ * constructors, methods, and fields that can be used within a Painless script at both compile-time * and run-time. * - * A whitelist consists of several pieces with {@link WhitelistClass}s as the top level. Each + * A whitelist consists of several pieces with {@link WhitelistClass}s as the top level. Each * {@link WhitelistClass} will contain zero-to-many {@link WhitelistConstructor}s, {@link WhitelistMethod}s, and * {@link WhitelistField}s which are what will be available with a Painless script. See each individual * whitelist object for more detail. @@ -56,14 +56,14 @@ public final class Whitelist { Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Whitelist.class, BASE_WHITELIST_FILES)); /** The {@link ClassLoader} used to look up the whitelisted Java classes, constructors, methods, and fields. */ - public final ClassLoader javaClassLoader; + public final ClassLoader classLoader; /** The {@link List} of all the whitelisted Painless classes. */ - public final List whitelistStructs; + public final List whitelistClasses; /** Standard constructor. All values must be not {@code null}. */ - public Whitelist(ClassLoader javaClassLoader, List whitelistStructs) { - this.javaClassLoader = Objects.requireNonNull(javaClassLoader); - this.whitelistStructs = Collections.unmodifiableList(Objects.requireNonNull(whitelistStructs)); + public Whitelist(ClassLoader classLoader, List whitelistClasses) { + this.classLoader = Objects.requireNonNull(classLoader); + this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses)); } } diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java index 12aa5f5bdd634..0b216ae5c2953 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistClass.java @@ -30,7 +30,7 @@ * specific context, as long as multiple classes representing the same Java class have the same * class name and have legal constructor/method overloading they can be merged together. * - * Classes in Painless allow for arity overloading for constructors and methods. Arity overloading + * Classes in Painless allow for arity overloading for constructors and methods. Arity overloading * means that multiple constructors are allowed for a single class as long as they have a different * number of parameters, and multiples methods with the same name are allowed for a single class * as long as they have the same return type and a different number of parameters. @@ -40,7 +40,7 @@ */ public final class WhitelistClass { - /** Information about where this class was white-listed from. Can be used for error messages. */ + /** Information about where this class was white-listed from. */ public final String origin; /** The Java class name this class represents. */ @@ -49,7 +49,7 @@ public final class WhitelistClass { /** * Allow the Java class name to only be specified as the fully-qualified name. */ - public final boolean onlyFQNJavaClassName; + public final boolean noImport; /** The {@link List} of whitelisted ({@link WhitelistConstructor}s) available to this class. */ public final List whitelistConstructors; @@ -61,13 +61,14 @@ public final class WhitelistClass { public final List whitelistFields; /** Standard constructor. All values must be not {@code null}. */ - public WhitelistClass(String origin, String javaClassName, boolean onlyFQNJavaClassName, + public WhitelistClass(String origin, String javaClassName, boolean noImport, List whitelistConstructors, List whitelistMethods, List whitelistFields) { + this.origin = Objects.requireNonNull(origin); this.javaClassName = Objects.requireNonNull(javaClassName); - this.onlyFQNJavaClassName = onlyFQNJavaClassName; + this.noImport = noImport; this.whitelistConstructors = Collections.unmodifiableList(Objects.requireNonNull(whitelistConstructors)); this.whitelistMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistMethods)); diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java index 0e70552760208..032ef397def01 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistConstructor.java @@ -25,24 +25,24 @@ /** * Constructor represents the equivalent of a Java constructor available as a whitelisted class - * constructor within Painless. Constructors for Painless classes may be accessed exactly as - * constructors for Java classes are using the 'new' keyword. Painless classes may have multiple + * constructor within Painless. Constructors for Painless classes may be accessed exactly as + * constructors for Java classes are using the 'new' keyword. Painless classes may have multiple * constructors as long as they comply with arity overloading described for {@link WhitelistClass}. */ public final class WhitelistConstructor { - /** Information about where this constructor was whitelisted from. Can be used for error messages. */ + /** Information about where this constructor was whitelisted from. */ public final String origin; /** * 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 painlessParameterTypeNames; + public final List canonicalTypeNameParameters; /** Standard constructor. All values must be not {@code null}. */ - public WhitelistConstructor(String origin, List painlessParameterTypeNames) { + public WhitelistConstructor(String origin, List canonicalTypeNameParameters) { this.origin = Objects.requireNonNull(origin); - this.painlessParameterTypeNames = Collections.unmodifiableList(Objects.requireNonNull(painlessParameterTypeNames)); + this.canonicalTypeNameParameters = Collections.unmodifiableList(Objects.requireNonNull(canonicalTypeNameParameters)); } } diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java index 116aea98fcf89..44ed31a227e1c 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistField.java @@ -23,24 +23,24 @@ /** * Field represents the equivalent of a Java field available as a whitelisted class field - * within Painless. Fields for Painless classes may be accessed exactly as fields for Java classes + * within Painless. Fields for Painless classes may be accessed exactly as fields for Java classes * are using the '.' operator on an existing class variable/field. */ public class WhitelistField { - /** Information about where this method was whitelisted from. Can be used for error messages. */ + /** Information about where this method was whitelisted from. */ public final String origin; - /** The Java field name used to look up the Java field through reflection. */ - public final String javaFieldName; + /** The field name used to look up the field reflection object. */ + public final String fieldName; - /** The Painless type name for the field which can be used to look up the Java field through reflection. */ - public final String painlessFieldTypeName; + /** The canonical type name for the field which can be used to look up the Java field through reflection. */ + public final String canonicalTypeNameParameter; /** Standard constructor. All values must be not {@code null}. */ - public WhitelistField(String origin, String javaFieldName, String painlessFieldTypeName) { + public WhitelistField(String origin, String fieldName, String canonicalTypeNameParameter) { this.origin = Objects.requireNonNull(origin); - this.javaFieldName = Objects.requireNonNull(javaFieldName); - this.painlessFieldTypeName = Objects.requireNonNull(painlessFieldTypeName); + this.fieldName = Objects.requireNonNull(fieldName); + this.canonicalTypeNameParameter = Objects.requireNonNull(canonicalTypeNameParameter); } } diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java index b104d03f1ea82..c59023b862280 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java @@ -35,14 +35,14 @@ public final class WhitelistLoader { /** - * Loads and creates a {@link Whitelist} from one to many text files. The file paths are passed in as an array of + * 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} - * is the path of a single text file. The {@link Class}'s {@link ClassLoader} will be used to lookup the Java + * is the path of a single text file. The {@link Class}'s {@link ClassLoader} will be used to lookup the Java * reflection objects for each individual {@link Class}, {@link Constructor}, {@link Method}, and {@link Field} * specified as part of the whitelist in the text file. * * A single pass is made through each file to collect all the information about each class, constructor, method, - * and field. Most validation will be done at a later point after all whitelists have been gathered and their + * and field. Most validation will be done at a later point after all whitelists have been gathered and their * merging takes place. * * A painless type name is one of the following: @@ -52,7 +52,7 @@ public final class WhitelistLoader { *

  • fully-qualified Java type name - Any whitelisted Java class will have the equivalent name as * a Painless type name with the exception that any dollar symbols used as part of inner classes will * be replaced with dot symbols.
  • - *
  • short Java type name - The text after the final dot symbol of any specified Java class. A + *
  • 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 'only_fqn' token during Painless class parsing * as described later.
  • * @@ -60,7 +60,7 @@ public final class WhitelistLoader { * The following can be parsed from each whitelist text file: *
      *
    • Blank lines will be ignored by the parser.
    • - *
    • Comments may be created starting with a pound '#' symbol and end with a newline. These will + *
    • Comments may be created starting with a pound '#' symbol and end with a newline. These will * be ignored by the parser.
    • *
    • 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.
    • @@ -93,10 +93,10 @@ public final class WhitelistLoader { * * Note there must be a one-to-one correspondence of Painless type names to Java type/class names. * If the same Painless type is defined across multiple files and the Java class is the same, all - * specified constructors, methods, and fields will be merged into a single Painless type. The + * specified constructors, methods, and fields will be merged into a single Painless type. The * Painless dynamic type, 'def', used as part of constructor, method, and field definitions will - * be appropriately parsed and handled. Painless complex types must be specified with the - * fully-qualified Java class name. Method argument types, method return types, and field types + * be appropriately parsed and handled. Painless complex types must be specified with the + * fully-qualified Java class name. Method argument types, method return types, and field types * must be specified with Painless type names (def, fully-qualified, or short) as described earlier. * * The following example is used to create a single whitelist text file: @@ -132,7 +132,7 @@ public final class WhitelistLoader { * } */ public static Whitelist loadFromResourceFiles(Class resource, String... filepaths) { - List whitelistStructs = new ArrayList<>(); + List whitelistClasses = new ArrayList<>(); // Execute a single pass through the whitelist text files. This will gather all the // constructors, methods, augmented methods, and fields for each whitelisted class. @@ -143,7 +143,7 @@ public static Whitelist loadFromResourceFiles(Class resource, String... filep try (LineNumberReader reader = new LineNumberReader( new InputStreamReader(resource.getResourceAsStream(filepath), StandardCharsets.UTF_8))) { - String whitelistStructOrigin = null; + String whitelistClassOrigin = null; String javaClassName = null; boolean onlyFQNJavaClassName = false; List whitelistConstructors = null; @@ -178,7 +178,7 @@ public static Whitelist loadFromResourceFiles(Class resource, String... filep throw new IllegalArgumentException("invalid class definition: failed to parse class name [" + line + "]"); } - whitelistStructOrigin = "[" + filepath + "]:[" + number + "]"; + whitelistClassOrigin = "[" + filepath + "]:[" + number + "]"; javaClassName = tokens[0]; // Reset all the constructors, methods, and fields to support a new class. @@ -194,11 +194,11 @@ public static Whitelist loadFromResourceFiles(Class resource, String... filep throw new IllegalArgumentException("invalid class definition: extraneous closing bracket"); } - whitelistStructs.add(new WhitelistClass(whitelistStructOrigin, javaClassName, onlyFQNJavaClassName, + whitelistClasses.add(new WhitelistClass(whitelistClassOrigin, javaClassName, onlyFQNJavaClassName, whitelistConstructors, whitelistMethods, whitelistFields)); // Set all the variables to null to ensure a new class definition is found before other parsable values. - whitelistStructOrigin = null; + whitelistClassOrigin = null; javaClassName = null; onlyFQNJavaClassName = false; whitelistConstructors = null; @@ -300,7 +300,7 @@ public static Whitelist loadFromResourceFiles(Class resource, String... filep } ClassLoader loader = AccessController.doPrivileged((PrivilegedAction)resource::getClassLoader); - return new Whitelist(loader, whitelistStructs); + return new Whitelist(loader, whitelistClasses); } private WhitelistLoader() {} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistMethod.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistMethod.java index df86619055b08..5cd023a3591ad 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistMethod.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistMethod.java @@ -25,52 +25,53 @@ /** * Method represents the equivalent of a Java method available as a whitelisted class method - * within Painless. Methods for Painless classes may be accessed exactly as methods for Java classes - * are using the '.' operator on an existing class variable/field. Painless classes may have multiple - * methods with the same name as long as they comply with arity overloading described for {@link WhitelistMethod}. + * within Painless. Methods for Painless classes may be accessed exactly as methods for Java classes + * are using the '.' operator on an existing class variable/field. Painless classes may have multiple + * methods with the same name as long as they comply with arity overloading described in + * {@link WhitelistClass}. * * Classes may also have additional methods that are not part of the Java class the class represents - - * these are known as augmented methods. An augmented method can be added to a class as a part of any + * these are known as augmented methods. An augmented method can be added to a class as a part of any * Java class as long as the method is static and the first parameter of the method is the Java class - * represented by the class. Note that the augmented method's parent Java class does not need to be + * represented by the class. Note that the augmented method's parent Java class does not need to be * whitelisted. */ public class WhitelistMethod { - /** Information about where this method was whitelisted from. Can be used for error messages. */ + /** Information about where this method was whitelisted from. */ public final String origin; /** - * The Java class name for the owner of an augmented method. If the method is not augmented + * The class name for the owner of an augmented method. If the method is not augmented * this should be {@code null}. */ - public final String javaAugmentedClassName; + public final String augmentedCanonicalClassName; - /** The Java method name used to look up the Java method through reflection. */ - public final String javaMethodName; + /** The method name used to look up the method reflection object. */ + public final String methodName; /** - * The Painless type name for the return type of the method which can be used to look up the Java - * method through reflection. + * The canonical type name for the return type. */ - public final String painlessReturnTypeName; + public final String returnCanonicalTypeName; /** - * A {@link List} of {@link String}s that are the Painless type names for the parameters of the - * method which can be used to look up the Java method through reflection. + * A {@link List} of {@link String}s that are the canonical type names for the parameters of the + * method used to look up the method reflection object. */ - public final List painlessParameterTypeNames; + public final List canonicalTypeNameParameters; /** - * Standard constructor. All values must be not {@code null} with the exception of jAugmentedClass; - * jAugmentedClass will be {@code null} unless the method is augmented as described in the class documentation. + * 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 javaAugmentedClassName, String javaMethodName, - String painlessReturnTypeName, List painlessParameterTypeNames) { + public WhitelistMethod(String origin, String augmentedCanonicalClassName, String methodName, + String returnCanonicalTypeName, List canonicalTypeNameParameters) { this.origin = Objects.requireNonNull(origin); - this.javaAugmentedClassName = javaAugmentedClassName; - this.javaMethodName = javaMethodName; - this.painlessReturnTypeName = Objects.requireNonNull(painlessReturnTypeName); - this.painlessParameterTypeNames = Collections.unmodifiableList(Objects.requireNonNull(painlessParameterTypeNames)); + this.augmentedCanonicalClassName = augmentedCanonicalClassName; + this.methodName = methodName; + this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName); + this.canonicalTypeNameParameters = Collections.unmodifiableList(Objects.requireNonNull(canonicalTypeNameParameters)); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java index fe53a3c11001c..588fe8ef5f7cf 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java @@ -41,421 +41,421 @@ public static PainlessCast getLegalCast(Location location, Class actual, Clas if (actual == def.class) { if (expected == boolean.class) { - return PainlessCast.unboxTo(def.class, Boolean.class, explicit, boolean.class); + return PainlessCast.unboxTargetType(def.class, Boolean.class, explicit, boolean.class); } else if (expected == byte.class) { - return PainlessCast.unboxTo(def.class, Byte.class, explicit, byte.class); + return PainlessCast.unboxTargetType(def.class, Byte.class, explicit, byte.class); } else if (expected == short.class) { - return PainlessCast.unboxTo(def.class, Short.class, explicit, short.class); + return PainlessCast.unboxTargetType(def.class, Short.class, explicit, short.class); } else if (expected == char.class) { - return PainlessCast.unboxTo(def.class, Character.class, explicit, char.class); + return PainlessCast.unboxTargetType(def.class, Character.class, explicit, char.class); } else if (expected == int.class) { - return PainlessCast.unboxTo(def.class, Integer.class, explicit, int.class); + return PainlessCast.unboxTargetType(def.class, Integer.class, explicit, int.class); } else if (expected == long.class) { - return PainlessCast.unboxTo(def.class, Long.class, explicit, long.class); + return PainlessCast.unboxTargetType(def.class, Long.class, explicit, long.class); } else if (expected == float.class) { - return PainlessCast.unboxTo(def.class, Float.class, explicit, float.class); + return PainlessCast.unboxTargetType(def.class, Float.class, explicit, float.class); } else if (expected == double.class) { - return PainlessCast.unboxTo(def.class, Double.class, explicit, double.class); + return PainlessCast.unboxTargetType(def.class, Double.class, explicit, double.class); } } else if (actual == Object.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxTo(Object.class, Byte.class, true, byte.class); + return PainlessCast.unboxTargetType(Object.class, Byte.class, true, byte.class); } else if (expected == short.class && explicit && internal) { - return PainlessCast.unboxTo(Object.class, Short.class, true, short.class); + return PainlessCast.unboxTargetType(Object.class, Short.class, true, short.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxTo(Object.class, Character.class, true, char.class); + return PainlessCast.unboxTargetType(Object.class, Character.class, true, char.class); } else if (expected == int.class && explicit && internal) { - return PainlessCast.unboxTo(Object.class, Integer.class, true, int.class); + return PainlessCast.unboxTargetType(Object.class, Integer.class, true, int.class); } else if (expected == long.class && explicit && internal) { - return PainlessCast.unboxTo(Object.class, Long.class, true, long.class); + return PainlessCast.unboxTargetType(Object.class, Long.class, true, long.class); } else if (expected == float.class && explicit && internal) { - return PainlessCast.unboxTo(Object.class, Float.class, true, float.class); + return PainlessCast.unboxTargetType(Object.class, Float.class, true, float.class); } else if (expected == double.class && explicit && internal) { - return PainlessCast.unboxTo(Object.class, Double.class, true, double.class); + return PainlessCast.unboxTargetType(Object.class, Double.class, true, double.class); } } else if (actual == Number.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxTo(Number.class, Byte.class, true, byte.class); + return PainlessCast.unboxTargetType(Number.class, Byte.class, true, byte.class); } else if (expected == short.class && explicit && internal) { - return PainlessCast.unboxTo(Number.class, Short.class, true, short.class); + return PainlessCast.unboxTargetType(Number.class, Short.class, true, short.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxTo(Number.class, Character.class, true, char.class); + return PainlessCast.unboxTargetType(Number.class, Character.class, true, char.class); } else if (expected == int.class && explicit && internal) { - return PainlessCast.unboxTo(Number.class, Integer.class, true, int.class); + return PainlessCast.unboxTargetType(Number.class, Integer.class, true, int.class); } else if (expected == long.class && explicit && internal) { - return PainlessCast.unboxTo(Number.class, Long.class, true, long.class); + return PainlessCast.unboxTargetType(Number.class, Long.class, true, long.class); } else if (expected == float.class && explicit && internal) { - return PainlessCast.unboxTo(Number.class, Float.class, true, float.class); + return PainlessCast.unboxTargetType(Number.class, Float.class, true, float.class); } else if (expected == double.class && explicit && internal) { - return PainlessCast.unboxTo(Number.class, Double.class, true, double.class); + return PainlessCast.unboxTargetType(Number.class, Double.class, true, double.class); } } else if (actual == String.class) { if (expected == char.class && explicit) { - return PainlessCast.standard(String.class, char.class, true); + return PainlessCast.originalTypetoTargetType(String.class, char.class, true); } } else if (actual == boolean.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Boolean.class, def.class, explicit, boolean.class); + return PainlessCast.boxOriginalType(Boolean.class, def.class, explicit, boolean.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Boolean.class, Object.class, explicit, boolean.class); + return PainlessCast.boxOriginalType(Boolean.class, Object.class, explicit, boolean.class); } else if (expected == Boolean.class && internal) { - return PainlessCast.boxTo(boolean.class, boolean.class, explicit, boolean.class); + return PainlessCast.boxTargetType(boolean.class, boolean.class, explicit, boolean.class); } } else if (actual == byte.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Byte.class, def.class, explicit, byte.class); + return PainlessCast.boxOriginalType(Byte.class, def.class, explicit, byte.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Byte.class, Object.class, explicit, byte.class); + return PainlessCast.boxOriginalType(Byte.class, Object.class, explicit, byte.class); } else if (expected == Number.class && internal) { - return PainlessCast.boxFrom(Byte.class, Number.class, explicit, byte.class); + return PainlessCast.boxOriginalType(Byte.class, Number.class, explicit, byte.class); } else if (expected == short.class) { - return PainlessCast.standard(byte.class, short.class, explicit); + return PainlessCast.originalTypetoTargetType(byte.class, short.class, explicit); } else if (expected == char.class && explicit) { - return PainlessCast.standard(byte.class, char.class, true); + return PainlessCast.originalTypetoTargetType(byte.class, char.class, true); } else if (expected == int.class) { - return PainlessCast.standard(byte.class, int.class, explicit); + return PainlessCast.originalTypetoTargetType(byte.class, int.class, explicit); } else if (expected == long.class) { - return PainlessCast.standard(byte.class, long.class, explicit); + return PainlessCast.originalTypetoTargetType(byte.class, long.class, explicit); } else if (expected == float.class) { - return PainlessCast.standard(byte.class, float.class, explicit); + return PainlessCast.originalTypetoTargetType(byte.class, float.class, explicit); } else if (expected == double.class) { - return PainlessCast.standard(byte.class, double.class, explicit); + return PainlessCast.originalTypetoTargetType(byte.class, double.class, explicit); } else if (expected == Byte.class && internal) { - return PainlessCast.boxTo(byte.class, byte.class, explicit, byte.class); + return PainlessCast.boxTargetType(byte.class, byte.class, explicit, byte.class); } else if (expected == Short.class && internal) { - return PainlessCast.boxTo(byte.class, short.class, explicit, short.class); + return PainlessCast.boxTargetType(byte.class, short.class, explicit, short.class); } else if (expected == Character.class && explicit && internal) { - return PainlessCast.boxTo(byte.class, char.class, true, char.class); + return PainlessCast.boxTargetType(byte.class, char.class, true, char.class); } else if (expected == Integer.class && internal) { - return PainlessCast.boxTo(byte.class, int.class, explicit, int.class); + return PainlessCast.boxTargetType(byte.class, int.class, explicit, int.class); } else if (expected == Long.class && internal) { - return PainlessCast.boxTo(byte.class, long.class, explicit, long.class); + return PainlessCast.boxTargetType(byte.class, long.class, explicit, long.class); } else if (expected == Float.class && internal) { - return PainlessCast.boxTo(byte.class, float.class, explicit, float.class); + return PainlessCast.boxTargetType(byte.class, float.class, explicit, float.class); } else if (expected == Double.class && internal) { - return PainlessCast.boxTo(byte.class, double.class, explicit, double.class); + return PainlessCast.boxTargetType(byte.class, double.class, explicit, double.class); } } else if (actual == short.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Short.class, def.class, explicit, short.class); + return PainlessCast.boxOriginalType(Short.class, def.class, explicit, short.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Short.class, Object.class, explicit, short.class); + return PainlessCast.boxOriginalType(Short.class, Object.class, explicit, short.class); } else if (expected == Number.class && internal) { - return PainlessCast.boxFrom(Short.class, Number.class, explicit, short.class); + return PainlessCast.boxOriginalType(Short.class, Number.class, explicit, short.class); } else if (expected == byte.class && explicit) { - return PainlessCast.standard(short.class, byte.class, true); + return PainlessCast.originalTypetoTargetType(short.class, byte.class, true); } else if (expected == char.class && explicit) { - return PainlessCast.standard(short.class, char.class, true); + return PainlessCast.originalTypetoTargetType(short.class, char.class, true); } else if (expected == int.class) { - return PainlessCast.standard(short.class, int.class, explicit); + return PainlessCast.originalTypetoTargetType(short.class, int.class, explicit); } else if (expected == long.class) { - return PainlessCast.standard(short.class, long.class, explicit); + return PainlessCast.originalTypetoTargetType(short.class, long.class, explicit); } else if (expected == float.class) { - return PainlessCast.standard(short.class, float.class, explicit); + return PainlessCast.originalTypetoTargetType(short.class, float.class, explicit); } else if (expected == double.class) { - return PainlessCast.standard(short.class, double.class, explicit); + return PainlessCast.originalTypetoTargetType(short.class, double.class, explicit); } else if (expected == Byte.class && explicit && internal) { - return PainlessCast.boxTo(short.class, byte.class, true, byte.class); + return PainlessCast.boxTargetType(short.class, byte.class, true, byte.class); } else if (expected == Short.class && internal) { - return PainlessCast.boxTo(short.class, short.class, explicit, short.class); + return PainlessCast.boxTargetType(short.class, short.class, explicit, short.class); } else if (expected == Character.class && explicit && internal) { - return PainlessCast.boxTo(short.class, char.class, true, char.class); + return PainlessCast.boxTargetType(short.class, char.class, true, char.class); } else if (expected == Integer.class && internal) { - return PainlessCast.boxTo(short.class, int.class, explicit, int.class); + return PainlessCast.boxTargetType(short.class, int.class, explicit, int.class); } else if (expected == Long.class && internal) { - return PainlessCast.boxTo(short.class, long.class, explicit, long.class); + return PainlessCast.boxTargetType(short.class, long.class, explicit, long.class); } else if (expected == Float.class && internal) { - return PainlessCast.boxTo(short.class, float.class, explicit, float.class); + return PainlessCast.boxTargetType(short.class, float.class, explicit, float.class); } else if (expected == Double.class && internal) { - return PainlessCast.boxTo(short.class, double.class, explicit, double.class); + return PainlessCast.boxTargetType(short.class, double.class, explicit, double.class); } } else if (actual == char.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Character.class, def.class, explicit, char.class); + return PainlessCast.boxOriginalType(Character.class, def.class, explicit, char.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Character.class, Object.class, explicit, char.class); + return PainlessCast.boxOriginalType(Character.class, Object.class, explicit, char.class); } else if (expected == Number.class && internal) { - return PainlessCast.boxFrom(Character.class, Number.class, explicit, char.class); + return PainlessCast.boxOriginalType(Character.class, Number.class, explicit, char.class); } else if (expected == String.class) { - return PainlessCast.standard(char.class, String.class, explicit); + return PainlessCast.originalTypetoTargetType(char.class, String.class, explicit); } else if (expected == byte.class && explicit) { - return PainlessCast.standard(char.class, byte.class, true); + return PainlessCast.originalTypetoTargetType(char.class, byte.class, true); } else if (expected == short.class && explicit) { - return PainlessCast.standard(char.class, short.class, true); + return PainlessCast.originalTypetoTargetType(char.class, short.class, true); } else if (expected == int.class) { - return PainlessCast.standard(char.class, int.class, explicit); + return PainlessCast.originalTypetoTargetType(char.class, int.class, explicit); } else if (expected == long.class) { - return PainlessCast.standard(char.class, long.class, explicit); + return PainlessCast.originalTypetoTargetType(char.class, long.class, explicit); } else if (expected == float.class) { - return PainlessCast.standard(char.class, float.class, explicit); + return PainlessCast.originalTypetoTargetType(char.class, float.class, explicit); } else if (expected == double.class) { - return PainlessCast.standard(char.class, double.class, explicit); + return PainlessCast.originalTypetoTargetType(char.class, double.class, explicit); } else if (expected == Byte.class && explicit && internal) { - return PainlessCast.boxTo(char.class, byte.class, true, byte.class); + return PainlessCast.boxTargetType(char.class, byte.class, true, byte.class); } else if (expected == Short.class && internal) { - return PainlessCast.boxTo(char.class, short.class, explicit, short.class); + return PainlessCast.boxTargetType(char.class, short.class, explicit, short.class); } else if (expected == Character.class && internal) { - return PainlessCast.boxTo(char.class, char.class, true, char.class); + return PainlessCast.boxTargetType(char.class, char.class, true, char.class); } else if (expected == Integer.class && internal) { - return PainlessCast.boxTo(char.class, int.class, explicit, int.class); + return PainlessCast.boxTargetType(char.class, int.class, explicit, int.class); } else if (expected == Long.class && internal) { - return PainlessCast.boxTo(char.class, long.class, explicit, long.class); + return PainlessCast.boxTargetType(char.class, long.class, explicit, long.class); } else if (expected == Float.class && internal) { - return PainlessCast.boxTo(char.class, float.class, explicit, float.class); + return PainlessCast.boxTargetType(char.class, float.class, explicit, float.class); } else if (expected == Double.class && internal) { - return PainlessCast.boxTo(char.class, double.class, explicit, double.class); + return PainlessCast.boxTargetType(char.class, double.class, explicit, double.class); } } else if (actual == int.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Integer.class, def.class, explicit, int.class); + return PainlessCast.boxOriginalType(Integer.class, def.class, explicit, int.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Integer.class, Object.class, explicit, int.class); + return PainlessCast.boxOriginalType(Integer.class, Object.class, explicit, int.class); } else if (expected == Number.class && internal) { - return PainlessCast.boxFrom(Integer.class, Number.class, explicit, int.class); + return PainlessCast.boxOriginalType(Integer.class, Number.class, explicit, int.class); } else if (expected == byte.class && explicit) { - return PainlessCast.standard(int.class, byte.class, true); + return PainlessCast.originalTypetoTargetType(int.class, byte.class, true); } else if (expected == char.class && explicit) { - return PainlessCast.standard(int.class, char.class, true); + return PainlessCast.originalTypetoTargetType(int.class, char.class, true); } else if (expected == short.class && explicit) { - return PainlessCast.standard(int.class, short.class, true); + return PainlessCast.originalTypetoTargetType(int.class, short.class, true); } else if (expected == long.class) { - return PainlessCast.standard(int.class, long.class, explicit); + return PainlessCast.originalTypetoTargetType(int.class, long.class, explicit); } else if (expected == float.class) { - return PainlessCast.standard(int.class, float.class, explicit); + return PainlessCast.originalTypetoTargetType(int.class, float.class, explicit); } else if (expected == double.class) { - return PainlessCast.standard(int.class, double.class, explicit); + return PainlessCast.originalTypetoTargetType(int.class, double.class, explicit); } else if (expected == Byte.class && explicit && internal) { - return PainlessCast.boxTo(int.class, byte.class, true, byte.class); + return PainlessCast.boxTargetType(int.class, byte.class, true, byte.class); } else if (expected == Short.class && explicit && internal) { - return PainlessCast.boxTo(int.class, short.class, true, short.class); + return PainlessCast.boxTargetType(int.class, short.class, true, short.class); } else if (expected == Character.class && explicit && internal) { - return PainlessCast.boxTo(int.class, char.class, true, char.class); + return PainlessCast.boxTargetType(int.class, char.class, true, char.class); } else if (expected == Integer.class && internal) { - return PainlessCast.boxTo(int.class, int.class, explicit, int.class); + return PainlessCast.boxTargetType(int.class, int.class, explicit, int.class); } else if (expected == Long.class && internal) { - return PainlessCast.boxTo(int.class, long.class, explicit, long.class); + return PainlessCast.boxTargetType(int.class, long.class, explicit, long.class); } else if (expected == Float.class && internal) { - return PainlessCast.boxTo(int.class, float.class, explicit, float.class); + return PainlessCast.boxTargetType(int.class, float.class, explicit, float.class); } else if (expected == Double.class && internal) { - return PainlessCast.boxTo(int.class, double.class, explicit, double.class); + return PainlessCast.boxTargetType(int.class, double.class, explicit, double.class); } } else if (actual == long.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Long.class, def.class, explicit, long.class); + return PainlessCast.boxOriginalType(Long.class, def.class, explicit, long.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Long.class, Object.class, explicit, long.class); + return PainlessCast.boxOriginalType(Long.class, Object.class, explicit, long.class); } else if (expected == Number.class && internal) { - return PainlessCast.boxFrom(Long.class, Number.class, explicit, long.class); + return PainlessCast.boxOriginalType(Long.class, Number.class, explicit, long.class); } else if (expected == byte.class && explicit) { - return PainlessCast.standard(long.class, byte.class, true); + return PainlessCast.originalTypetoTargetType(long.class, byte.class, true); } else if (expected == char.class && explicit) { - return PainlessCast.standard(long.class, char.class, true); + return PainlessCast.originalTypetoTargetType(long.class, char.class, true); } else if (expected == short.class && explicit) { - return PainlessCast.standard(long.class, short.class, true); + return PainlessCast.originalTypetoTargetType(long.class, short.class, true); } else if (expected == int.class && explicit) { - return PainlessCast.standard(long.class, int.class, true); + return PainlessCast.originalTypetoTargetType(long.class, int.class, true); } else if (expected == float.class) { - return PainlessCast.standard(long.class, float.class, explicit); + return PainlessCast.originalTypetoTargetType(long.class, float.class, explicit); } else if (expected == double.class) { - return PainlessCast.standard(long.class, double.class, explicit); + return PainlessCast.originalTypetoTargetType(long.class, double.class, explicit); } else if (expected == Byte.class && explicit && internal) { - return PainlessCast.boxTo(long.class, byte.class, true, byte.class); + return PainlessCast.boxTargetType(long.class, byte.class, true, byte.class); } else if (expected == Short.class && explicit && internal) { - return PainlessCast.boxTo(long.class, short.class, true, short.class); + return PainlessCast.boxTargetType(long.class, short.class, true, short.class); } else if (expected == Character.class && explicit && internal) { - return PainlessCast.boxTo(long.class, char.class, true, char.class); + return PainlessCast.boxTargetType(long.class, char.class, true, char.class); } else if (expected == Integer.class && explicit && internal) { - return PainlessCast.boxTo(long.class, int.class, true, int.class); + return PainlessCast.boxTargetType(long.class, int.class, true, int.class); } else if (expected == Long.class && internal) { - return PainlessCast.boxTo(long.class, long.class, explicit, long.class); + return PainlessCast.boxTargetType(long.class, long.class, explicit, long.class); } else if (expected == Float.class && internal) { - return PainlessCast.boxTo(long.class, float.class, explicit, float.class); + return PainlessCast.boxTargetType(long.class, float.class, explicit, float.class); } else if (expected == Double.class && internal) { - return PainlessCast.boxTo(long.class, double.class, explicit, double.class); + return PainlessCast.boxTargetType(long.class, double.class, explicit, double.class); } } else if (actual == float.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Float.class, def.class, explicit, float.class); + return PainlessCast.boxOriginalType(Float.class, def.class, explicit, float.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Float.class, Object.class, explicit, float.class); + return PainlessCast.boxOriginalType(Float.class, Object.class, explicit, float.class); } else if (expected == Number.class && internal) { - return PainlessCast.boxFrom(Float.class, Number.class, explicit, float.class); + return PainlessCast.boxOriginalType(Float.class, Number.class, explicit, float.class); } else if (expected == byte.class && explicit) { - return PainlessCast.standard(float.class, byte.class, true); + return PainlessCast.originalTypetoTargetType(float.class, byte.class, true); } else if (expected == char.class && explicit) { - return PainlessCast.standard(float.class, char.class, true); + return PainlessCast.originalTypetoTargetType(float.class, char.class, true); } else if (expected == short.class && explicit) { - return PainlessCast.standard(float.class, short.class, true); + return PainlessCast.originalTypetoTargetType(float.class, short.class, true); } else if (expected == int.class && explicit) { - return PainlessCast.standard(float.class, int.class, true); + return PainlessCast.originalTypetoTargetType(float.class, int.class, true); } else if (expected == long.class && explicit) { - return PainlessCast.standard(float.class, long.class, true); + return PainlessCast.originalTypetoTargetType(float.class, long.class, true); } else if (expected == double.class) { - return PainlessCast.standard(float.class, double.class, explicit); + return PainlessCast.originalTypetoTargetType(float.class, double.class, explicit); } else if (expected == Byte.class && explicit && internal) { - return PainlessCast.boxTo(float.class, byte.class, true, byte.class); + return PainlessCast.boxTargetType(float.class, byte.class, true, byte.class); } else if (expected == Short.class && explicit && internal) { - return PainlessCast.boxTo(float.class, short.class, true, short.class); + return PainlessCast.boxTargetType(float.class, short.class, true, short.class); } else if (expected == Character.class && explicit && internal) { - return PainlessCast.boxTo(float.class, char.class, true, char.class); + return PainlessCast.boxTargetType(float.class, char.class, true, char.class); } else if (expected == Integer.class && explicit && internal) { - return PainlessCast.boxTo(float.class, int.class, true, int.class); + return PainlessCast.boxTargetType(float.class, int.class, true, int.class); } else if (expected == Long.class && explicit && internal) { - return PainlessCast.boxTo(float.class, long.class, true, long.class); + return PainlessCast.boxTargetType(float.class, long.class, true, long.class); } else if (expected == Float.class && internal) { - return PainlessCast.boxTo(float.class, float.class, explicit, float.class); + return PainlessCast.boxTargetType(float.class, float.class, explicit, float.class); } else if (expected == Double.class && internal) { - return PainlessCast.boxTo(float.class, double.class, explicit, double.class); + return PainlessCast.boxTargetType(float.class, double.class, explicit, double.class); } } else if (actual == double.class) { if (expected == def.class) { - return PainlessCast.boxFrom(Double.class, def.class, explicit, double.class); + return PainlessCast.boxOriginalType(Double.class, def.class, explicit, double.class); } else if (expected == Object.class && internal) { - return PainlessCast.boxFrom(Double.class, Object.class, explicit, double.class); + return PainlessCast.boxOriginalType(Double.class, Object.class, explicit, double.class); } else if (expected == Number.class && internal) { - return PainlessCast.boxFrom(Double.class, Number.class, explicit, double.class); + return PainlessCast.boxOriginalType(Double.class, Number.class, explicit, double.class); } else if (expected == byte.class && explicit) { - return PainlessCast.standard(double.class, byte.class, true); + return PainlessCast.originalTypetoTargetType(double.class, byte.class, true); } else if (expected == char.class && explicit) { - return PainlessCast.standard(double.class, char.class, true); + return PainlessCast.originalTypetoTargetType(double.class, char.class, true); } else if (expected == short.class && explicit) { - return PainlessCast.standard(double.class, short.class, true); + return PainlessCast.originalTypetoTargetType(double.class, short.class, true); } else if (expected == int.class && explicit) { - return PainlessCast.standard(double.class, int.class, true); + return PainlessCast.originalTypetoTargetType(double.class, int.class, true); } else if (expected == long.class && explicit) { - return PainlessCast.standard(double.class, long.class, true); + return PainlessCast.originalTypetoTargetType(double.class, long.class, true); } else if (expected == float.class && explicit) { - return PainlessCast.standard(double.class, float.class, true); + return PainlessCast.originalTypetoTargetType(double.class, float.class, true); } else if (expected == Byte.class && explicit && internal) { - return PainlessCast.boxTo(double.class, byte.class, true, byte.class); + return PainlessCast.boxTargetType(double.class, byte.class, true, byte.class); } else if (expected == Short.class && explicit && internal) { - return PainlessCast.boxTo(double.class, short.class, true, short.class); + return PainlessCast.boxTargetType(double.class, short.class, true, short.class); } else if (expected == Character.class && explicit && internal) { - return PainlessCast.boxTo(double.class, char.class, true, char.class); + return PainlessCast.boxTargetType(double.class, char.class, true, char.class); } else if (expected == Integer.class && explicit && internal) { - return PainlessCast.boxTo(double.class, int.class, true, int.class); + return PainlessCast.boxTargetType(double.class, int.class, true, int.class); } else if (expected == Long.class && explicit && internal) { - return PainlessCast.boxTo(double.class, long.class, true, long.class); + return PainlessCast.boxTargetType(double.class, long.class, true, long.class); } else if (expected == Float.class && explicit && internal) { - return PainlessCast.boxTo(double.class, float.class, true, float.class); + return PainlessCast.boxTargetType(double.class, float.class, true, float.class); } else if (expected == Double.class && internal) { - return PainlessCast.boxTo(double.class, double.class, explicit, double.class); + return PainlessCast.boxTargetType(double.class, double.class, explicit, double.class); } } else if (actual == Boolean.class) { if (expected == boolean.class && internal) { - return PainlessCast.unboxFrom(boolean.class, boolean.class, explicit, boolean.class); + return PainlessCast.unboxOriginalType(boolean.class, boolean.class, explicit, boolean.class); } } else if (actual == Byte.class) { if (expected == byte.class && internal) { - return PainlessCast.unboxFrom(byte.class, byte.class, explicit, byte.class); + return PainlessCast.unboxOriginalType(byte.class, byte.class, explicit, byte.class); } else if (expected == short.class && internal) { - return PainlessCast.unboxFrom(byte.class, short.class, explicit, byte.class); + return PainlessCast.unboxOriginalType(byte.class, short.class, explicit, byte.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxFrom(byte.class, char.class, true, byte.class); + return PainlessCast.unboxOriginalType(byte.class, char.class, true, byte.class); } else if (expected == int.class && internal) { - return PainlessCast.unboxFrom(byte.class, int.class, explicit, byte.class); + return PainlessCast.unboxOriginalType(byte.class, int.class, explicit, byte.class); } else if (expected == long.class && internal) { - return PainlessCast.unboxFrom(byte.class, long.class, explicit, byte.class); + return PainlessCast.unboxOriginalType(byte.class, long.class, explicit, byte.class); } else if (expected == float.class && internal) { - return PainlessCast.unboxFrom(byte.class, float.class, explicit, byte.class); + return PainlessCast.unboxOriginalType(byte.class, float.class, explicit, byte.class); } else if (expected == double.class && internal) { - return PainlessCast.unboxFrom(byte.class, double.class, explicit, byte.class); + return PainlessCast.unboxOriginalType(byte.class, double.class, explicit, byte.class); } } else if (actual == Short.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxFrom(short.class, byte.class, true, short.class); + return PainlessCast.unboxOriginalType(short.class, byte.class, true, short.class); } else if (expected == short.class && internal) { - return PainlessCast.unboxFrom(short.class, short.class, explicit, short.class); + return PainlessCast.unboxOriginalType(short.class, short.class, explicit, short.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxFrom(short.class, char.class, true, short.class); + return PainlessCast.unboxOriginalType(short.class, char.class, true, short.class); } else if (expected == int.class && internal) { - return PainlessCast.unboxFrom(short.class, int.class, explicit, short.class); + return PainlessCast.unboxOriginalType(short.class, int.class, explicit, short.class); } else if (expected == long.class && internal) { - return PainlessCast.unboxFrom(short.class, long.class, explicit, short.class); + return PainlessCast.unboxOriginalType(short.class, long.class, explicit, short.class); } else if (expected == float.class && internal) { - return PainlessCast.unboxFrom(short.class, float.class, explicit, short.class); + return PainlessCast.unboxOriginalType(short.class, float.class, explicit, short.class); } else if (expected == double.class && internal) { - return PainlessCast.unboxFrom(short.class, double.class, explicit, short.class); + return PainlessCast.unboxOriginalType(short.class, double.class, explicit, short.class); } } else if (actual == Character.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxFrom(char.class, byte.class, true, char.class); + return PainlessCast.unboxOriginalType(char.class, byte.class, true, char.class); } else if (expected == short.class && explicit && internal) { - return PainlessCast.unboxFrom(char.class, short.class, true, char.class); + return PainlessCast.unboxOriginalType(char.class, short.class, true, char.class); } else if (expected == char.class && internal) { - return PainlessCast.unboxFrom(char.class, char.class, explicit, char.class); + return PainlessCast.unboxOriginalType(char.class, char.class, explicit, char.class); } else if (expected == int.class && internal) { - return PainlessCast.unboxFrom(char.class, int.class, explicit, char.class); + return PainlessCast.unboxOriginalType(char.class, int.class, explicit, char.class); } else if (expected == long.class && internal) { - return PainlessCast.unboxFrom(char.class, long.class, explicit, char.class); + return PainlessCast.unboxOriginalType(char.class, long.class, explicit, char.class); } else if (expected == float.class && internal) { - return PainlessCast.unboxFrom(char.class, float.class, explicit, char.class); + return PainlessCast.unboxOriginalType(char.class, float.class, explicit, char.class); } else if (expected == double.class && internal) { - return PainlessCast.unboxFrom(char.class, double.class, explicit, char.class); + return PainlessCast.unboxOriginalType(char.class, double.class, explicit, char.class); } } else if (actual == Integer.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxFrom(int.class, byte.class, true, int.class); + return PainlessCast.unboxOriginalType(int.class, byte.class, true, int.class); } else if (expected == short.class && explicit && internal) { - return PainlessCast.unboxFrom(int.class, short.class, true, int.class); + return PainlessCast.unboxOriginalType(int.class, short.class, true, int.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxFrom(int.class, char.class, true, int.class); + return PainlessCast.unboxOriginalType(int.class, char.class, true, int.class); } else if (expected == int.class && internal) { - return PainlessCast.unboxFrom(int.class, int.class, explicit, int.class); + return PainlessCast.unboxOriginalType(int.class, int.class, explicit, int.class); } else if (expected == long.class && internal) { - return PainlessCast.unboxFrom(int.class, long.class, explicit, int.class); + return PainlessCast.unboxOriginalType(int.class, long.class, explicit, int.class); } else if (expected == float.class && internal) { - return PainlessCast.unboxFrom(int.class, float.class, explicit, int.class); + return PainlessCast.unboxOriginalType(int.class, float.class, explicit, int.class); } else if (expected == double.class && internal) { - return PainlessCast.unboxFrom(int.class, double.class, explicit, int.class); + return PainlessCast.unboxOriginalType(int.class, double.class, explicit, int.class); } } else if (actual == Long.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxFrom(long.class, byte.class, true, long.class); + return PainlessCast.unboxOriginalType(long.class, byte.class, true, long.class); } else if (expected == short.class && explicit && internal) { - return PainlessCast.unboxFrom(long.class, short.class, true, long.class); + return PainlessCast.unboxOriginalType(long.class, short.class, true, long.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxFrom(long.class, char.class, true, long.class); + return PainlessCast.unboxOriginalType(long.class, char.class, true, long.class); } else if (expected == int.class && explicit && internal) { - return PainlessCast.unboxFrom(long.class, int.class, true, long.class); + return PainlessCast.unboxOriginalType(long.class, int.class, true, long.class); } else if (expected == long.class && internal) { - return PainlessCast.unboxFrom(long.class, long.class, explicit, long.class); + return PainlessCast.unboxOriginalType(long.class, long.class, explicit, long.class); } else if (expected == float.class && internal) { - return PainlessCast.unboxFrom(long.class, float.class, explicit, long.class); + return PainlessCast.unboxOriginalType(long.class, float.class, explicit, long.class); } else if (expected == double.class && internal) { - return PainlessCast.unboxFrom(long.class, double.class, explicit, long.class); + return PainlessCast.unboxOriginalType(long.class, double.class, explicit, long.class); } } else if (actual == Float.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxFrom(float.class, byte.class, true, float.class); + return PainlessCast.unboxOriginalType(float.class, byte.class, true, float.class); } else if (expected == short.class && explicit && internal) { - return PainlessCast.unboxFrom(float.class, short.class, true, float.class); + return PainlessCast.unboxOriginalType(float.class, short.class, true, float.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxFrom(float.class, char.class, true, float.class); + return PainlessCast.unboxOriginalType(float.class, char.class, true, float.class); } else if (expected == int.class && explicit && internal) { - return PainlessCast.unboxFrom(float.class, int.class, true, float.class); + return PainlessCast.unboxOriginalType(float.class, int.class, true, float.class); } else if (expected == long.class && explicit && internal) { - return PainlessCast.unboxFrom(float.class, long.class, true, float.class); + return PainlessCast.unboxOriginalType(float.class, long.class, true, float.class); } else if (expected == float.class && internal) { - return PainlessCast.unboxFrom(float.class, float.class, explicit, float.class); + return PainlessCast.unboxOriginalType(float.class, float.class, explicit, float.class); } else if (expected == double.class && internal) { - return PainlessCast.unboxFrom(float.class, double.class, explicit, float.class); + return PainlessCast.unboxOriginalType(float.class, double.class, explicit, float.class); } } else if (actual == Double.class) { if (expected == byte.class && explicit && internal) { - return PainlessCast.unboxFrom(double.class, byte.class, true, double.class); + return PainlessCast.unboxOriginalType(double.class, byte.class, true, double.class); } else if (expected == short.class && explicit && internal) { - return PainlessCast.unboxFrom(double.class, short.class, true, double.class); + return PainlessCast.unboxOriginalType(double.class, short.class, true, double.class); } else if (expected == char.class && explicit && internal) { - return PainlessCast.unboxFrom(double.class, char.class, true, double.class); + return PainlessCast.unboxOriginalType(double.class, char.class, true, double.class); } else if (expected == int.class && explicit && internal) { - return PainlessCast.unboxFrom(double.class, int.class, true, double.class); + return PainlessCast.unboxOriginalType(double.class, int.class, true, double.class); } else if (expected == long.class && explicit && internal) { - return PainlessCast.unboxFrom(double.class, long.class, true, double.class); + return PainlessCast.unboxOriginalType(double.class, long.class, true, double.class); } else if (expected == float.class && explicit && internal) { - return PainlessCast.unboxFrom(double.class, float.class, true, double.class); + return PainlessCast.unboxOriginalType(double.class, float.class, true, double.class); } else if (expected == double.class && internal) { - return PainlessCast.unboxFrom(double.class, double.class, explicit, double.class); + return PainlessCast.unboxOriginalType(double.class, double.class, explicit, double.class); } } @@ -463,7 +463,7 @@ public static PainlessCast getLegalCast(Location location, Class actual, Clas (actual != void.class && expected == def.class) || expected.isAssignableFrom(actual) || (actual.isAssignableFrom(expected) && explicit)) { - return PainlessCast.standard(actual, expected, explicit); + return PainlessCast.originalTypetoTargetType(actual, expected, explicit); } else { throw location.createError(new ClassCastException("Cannot cast from " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(actual) + "] to " + @@ -472,8 +472,8 @@ public static PainlessCast getLegalCast(Location location, Class actual, Clas } public static Object constCast(Location location, Object constant, PainlessCast cast) { - Class fsort = cast.from; - Class tsort = cast.to; + Class fsort = cast.originalType; + Class tsort = cast.targetType; if (fsort == tsort) { return constant; @@ -499,11 +499,11 @@ public static Object constCast(Location location, Object constant, PainlessCast else if (tsort == double.class) return number.doubleValue(); else { throw location.createError(new IllegalStateException("Cannot cast from " + - "[" + cast.from.getCanonicalName() + "] to [" + cast.to.getCanonicalName() + "].")); + "[" + cast.originalType.getCanonicalName() + "] to [" + cast.targetType.getCanonicalName() + "].")); } } else { throw location.createError(new IllegalStateException("Cannot cast from " + - "[" + cast.from.getCanonicalName() + "] to [" + cast.to.getCanonicalName() + "].")); + "[" + cast.originalType.getCanonicalName() + "] to [" + cast.targetType.getCanonicalName() + "].")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java index 03345fcfff35a..97dddbdfe52c5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java @@ -20,6 +20,7 @@ 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; @@ -32,6 +33,7 @@ import java.security.CodeSource; import java.security.SecureClassLoader; import java.security.cert.Certificate; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import static org.elasticsearch.painless.WriterConstants.CLASS_NAME; @@ -69,17 +71,14 @@ final class Compiler { /** * A secure class loader used to define Painless scripts. */ - static final class Loader extends SecureClassLoader { + final class Loader extends SecureClassLoader { private final AtomicInteger lambdaCounter = new AtomicInteger(0); - private final PainlessLookup painlessLookup; /** * @param parent The parent ClassLoader. */ - Loader(ClassLoader parent, PainlessLookup painlessLookup) { + Loader(ClassLoader parent) { super(parent); - - this.painlessLookup = painlessLookup; } /** @@ -90,7 +89,16 @@ static final class Loader extends SecureClassLoader { */ @Override public Class findClass(String name) throws ClassNotFoundException { - Class found = painlessLookup.getClassFromBinaryName(name); + if (scriptClass.getName().equals(name)) { + return scriptClass; + } + if (factoryClass != null && factoryClass.getName().equals(name)) { + return factoryClass; + } + if (statefulFactoryClass != null && statefulFactoryClass.getName().equals(name)) { + return statefulFactoryClass; + } + Class found = painlessLookup.canonicalTypeNameToType(name.replace('$', '.')); return found != null ? found : super.findClass(name); } @@ -139,13 +147,23 @@ int newLambdaIdentifier() { * {@link Compiler}'s specified {@link PainlessLookup}. */ public Loader createLoader(ClassLoader parent) { - return new Loader(parent, painlessLookup); + return new Loader(parent); } /** - * The class/interface the script is guaranteed to derive/implement. + * The class/interface the script will implement. + */ + private final Class scriptClass; + + /** + * The class/interface to create the {@code scriptClass} instance. */ - private final Class base; + private final Class factoryClass; + + /** + * An optional class/interface to create the {@code factoryClass} instance. + */ + private final Class statefulFactoryClass; /** * The whitelist the script will use. @@ -154,11 +172,15 @@ public Loader createLoader(ClassLoader parent) { /** * Standard constructor. - * @param base The class/interface the script is guaranteed to derive/implement. + * @param scriptClass The class/interface the script will implement. + * @param factoryClass An optional class/interface to create the {@code scriptClass} instance. + * @param statefulFactoryClass An optional class/interface to create the {@code factoryClass} instance. * @param painlessLookup The whitelist the script will use. */ - Compiler(Class base, PainlessLookup painlessLookup) { - this.base = base; + Compiler(Class scriptClass, Class factoryClass, Class statefulFactoryClass, PainlessLookup painlessLookup) { + this.scriptClass = scriptClass; + this.factoryClass = factoryClass; + this.statefulFactoryClass = statefulFactoryClass; this.painlessLookup = painlessLookup; } @@ -177,10 +199,10 @@ Constructor compile(Loader loader, MainMethodReserved reserved, String name, " plugin if a script longer than this length is a requirement."); } - ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, base); + ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass); SSource root = Walker.buildPainlessTree(scriptClassInfo, reserved, name, source, settings, painlessLookup, null); - root.analyze(painlessLookup); + Map localMethods = root.analyze(painlessLookup); root.write(); try { @@ -189,6 +211,7 @@ Constructor compile(Loader loader, MainMethodReserved reserved, String name, 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); return clazz.getConstructors()[0]; } catch (Exception exception) { // Catch everything to let the user know this is something caused internally. @@ -209,7 +232,7 @@ byte[] compile(String name, String source, CompilerSettings settings, Printer de " plugin if a script longer than this length is a requirement."); } - ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, base); + ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass); SSource root = Walker.buildPainlessTree(scriptClassInfo, new MainMethodReserved(), name, source, settings, painlessLookup, debugStream); root.analyze(painlessLookup); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java index 8a90f53b4fdfa..1e17d6024d4d1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java @@ -19,7 +19,7 @@ package org.elasticsearch.painless; -import org.elasticsearch.painless.lookup.PainlessClass; +import org.elasticsearch.painless.Locals.LocalMethod; import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; @@ -37,6 +37,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; + /** * Support for dynamic type (def). *

      @@ -166,52 +168,6 @@ static MethodHandle arrayLengthGetter(Class arrayType) { } } - /** - * Looks up method entry for a dynamic method call. - *

      - * A dynamic method call for variable {@code x} of type {@code def} looks like: - * {@code x.method(args...)} - *

      - * This method traverses {@code recieverClass}'s class hierarchy (including interfaces) - * until it finds a matching whitelisted method. If one is not found, it throws an exception. - * Otherwise it returns the matching method. - *

      - * @params painlessLookup the whitelist - * @param receiverClass Class of the object to invoke the method on. - * @param name Name of the method. - * @param arity arity of method - * @return matching method to invoke. never returns null. - * @throws IllegalArgumentException if no matching whitelisted method was found. - */ - static PainlessMethod lookupMethodInternal(PainlessLookup painlessLookup, Class receiverClass, String name, int arity) { - String key = PainlessLookupUtility.buildPainlessMethodKey(name, arity); - // check whitelist for matching method - for (Class clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) { - PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(clazz); - - if (struct != null) { - PainlessMethod method = struct.methods.get(key); - if (method != null) { - return method; - } - } - - for (Class iface : clazz.getInterfaces()) { - struct = painlessLookup.getPainlessStructFromJavaClass(iface); - - if (struct != null) { - PainlessMethod method = struct.methods.get(key); - if (method != null) { - return method; - } - } - } - } - - throw new IllegalArgumentException("Unable to find dynamic method [" + name + "] with [" + arity + "] arguments " + - "for class [" + receiverClass.getCanonicalName() + "]."); - } - /** * Looks up handle for a dynamic method call, with lambda replacement *

      @@ -232,13 +188,22 @@ static PainlessMethod lookupMethodInternal(PainlessLookup painlessLookup, Class< * @throws IllegalArgumentException if no matching whitelisted method was found. * @throws Throwable if a method reference cannot be converted to an functional interface */ - static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, MethodType callSiteType, - Class receiverClass, String name, Object args[]) throws Throwable { + static MethodHandle lookupMethod(PainlessLookup painlessLookup, Map localMethods, + MethodHandles.Lookup methodHandlesLookup, MethodType callSiteType, Class receiverClass, String name, Object args[]) + throws Throwable { + String recipeString = (String) args[0]; int numArguments = callSiteType.parameterCount(); // simple case: no lambdas if (recipeString.isEmpty()) { - return lookupMethodInternal(painlessLookup, receiverClass, name, numArguments - 1).handle; + PainlessMethod painlessMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, numArguments - 1); + + if (painlessMethod == null) { + throw new IllegalArgumentException("dynamic method " + + "[" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + (numArguments - 1) + "] not found"); + } + + return painlessMethod.methodHandle; } // convert recipe string to a bitset for convenience (the code below should be refactored...) @@ -261,8 +226,14 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lo // lookup the method with the proper arity, then we know everything (e.g. interface types of parameters). // based on these we can finally link any remaining lambdas that were deferred. - PainlessMethod method = lookupMethodInternal(painlessLookup, receiverClass, name, arity); - MethodHandle handle = method.handle; + PainlessMethod method = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, arity); + + if (method == null) { + throw new IllegalArgumentException( + "dynamic method [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + arity + "] not found"); + } + + MethodHandle handle = method.methodHandle; int replaced = 0; upTo = 1; @@ -276,27 +247,29 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lo String type = signature.substring(1, separator); String call = signature.substring(separator+1, separator2); int numCaptures = Integer.parseInt(signature.substring(separator2+1)); - Class captures[] = new Class[numCaptures]; - for (int capture = 0; capture < captures.length; capture++) { - captures[capture] = callSiteType.parameterType(i + 1 + capture); - } MethodHandle filter; - Class interfaceType = method.arguments.get(i - 1 - replaced); + Class interfaceType = method.typeParameters.get(i - 1 - replaced); if (signature.charAt(0) == 'S') { // the implementation is strongly typed, now that we know the interface type, // we have everything. filter = lookupReferenceInternal(painlessLookup, + localMethods, methodHandlesLookup, interfaceType, type, call, - captures); + numCaptures); } else if (signature.charAt(0) == 'D') { // the interface type is now known, but we need to get the implementation. // this is dynamically based on the receiver type (and cached separately, underneath // this cache). It won't blow up since we never nest here (just references) + Class captures[] = new Class[numCaptures]; + for (int capture = 0; capture < captures.length; capture++) { + captures[capture] = callSiteType.parameterType(i + 1 + capture); + } MethodType nestedType = MethodType.methodType(interfaceType, captures); CallSite nested = DefBootstrap.bootstrap(painlessLookup, + localMethods, methodHandlesLookup, call, nestedType, @@ -324,69 +297,44 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lo * This is just like LambdaMetaFactory, only with a dynamic type. The interface type is known, * so we simply need to lookup the matching implementation method based on receiver type. */ - static MethodHandle lookupReference(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, String interfaceClass, - Class receiverClass, String name) throws Throwable { - Class interfaceType = painlessLookup.getJavaClassFromPainlessType(interfaceClass); - PainlessMethod interfaceMethod = painlessLookup.getPainlessStructFromJavaClass(interfaceType).functionalMethod; - if (interfaceMethod == null) { - throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface"); - } - int arity = interfaceMethod.arguments.size(); - PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity); - return lookupReferenceInternal(painlessLookup, methodHandlesLookup, interfaceType, - PainlessLookupUtility.typeToCanonicalTypeName(implMethod.target), implMethod.name, receiverClass); + static MethodHandle lookupReference(PainlessLookup painlessLookup, Map localMethods, + MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class receiverClass, String name) throws Throwable { + Class interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass); + if (interfaceType == null) { + throw new IllegalArgumentException("type [" + interfaceClass + "] not found"); + } + PainlessMethod interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(interfaceType); + if (interfaceMethod == null) { + throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface"); + } + int arity = interfaceMethod.typeParameters.size(); + PainlessMethod implMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, arity); + if (implMethod == null) { + throw new IllegalArgumentException( + "dynamic method [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + arity + "] not found"); + } + + return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup, + interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass), + implMethod.javaMethod.getName(), 1); } /** Returns a method handle to an implementation of clazz, given method reference signature. */ - private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, - Class clazz, String type, String call, Class... captures) - throws Throwable { - final FunctionRef ref; - if ("this".equals(type)) { - // user written method - PainlessMethod interfaceMethod = painlessLookup.getPainlessStructFromJavaClass(clazz).functionalMethod; - if (interfaceMethod == null) { - throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " + - "to [" + PainlessLookupUtility.typeToCanonicalTypeName(clazz) + "], not a functional interface"); - } - int arity = interfaceMethod.arguments.size() + captures.length; - final MethodHandle handle; - try { - MethodHandle accessor = methodHandlesLookup.findStaticGetter(methodHandlesLookup.lookupClass(), - getUserFunctionHandleFieldName(call, arity), - MethodHandle.class); - handle = (MethodHandle)accessor.invokeExact(); - } catch (NoSuchFieldException | IllegalAccessException e) { - // is it a synthetic method? If we generated the method ourselves, be more helpful. It can only fail - // because the arity does not match the expected interface type. - if (call.contains("$")) { - throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name + - "] in [" + clazz + "]"); - } - throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments."); - } - ref = new FunctionRef(clazz, interfaceMethod, call, handle.type(), captures.length); - } else { - // whitelist lookup - ref = new FunctionRef(painlessLookup, clazz, type, call, captures.length); - } - final CallSite callSite = LambdaBootstrap.lambdaBootstrap( - methodHandlesLookup, - ref.interfaceMethodName, - ref.factoryMethodType, - ref.interfaceMethodType, - ref.delegateClassName, - ref.delegateInvokeType, - ref.delegateMethodName, - ref.delegateMethodType, - ref.isDelegateInterface ? 1 : 0 - ); - return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, captures)); - } - - /** gets the field name used to lookup up the MethodHandle for a function. */ - public static String getUserFunctionHandleFieldName(String name, int arity) { - return "handle$" + name + "$" + arity; + private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map localMethods, + MethodHandles.Lookup methodHandlesLookup, Class clazz, String type, String call, int captures) throws Throwable { + final FunctionRef ref = FunctionRef.create(painlessLookup, localMethods, null, clazz, type, call, captures); + final CallSite callSite = LambdaBootstrap.lambdaBootstrap( + methodHandlesLookup, + ref.interfaceMethodName, + ref.factoryMethodType, + ref.interfaceMethodType, + ref.delegateClassName, + ref.delegateInvokeType, + ref.delegateMethodName, + ref.delegateMethodType, + ref.isDelegateInterface ? 1 : 0 + ); + return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, ref.factoryMethodType.parameterArray())); } /** @@ -417,27 +365,12 @@ public static String getUserFunctionHandleFieldName(String name, int arity) { */ static MethodHandle lookupGetter(PainlessLookup painlessLookup, Class receiverClass, String name) { // first try whitelist - for (Class clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) { - PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(clazz); - - if (struct != null) { - MethodHandle handle = struct.getterMethodHandles.get(name); - if (handle != null) { - return handle; - } - } + MethodHandle getter = painlessLookup.lookupRuntimeGetterMethodHandle(receiverClass, name); - for (final Class iface : clazz.getInterfaces()) { - struct = painlessLookup.getPainlessStructFromJavaClass(iface); - - if (struct != null) { - MethodHandle handle = struct.getterMethodHandles.get(name); - if (handle != null) { - return handle; - } - } - } + if (getter != null) { + return getter; } + // special case: arrays, maps, and lists if (receiverClass.isArray() && "length".equals(name)) { // arrays expose .length as a read-only getter @@ -454,12 +387,12 @@ static MethodHandle lookupGetter(PainlessLookup painlessLookup, Class receive int index = Integer.parseInt(name); return MethodHandles.insertArguments(LIST_GET, 1, index); } catch (NumberFormatException exception) { - throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "]."); + throw new IllegalArgumentException("Illegal list shortcut value [" + name + "]."); } } - throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " + - "for class [" + receiverClass.getCanonicalName() + "]."); + throw new IllegalArgumentException( + "dynamic getter [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "] not found"); } /** @@ -488,27 +421,12 @@ static MethodHandle lookupGetter(PainlessLookup painlessLookup, Class receive */ static MethodHandle lookupSetter(PainlessLookup painlessLookup, Class receiverClass, String name) { // first try whitelist - for (Class clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) { - PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(clazz); - - if (struct != null) { - MethodHandle handle = struct.setterMethodHandles.get(name); - if (handle != null) { - return handle; - } - } - - for (final Class iface : clazz.getInterfaces()) { - struct = painlessLookup.getPainlessStructFromJavaClass(iface); + MethodHandle setter = painlessLookup.lookupRuntimeSetterMethodHandle(receiverClass, name); - if (struct != null) { - MethodHandle handle = struct.setterMethodHandles.get(name); - if (handle != null) { - return handle; - } - } - } + if (setter != null) { + return setter; } + // special case: maps, and lists if (Map.class.isAssignableFrom(receiverClass)) { // maps allow access like mymap.key @@ -522,12 +440,12 @@ static MethodHandle lookupSetter(PainlessLookup painlessLookup, Class receive int index = Integer.parseInt(name); return MethodHandles.insertArguments(LIST_SET, 1, index); } catch (final NumberFormatException exception) { - throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "]."); + throw new IllegalArgumentException("Illegal list shortcut value [" + name + "]."); } } - throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " + - "for class [" + receiverClass.getCanonicalName() + "]."); + throw new IllegalArgumentException( + "dynamic getter [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "] not found"); } /** diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java index 2fadaf30964a6..2488b6f218a7f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless; import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.painless.Locals.LocalMethod; import org.elasticsearch.painless.lookup.PainlessLookup; import java.lang.invoke.CallSite; @@ -28,6 +29,7 @@ import java.lang.invoke.MethodType; import java.lang.invoke.MutableCallSite; import java.lang.invoke.WrongMethodTypeException; +import java.util.Map; /** * Painless invokedynamic bootstrap for the call site. @@ -105,19 +107,21 @@ static final class PIC extends MutableCallSite { static final int MAX_DEPTH = 5; private final PainlessLookup painlessLookup; + private final Map localMethods; private final MethodHandles.Lookup methodHandlesLookup; private final String name; private final int flavor; private final Object[] args; int depth; // pkg-protected for testing - PIC(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, - String name, MethodType type, int initialDepth, int flavor, Object[] args) { + PIC(PainlessLookup painlessLookup, Map localMethods, + MethodHandles.Lookup methodHandlesLookup, String name, MethodType type, int initialDepth, int flavor, Object[] args) { super(type); if (type.parameterType(0) != Object.class) { throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object."); } this.painlessLookup = painlessLookup; + this.localMethods = localMethods; this.methodHandlesLookup = methodHandlesLookup; this.name = name; this.flavor = flavor; @@ -145,7 +149,7 @@ static boolean checkClass(Class clazz, Object receiver) { private MethodHandle lookup(int flavor, String name, Class receiver) throws Throwable { switch(flavor) { case METHOD_CALL: - return Def.lookupMethod(painlessLookup, methodHandlesLookup, type(), receiver, name, args); + return Def.lookupMethod(painlessLookup, localMethods, methodHandlesLookup, type(), receiver, name, args); case LOAD: return Def.lookupGetter(painlessLookup, receiver, name); case STORE: @@ -157,7 +161,7 @@ private MethodHandle lookup(int flavor, String name, Class receiver) throws T case ITERATOR: return Def.lookupIterator(receiver); case REFERENCE: - return Def.lookupReference(painlessLookup, methodHandlesLookup, (String) args[0], receiver, name); + return Def.lookupReference(painlessLookup, localMethods, methodHandlesLookup, (String) args[0], receiver, name); case INDEX_NORMALIZE: return Def.lookupIndexNormalize(receiver); default: throw new AssertionError(); @@ -432,8 +436,9 @@ static boolean checkBoth(Class left, Class right, Object leftObject, Objec *

      * see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic */ - public static CallSite bootstrap(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, String name, - MethodType type, int initialDepth, int flavor, Object... args) { + @SuppressWarnings("unchecked") + public static CallSite bootstrap(PainlessLookup painlessLookup, Map localMethods, + MethodHandles.Lookup methodHandlesLookup, String name, MethodType type, int initialDepth, int flavor, Object... args) { // validate arguments switch(flavor) { // "function-call" like things get a polymorphic cache @@ -452,7 +457,7 @@ public static CallSite bootstrap(PainlessLookup painlessLookup, MethodHandles.Lo if (args.length != numLambdas + 1) { throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references"); } - return new PIC(painlessLookup, methodHandlesLookup, name, type, initialDepth, flavor, args); + return new PIC(painlessLookup, localMethods, methodHandlesLookup, name, type, initialDepth, flavor, args); case LOAD: case STORE: case ARRAY_LOAD: @@ -462,7 +467,7 @@ public static CallSite bootstrap(PainlessLookup painlessLookup, MethodHandles.Lo if (args.length > 0) { throw new BootstrapMethodError("Illegal static bootstrap parameters for flavor: " + flavor); } - return new PIC(painlessLookup, methodHandlesLookup, name, type, initialDepth, flavor, args); + return new PIC(painlessLookup, localMethods, methodHandlesLookup, name, type, initialDepth, flavor, args); case REFERENCE: if (args.length != 1) { throw new BootstrapMethodError("Invalid number of parameters for reference call"); @@ -470,7 +475,7 @@ public static CallSite bootstrap(PainlessLookup painlessLookup, MethodHandles.Lo if (args[0] instanceof String == false) { throw new BootstrapMethodError("Illegal parameter for reference call: " + args[0]); } - return new PIC(painlessLookup, methodHandlesLookup, name, type, initialDepth, flavor, args); + return new PIC(painlessLookup, localMethods, methodHandlesLookup, name, type, initialDepth, flavor, args); // operators get monomorphic cache, with a generic impl for a fallback case UNARY_OPERATOR: diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java index aa72724b93029..2580d7da3e8e7 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java @@ -19,14 +19,18 @@ package org.elasticsearch.painless; -import org.elasticsearch.painless.lookup.PainlessClass; +import org.elasticsearch.painless.Locals.LocalMethod; +import org.elasticsearch.painless.lookup.PainlessConstructor; import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; -import org.objectweb.asm.Type; import java.lang.invoke.MethodType; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import static org.elasticsearch.painless.WriterConstants.CLASS_NAME; import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE; @@ -35,171 +39,205 @@ import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL; /** - * Reference to a function or lambda. - *

      - * Once you have created one of these, you have "everything you need" to call {@link LambdaBootstrap} - * either statically from bytecode with invokedynamic, or at runtime from Java. + * Contains all the values necessary to write the instruction to initiate a + * {@link LambdaBootstrap} for either a function reference or a user-defined + * lambda function. */ public class FunctionRef { - /** functional interface method name */ - public final String interfaceMethodName; - /** factory (CallSite) method signature */ - public final MethodType factoryMethodType; - /** functional interface method signature */ - public final MethodType interfaceMethodType; - /** class of the delegate method to be called */ - public final String delegateClassName; - /** the invocation type of the delegate method */ - public final int delegateInvokeType; - /** the name of the delegate method */ - public final String delegateMethodName; - /** delegate method signature */ - public final MethodType delegateMethodType; + /** + * Creates a new FunctionRef which will resolve {@code type::call} from the whitelist. + * @param painlessLookup the whitelist against which this script is being compiled + * @param localMethods user-defined and synthetic methods generated directly on the script class + * @param location the character number within the script at compile-time + * @param targetClass functional interface type to implement. + * @param typeName the left hand side of a method reference expression + * @param methodName the right hand side of a method reference expression + * @param numberOfCaptures number of captured arguments + */ + public static FunctionRef create(PainlessLookup painlessLookup, Map localMethods, Location location, + Class targetClass, String typeName, String methodName, int numberOfCaptures) { - /** interface method */ - public final PainlessMethod interfaceMethod; - /** delegate method */ - public final PainlessMethod delegateMethod; + Objects.requireNonNull(painlessLookup); + Objects.requireNonNull(targetClass); + Objects.requireNonNull(typeName); + Objects.requireNonNull(methodName); - /** factory method type descriptor */ - public final String factoryDescriptor; - /** functional interface method as type */ - public final Type interfaceType; - /** delegate method type method as type */ - public final Type delegateType; + String targetClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass); + PainlessMethod interfaceMethod; - /** whether a call is made on a delegate interface */ - public final boolean isDelegateInterface; + try { + interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(targetClass); - /** - * Creates a new FunctionRef, which will resolve {@code type::call} from the whitelist. - * @param painlessLookup the whitelist against which this script is being compiled - * @param expected functional interface type to implement. - * @param type the left hand side of a method reference expression - * @param call the right hand side of a method reference expression - * @param numCaptures number of captured arguments - */ - public FunctionRef(PainlessLookup painlessLookup, Class expected, String type, String call, int numCaptures) { - this(expected, painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod, - lookup(painlessLookup, expected, type, call, numCaptures > 0), numCaptures); - } + if (interfaceMethod == null) { + throw new IllegalArgumentException("cannot convert function reference [" + typeName + "::" + methodName + "] " + + "to a non-functional interface [" + targetClassName + "]"); + } - /** - * Creates a new FunctionRef (already resolved) - * @param expected functional interface type to implement - * @param interfaceMethod functional interface method - * @param delegateMethod implementation method - * @param numCaptures number of captured arguments - */ - public FunctionRef(Class expected, PainlessMethod interfaceMethod, PainlessMethod delegateMethod, int numCaptures) { - MethodType delegateMethodType = delegateMethod.getMethodType(); - - interfaceMethodName = interfaceMethod.name; - factoryMethodType = MethodType.methodType(expected, - delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount())); - interfaceMethodType = interfaceMethod.getMethodType().dropParameterTypes(0, 1); - - // the Painless$Script class can be inferred if owner is null - if (delegateMethod.target == null) { - delegateClassName = CLASS_NAME; - isDelegateInterface = false; - } else if (delegateMethod.augmentation != null) { - delegateClassName = delegateMethod.augmentation.getName(); - isDelegateInterface = delegateMethod.augmentation.isInterface(); - } else { - delegateClassName = delegateMethod.target.getName(); - isDelegateInterface = delegateMethod.target.isInterface(); - } + String interfaceMethodName = interfaceMethod.javaMethod.getName(); + MethodType interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1); + String delegateClassName; + boolean isDelegateInterface; + int delegateInvokeType; + String delegateMethodName; + MethodType delegateMethodType; - if ("".equals(delegateMethod.name)) { - delegateInvokeType = H_NEWINVOKESPECIAL; - } else if (Modifier.isStatic(delegateMethod.modifiers)) { - delegateInvokeType = H_INVOKESTATIC; - } else if (delegateMethod.target.isInterface()) { - delegateInvokeType = H_INVOKEINTERFACE; - } else { - delegateInvokeType = H_INVOKEVIRTUAL; - } + Class delegateMethodReturnType; + List> delegateMethodParameters; + int interfaceTypeParametersSize = interfaceMethod.typeParameters.size(); - delegateMethodName = delegateMethod.name; - this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures); + if ("this".equals(typeName)) { + Objects.requireNonNull(localMethods); - this.interfaceMethod = interfaceMethod; - this.delegateMethod = delegateMethod; + if (numberOfCaptures < 0) { + throw new IllegalStateException("internal error"); + } - factoryDescriptor = factoryMethodType.toMethodDescriptorString(); - interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString()); - delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString()); - } + String localMethodKey = Locals.buildLocalMethodKey(methodName, numberOfCaptures + interfaceTypeParametersSize); + LocalMethod localMethod = localMethods.get(localMethodKey); - /** - * Creates a new FunctionRef (low level). - * It is for runtime use only. - */ - public FunctionRef(Class expected, - PainlessMethod interfaceMethod, String delegateMethodName, MethodType delegateMethodType, int numCaptures) { - interfaceMethodName = interfaceMethod.name; - factoryMethodType = MethodType.methodType(expected, - delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount())); - interfaceMethodType = interfaceMethod.getMethodType().dropParameterTypes(0, 1); - - delegateClassName = CLASS_NAME; - delegateInvokeType = H_INVOKESTATIC; - this.delegateMethodName = delegateMethodName; - this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures); - isDelegateInterface = false; + if (localMethod == null) { + throw new IllegalArgumentException("function reference [this::" + localMethodKey + "] " + + "matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " + + "not found" + (localMethodKey.contains("$") ? " due to an incorrect number of arguments" : "") + ); + } - this.interfaceMethod = null; - delegateMethod = null; + delegateClassName = CLASS_NAME; + isDelegateInterface = false; + delegateInvokeType = H_INVOKESTATIC; + delegateMethodName = localMethod.name; + delegateMethodType = localMethod.methodType; + + delegateMethodReturnType = localMethod.returnType; + delegateMethodParameters = localMethod.typeParameters; + } else if ("new".equals(methodName)) { + if (numberOfCaptures != 0) { + throw new IllegalStateException("internal error"); + } - factoryDescriptor = null; - interfaceType = null; - delegateType = null; - } + PainlessConstructor painlessConstructor = painlessLookup.lookupPainlessConstructor(typeName, interfaceTypeParametersSize); - /** - * Looks up {@code type::call} from the whitelist, and returns a matching method. - */ - private static PainlessMethod lookup(PainlessLookup painlessLookup, Class expected, - String type, String call, boolean receiverCaptured) { - // check its really a functional interface - // for e.g. Comparable - PainlessMethod method = painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod; - if (method == null) { - throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " + - "to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"); - } + if (painlessConstructor == null) { + throw new IllegalArgumentException("function reference [" + typeName + "::new/" + interfaceTypeParametersSize + "] " + + "matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " + + "not found"); + } + + delegateClassName = painlessConstructor.javaConstructor.getDeclaringClass().getName(); + isDelegateInterface = false; + delegateInvokeType = H_NEWINVOKESPECIAL; + delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME; + delegateMethodType = painlessConstructor.methodType; - // lookup requested method - PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(painlessLookup.getJavaClassFromPainlessType(type)); - final PainlessMethod impl; - // ctor ref - if ("new".equals(call)) { - impl = struct.constructors.get(PainlessLookupUtility.buildPainlessMethodKey("", method.arguments.size())); - } else { - // look for a static impl first - PainlessMethod staticImpl = - struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.arguments.size())); - if (staticImpl == null) { - // otherwise a virtual impl - final int arity; - if (receiverCaptured) { - // receiver captured - arity = method.arguments.size(); + delegateMethodReturnType = painlessConstructor.javaConstructor.getDeclaringClass(); + delegateMethodParameters = painlessConstructor.typeParameters; + } else { + if (numberOfCaptures != 0 && numberOfCaptures != 1) { + throw new IllegalStateException("internal error"); + } + + boolean captured = numberOfCaptures == 1; + PainlessMethod painlessMethod = + painlessLookup.lookupPainlessMethod(typeName, true, methodName, interfaceTypeParametersSize); + + if (painlessMethod == null) { + painlessMethod = painlessLookup.lookupPainlessMethod(typeName, false, methodName, + captured ? interfaceTypeParametersSize : interfaceTypeParametersSize - 1); + + if (painlessMethod == null) { + throw new IllegalArgumentException( + "function reference " + "[" + typeName + "::" + methodName + "/" + interfaceTypeParametersSize + "] " + + "matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " + + "not found"); + } + } else if (captured) { + throw new IllegalStateException("internal error"); + } + + delegateClassName = painlessMethod.javaMethod.getDeclaringClass().getName(); + isDelegateInterface = painlessMethod.javaMethod.getDeclaringClass().isInterface(); + + if (Modifier.isStatic(painlessMethod.javaMethod.getModifiers())) { + delegateInvokeType = H_INVOKESTATIC; + } else if (isDelegateInterface) { + delegateInvokeType = H_INVOKEINTERFACE; } else { - // receiver passed - arity = method.arguments.size() - 1; + delegateInvokeType = H_INVOKEVIRTUAL; + } + + delegateMethodName = painlessMethod.javaMethod.getName(); + delegateMethodType = painlessMethod.methodType; + + delegateMethodReturnType = painlessMethod.returnType; + + if (delegateMethodType.parameterList().size() > painlessMethod.typeParameters.size()) { + delegateMethodParameters = new ArrayList<>(painlessMethod.typeParameters); + delegateMethodParameters.add(0, delegateMethodType.parameterType(0)); + } else { + delegateMethodParameters = painlessMethod.typeParameters; } - impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity)); - } else { - impl = staticImpl; } + + if (location != null) { + for (int typeParameter = 0; typeParameter < interfaceTypeParametersSize; ++typeParameter) { + Class from = interfaceMethod.typeParameters.get(typeParameter); + Class to = delegateMethodParameters.get(numberOfCaptures + typeParameter); + AnalyzerCaster.getLegalCast(location, from, to, false, true); + } + + if (interfaceMethod.returnType != void.class) { + AnalyzerCaster.getLegalCast(location, delegateMethodReturnType, interfaceMethod.returnType, false, true); + } + } + + MethodType factoryMethodType = MethodType.methodType(targetClass, + delegateMethodType.dropParameterTypes(numberOfCaptures, delegateMethodType.parameterCount())); + delegateMethodType = delegateMethodType.dropParameterTypes(0, numberOfCaptures); + + return new FunctionRef(interfaceMethodName, interfaceMethodType, + delegateClassName, isDelegateInterface, delegateInvokeType, delegateMethodName, delegateMethodType, + factoryMethodType + ); + } catch (IllegalArgumentException iae) { + if (location != null) { + throw location.createError(iae); + } + + throw iae; } - if (impl == null) { - throw new IllegalArgumentException("Unknown reference [" + type + "::" + call + "] matching " + - "[" + expected + "]"); - } - return impl; + } + + /** functional interface method name */ + public final String interfaceMethodName; + /** functional interface method signature */ + public final MethodType interfaceMethodType; + /** class of the delegate method to be called */ + public final String delegateClassName; + /** whether a call is made on a delegate interface */ + public final boolean isDelegateInterface; + /** the invocation type of the delegate method */ + public final int delegateInvokeType; + /** the name of the delegate method */ + public final String delegateMethodName; + /** delegate method signature */ + public final MethodType delegateMethodType; + /** factory (CallSite) method signature */ + public final MethodType factoryMethodType; + + private FunctionRef( + String interfaceMethodName, MethodType interfaceMethodType, + String delegateClassName, boolean isDelegateInterface, + int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType, + MethodType factoryMethodType) { + + this.interfaceMethodName = interfaceMethodName; + this.interfaceMethodType = interfaceMethodType; + this.delegateClassName = delegateClassName; + this.isDelegateInterface = isDelegateInterface; + this.delegateInvokeType = delegateInvokeType; + this.delegateMethodName = delegateMethodName; + this.delegateMethodType = delegateMethodType; + this.factoryMethodType = factoryMethodType; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java index 3fc8554b271e2..d95dc4266889b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/LambdaBootstrap.java @@ -36,7 +36,6 @@ import java.security.PrivilegedAction; import static java.lang.invoke.MethodHandles.Lookup; -import static org.elasticsearch.painless.Compiler.Loader; import static org.elasticsearch.painless.WriterConstants.CLASS_VERSION; import static org.elasticsearch.painless.WriterConstants.CTOR_METHOD_NAME; import static org.elasticsearch.painless.WriterConstants.DELEGATE_BOOTSTRAP_HANDLE; @@ -207,7 +206,7 @@ public static CallSite lambdaBootstrap( MethodType delegateMethodType, int isDelegateInterface) throws LambdaConversionException { - Loader loader = (Loader)lookup.lookupClass().getClassLoader(); + Compiler.Loader loader = (Compiler.Loader)lookup.lookupClass().getClassLoader(); String lambdaClassName = Type.getInternalName(lookup.lookupClass()) + "$$Lambda" + loader.newLambdaIdentifier(); Type lambdaClassType = Type.getObjectType(lambdaClassName); Type delegateClassType = Type.getObjectType(delegateClassName.replace('.', '/')); @@ -457,11 +456,11 @@ private static void endLambdaClass(ClassWriter cw) { } /** - * Defines the {@link Class} for the lambda class using the same {@link Loader} + * Defines the {@link Class} for the lambda class using the same {@link Compiler.Loader} * that originally defined the class for the Painless script. */ private static Class createLambdaClass( - Loader loader, + Compiler.Loader loader, ClassWriter cw, Type lambdaClassType) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java index 804f6aa2b689c..e07c016ddd0e3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java @@ -22,8 +22,8 @@ import org.elasticsearch.painless.ScriptClassInfo.MethodArgument; import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupUtility; -import org.elasticsearch.painless.lookup.PainlessMethod; +import java.lang.invoke.MethodType; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -32,12 +32,39 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; + +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToJavaType; /** * Tracks user defined methods and variables across compilation phases. */ public final class Locals { + /** + * Constructs a local method key used to lookup local methods from a painless class. + */ + public static String buildLocalMethodKey(String methodName, int methodArity) { + return methodName + "/" + methodArity; + } + + /** + * Stores information about methods directly callable on the generated script class. + */ + public static class LocalMethod { + public final String name; + public final Class returnType; + public final List> typeParameters; + public final MethodType methodType; + + public LocalMethod(String name, Class returnType, List> typeParameters, MethodType methodType) { + this.name = name; + this.returnType = returnType; + this.typeParameters = typeParameters; + this.methodType = methodType; + } + } + /** Reserved word: loop counter */ public static final String LOOP = "#loop"; /** Reserved word: unused */ @@ -50,7 +77,10 @@ public final class Locals { /** Creates a new local variable scope (e.g. loop) inside the current scope */ public static Locals newLocalScope(Locals currentScope) { - return new Locals(currentScope); + Locals locals = new Locals(currentScope); + locals.methods = currentScope.methods; + + return locals; } /** @@ -58,9 +88,13 @@ public static Locals newLocalScope(Locals currentScope) { *

      * This is just like {@link #newFunctionScope}, except the captured parameters are made read-only. */ - public static Locals newLambdaScope(Locals programScope, Class returnType, List parameters, + public static Locals newLambdaScope(Locals programScope, String name, Class returnType, List parameters, int captureCount, int maxLoopCounter) { Locals locals = new Locals(programScope, programScope.painlessLookup, returnType, KEYWORDS); + locals.methods = programScope.methods; + List> typeParameters = parameters.stream().map(parameter -> typeToJavaType(parameter.clazz)).collect(Collectors.toList()); + locals.methods.put(buildLocalMethodKey(name, parameters.size()), new LocalMethod(name, returnType, typeParameters, + MethodType.methodType(typeToJavaType(returnType), typeParameters))); for (int i = 0; i < parameters.size(); i++) { Parameter parameter = parameters.get(i); // TODO: allow non-captures to be r/w: @@ -80,6 +114,7 @@ public static Locals newLambdaScope(Locals programScope, Class returnType, Li /** Creates a new function scope inside the current scope */ public static Locals newFunctionScope(Locals programScope, Class returnType, List parameters, int maxLoopCounter) { Locals locals = new Locals(programScope, programScope.painlessLookup, returnType, KEYWORDS); + locals.methods = programScope.methods; for (Parameter parameter : parameters) { locals.addVariable(parameter.location, parameter.clazz, parameter.name, false); } @@ -94,6 +129,7 @@ public static Locals newFunctionScope(Locals programScope, Class returnType, public static Locals newMainMethodScope(ScriptClassInfo scriptClassInfo, Locals programScope, int maxLoopCounter) { Locals locals = new Locals( programScope, programScope.painlessLookup, scriptClassInfo.getExecuteMethodReturnType(), KEYWORDS); + locals.methods = programScope.methods; // This reference. Internal use only. locals.defineVariable(null, Object.class, THIS, true); @@ -110,9 +146,10 @@ public static Locals newMainMethodScope(ScriptClassInfo scriptClassInfo, Locals } /** Creates a new program scope: the list of methods. It is the parent for all methods */ - public static Locals newProgramScope(PainlessLookup painlessLookup, Collection methods) { + public static Locals newProgramScope(PainlessLookup painlessLookup, Collection methods) { Locals locals = new Locals(null, painlessLookup, null, null); - for (PainlessMethod method : methods) { + locals.methods = new HashMap<>(); + for (LocalMethod method : methods) { locals.addMethod(method); } return locals; @@ -143,15 +180,8 @@ public Variable getVariable(Location location, String name) { } /** Looks up a method. Returns null if the method does not exist. */ - public PainlessMethod getMethod(String key) { - PainlessMethod method = lookupMethod(key); - if (method != null) { - return method; - } - if (parent != null) { - return parent.getMethod(key); - } - return null; + public LocalMethod getMethod(String methodName, int methodArity) { + return methods.get(buildLocalMethodKey(methodName, methodArity)); } /** Creates a new variable. Throws IAE if the variable has already been defined (even in a parent) or reserved. */ @@ -199,7 +229,7 @@ public PainlessLookup getPainlessLookup() { // variable name -> variable private Map variables; // method name+arity -> methods - private Map methods; + private Map methods; /** * Create a new Locals @@ -236,15 +266,10 @@ private Variable lookupVariable(Location location, String name) { return variables.get(name); } - /** Looks up a method at this scope only. Returns null if the method does not exist. */ - private PainlessMethod lookupMethod(String key) { - if (methods == null) { - return null; - } - return methods.get(key); + public Map getMethods() { + return Collections.unmodifiableMap(methods); } - /** Defines a variable at this scope internally. */ private Variable defineVariable(Location location, Class type, String name, boolean readonly) { if (variables == null) { @@ -256,15 +281,10 @@ private Variable defineVariable(Location location, Class type, String name, b return variable; } - private void addMethod(PainlessMethod method) { - if (methods == null) { - methods = new HashMap<>(); - } - methods.put(PainlessLookupUtility.buildPainlessMethodKey(method.name, method.arguments.size()), method); - // TODO: check result + private void addMethod(LocalMethod method) { + methods.put(buildLocalMethodKey(method.name, method.typeParameters.size()), method); } - private int getNextSlot() { return nextSlotNumber; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java index c339e7bfb2613..dca638b3dddac 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless; import org.elasticsearch.painless.lookup.PainlessCast; +import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.lookup.def; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; @@ -28,6 +29,7 @@ import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; +import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -54,6 +56,7 @@ import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_IMPLICIT; import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE; import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE; +import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE; import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS; import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE; import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN; @@ -132,52 +135,52 @@ public void writeLoopCounter(int slot, int count, Location location) { public void writeCast(PainlessCast cast) { if (cast != null) { - if (cast.from == char.class && cast.to == String.class) { + if (cast.originalType == char.class && cast.targetType == String.class) { invokeStatic(UTILITY_TYPE, CHAR_TO_STRING); - } else if (cast.from == String.class && cast.to == char.class) { + } else if (cast.originalType == String.class && cast.targetType == char.class) { invokeStatic(UTILITY_TYPE, STRING_TO_CHAR); - } else if (cast.unboxFrom != null) { - unbox(getType(cast.unboxFrom)); - writeCast(cast.from, cast.to); - } else if (cast.unboxTo != null) { - if (cast.from == def.class) { - if (cast.explicit) { - if (cast.to == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN); - else if (cast.to == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT); - else if (cast.to == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT); - else if (cast.to == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT); - else if (cast.to == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT); - else if (cast.to == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT); - else if (cast.to == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT); - else if (cast.to == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT); + } else if (cast.unboxOriginalType != null) { + unbox(getType(cast.unboxOriginalType)); + writeCast(cast.originalType, cast.targetType); + } else if (cast.unboxTargetType != null) { + if (cast.originalType == def.class) { + if (cast.explicitCast) { + if (cast.targetType == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN); + else if (cast.targetType == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT); + else if (cast.targetType == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT); + else if (cast.targetType == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT); + else if (cast.targetType == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT); + else if (cast.targetType == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT); + else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT); + else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT); else { throw new IllegalStateException("Illegal tree structure."); } } else { - if (cast.to == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN); - else if (cast.to == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT); - else if (cast.to == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT); - else if (cast.to == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT); - else if (cast.to == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT); - else if (cast.to == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT); - else if (cast.to == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT); - else if (cast.to == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT); + if (cast.targetType == Boolean.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN); + else if (cast.targetType == Byte.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT); + else if (cast.targetType == Short.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT); + else if (cast.targetType == Character.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT); + else if (cast.targetType == Integer.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT); + else if (cast.targetType == Long.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT); + else if (cast.targetType == Float.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT); + else if (cast.targetType == Double.class) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT); else { throw new IllegalStateException("Illegal tree structure."); } } } else { - writeCast(cast.from, cast.to); - unbox(getType(cast.unboxTo)); + writeCast(cast.originalType, cast.targetType); + unbox(getType(cast.unboxTargetType)); } - } else if (cast.boxFrom != null) { - box(getType(cast.boxFrom)); - writeCast(cast.from, cast.to); - } else if (cast.boxTo != null) { - writeCast(cast.from, cast.to); - box(getType(cast.boxTo)); + } else if (cast.boxOriginalType != null) { + box(getType(cast.boxOriginalType)); + writeCast(cast.originalType, cast.targetType); + } else if (cast.boxTargetType != null) { + writeCast(cast.originalType, cast.targetType); + box(getType(cast.boxTargetType)); } else { - writeCast(cast.from, cast.to); + writeCast(cast.originalType, cast.targetType); } } } @@ -415,4 +418,40 @@ public void invokeDefCall(String name, Type methodType, int flavor, Object... pa System.arraycopy(params, 0, args, 2, params.length); invokeDynamic(name, methodType.getDescriptor(), DEF_BOOTSTRAP_HANDLE, args); } + + public void invokeMethodCall(PainlessMethod painlessMethod) { + Type type = Type.getType(painlessMethod.javaMethod.getDeclaringClass()); + Method method = Method.getMethod(painlessMethod.javaMethod); + + if (Modifier.isStatic(painlessMethod.javaMethod.getModifiers())) { + // invokeStatic assumes that the owner class is not an interface, so this is a + // special case for interfaces where the interface method boolean needs to be set to + // true to reference the appropriate class constant when calling a static interface + // method since java 8 did not check, but java 9 and 10 do + if (painlessMethod.javaMethod.getDeclaringClass().isInterface()) { + visitMethodInsn(Opcodes.INVOKESTATIC, type.getInternalName(), + painlessMethod.javaMethod.getName(), painlessMethod.methodType.toMethodDescriptorString(), true); + } else { + invokeStatic(type, method); + } + } else if (painlessMethod.javaMethod.getDeclaringClass().isInterface()) { + invokeInterface(type, method); + } else { + invokeVirtual(type, method); + } + } + + public void invokeLambdaCall(FunctionRef functionRef) { + invokeDynamic( + functionRef.interfaceMethodName, + functionRef.factoryMethodType.toMethodDescriptorString(), + LAMBDA_BOOTSTRAP_HANDLE, + Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString()), + functionRef.delegateClassName, + functionRef.delegateInvokeType, + functionRef.delegateMethodName, + Type.getMethodType(functionRef.delegateMethodType.toMethodDescriptorString()), + functionRef.isDelegateInterface ? 1 : 0 + ); + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java index 7bef028c7d1ca..e4988103bc681 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessExplainError.java @@ -57,7 +57,7 @@ public Map> getHeaders(PainlessLookup painlessLookup) { if (objectToExplain != null) { toString = objectToExplain.toString(); javaClassName = objectToExplain.getClass().getName(); - PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(objectToExplain.getClass()); + PainlessClass struct = painlessLookup.lookupPainlessClass(objectToExplain.getClass()); if (struct != null) { painlessClassName = PainlessLookupUtility.typeToCanonicalTypeName(objectToExplain.getClass()); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java index a5a9823d13018..3a2a6d1452df1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java @@ -102,10 +102,10 @@ public PainlessScriptEngine(Settings settings, Map, List, List> entry : contexts.entrySet()) { ScriptContext context = entry.getKey(); if (context.instanceClazz.equals(SearchScript.class) || context.instanceClazz.equals(ExecutableScript.class)) { - contextsToCompilers.put(context, new Compiler(GenericElasticsearchScript.class, + contextsToCompilers.put(context, new Compiler(GenericElasticsearchScript.class, null, null, PainlessLookupBuilder.buildFromWhitelists(entry.getValue()))); } else { - contextsToCompilers.put(context, new Compiler(context.instanceClazz, + contextsToCompilers.put(context, new Compiler(context.instanceClazz, context.factoryClazz, context.statefulFactoryClazz, PainlessLookupBuilder.buildFromWhitelists(entry.getValue()))); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java index 6d4b455269616..345db46f8875f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java @@ -190,7 +190,7 @@ private static Class definitionTypeForClass(PainlessLookup painlessLookup, Cl componentType = componentType.getComponentType(); } - if (painlessLookup.getPainlessStructFromJavaClass(componentType) == null) { + if (painlessLookup.lookupPainlessClass(componentType) == null) { throw new IllegalArgumentException(unknownErrorMessageSource.apply(componentType)); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java index db3aeff0483f6..9c3d991080d26 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java @@ -120,7 +120,7 @@ public final class WriterConstants { DEF_BOOTSTRAP_METHOD.getDescriptor(), false); public static final Type DEF_BOOTSTRAP_DELEGATE_TYPE = Type.getType(DefBootstrap.class); public static final Method DEF_BOOTSTRAP_DELEGATE_METHOD = getAsmMethod(CallSite.class, "bootstrap", PainlessLookup.class, - MethodHandles.Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class); + Map.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class); public static final Type DEF_UTIL_TYPE = Type.getType(Def.class); public static final Method DEF_TO_BOOLEAN = getAsmMethod(boolean.class, "DefToboolean" , Object.class); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java index f1db35636b41c..9279093cf31ae 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/EnhancedPainlessLexer.java @@ -75,7 +75,7 @@ public void recover(final LexerNoViableAltException lnvae) { @Override protected boolean isType(String name) { - return painlessLookup.isSimplePainlessType(name); + return painlessLookup.isValidCanonicalClassName(name); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessCast.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessCast.java index 2440fb45d4dfb..f87f8a134b8c4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessCast.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessCast.java @@ -22,46 +22,55 @@ public class PainlessCast { /** Create a standard cast with no boxing/unboxing. */ - public static PainlessCast standard(Class from, Class to, boolean explicit) { - return new PainlessCast(from, to, explicit, null, null, null, null); + public static PainlessCast originalTypetoTargetType(Class originalType, Class targetType, boolean explicitCast) { + return new PainlessCast(originalType, targetType, explicitCast, null, null, null, null); } - /** Create a cast where the from type will be unboxed, and then the cast will be performed. */ - public static PainlessCast unboxFrom(Class from, Class to, boolean explicit, Class unboxFrom) { - return new PainlessCast(from, to, explicit, unboxFrom, null, null, null); + /** Create a cast where the original type will be unboxed, and then the cast will be performed. */ + public static PainlessCast unboxOriginalType( + Class originalType, Class targetType, boolean explicitCast, Class unboxOriginalType) { + + return new PainlessCast(originalType, targetType, explicitCast, unboxOriginalType, null, null, null); } - /** Create a cast where the to type will be unboxed, and then the cast will be performed. */ - public static PainlessCast unboxTo(Class from, Class to, boolean explicit, Class unboxTo) { - return new PainlessCast(from, to, explicit, null, unboxTo, null, null); + /** Create a cast where the target type will be unboxed, and then the cast will be performed. */ + public static PainlessCast unboxTargetType( + Class originalType, Class targetType, boolean explicitCast, Class unboxTargetType) { + + return new PainlessCast(originalType, targetType, explicitCast, null, unboxTargetType, null, null); } - /** Create a cast where the from type will be boxed, and then the cast will be performed. */ - public static PainlessCast boxFrom(Class from, Class to, boolean explicit, Class boxFrom) { - return new PainlessCast(from, to, explicit, null, null, boxFrom, null); + /** Create a cast where the original type will be boxed, and then the cast will be performed. */ + public static PainlessCast boxOriginalType( + Class originalType, Class targetType, boolean explicitCast, Class boxOriginalType) { + + return new PainlessCast(originalType, targetType, explicitCast, null, null, boxOriginalType, null); } - /** Create a cast where the to type will be boxed, and then the cast will be performed. */ - public static PainlessCast boxTo(Class from, Class to, boolean explicit, Class boxTo) { - return new PainlessCast(from, to, explicit, null, null, null, boxTo); + /** Create a cast where the target type will be boxed, and then the cast will be performed. */ + public static PainlessCast boxTargetType( + Class originalType, Class targetType, boolean explicitCast, Class boxTargetType) { + + return new PainlessCast(originalType, targetType, explicitCast, null, null, null, boxTargetType); } - public final Class from; - public final Class to; - public final boolean explicit; - public final Class unboxFrom; - public final Class unboxTo; - public final Class boxFrom; - public final Class boxTo; + public final Class originalType; + public final Class targetType; + public final boolean explicitCast; + public final Class unboxOriginalType; + public final Class unboxTargetType; + public final Class boxOriginalType; + public final Class boxTargetType; + + private PainlessCast(Class originalType, Class targetType, boolean explicitCast, + Class unboxOriginalType, Class unboxTargetType, Class boxOriginalType, Class boxTargetType) { - private PainlessCast(Class from, Class to, boolean explicit, - Class unboxFrom, Class unboxTo, Class boxFrom, Class boxTo) { - this.from = from; - this.to = to; - this.explicit = explicit; - this.unboxFrom = unboxFrom; - this.unboxTo = unboxTo; - this.boxFrom = boxFrom; - this.boxTo = boxTo; + this.originalType = originalType; + this.targetType = targetType; + this.explicitCast = explicitCast; + this.unboxOriginalType = unboxOriginalType; + this.unboxTargetType = unboxTargetType; + this.boxOriginalType = boxOriginalType; + this.boxTargetType = boxTargetType; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java index 24dcf0ebdba87..50bb79dcfbdf5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java @@ -24,7 +24,8 @@ import java.util.Map; public final class PainlessClass { - public final Map constructors; + public final Map constructors; + public final Map staticMethods; public final Map methods; @@ -34,15 +35,16 @@ public final class PainlessClass { public final Map getterMethodHandles; public final Map setterMethodHandles; - public final PainlessMethod functionalMethod; + public final PainlessMethod functionalInterfaceMethod; - PainlessClass(Map constructors, + PainlessClass(Map constructors, Map staticMethods, Map methods, Map staticFields, Map fields, Map getterMethodHandles, Map setterMethodHandles, - PainlessMethod functionalMethod) { + PainlessMethod functionalInterfaceMethod) { this.constructors = Collections.unmodifiableMap(constructors); + this.staticMethods = Collections.unmodifiableMap(staticMethods); this.methods = Collections.unmodifiableMap(methods); @@ -52,6 +54,6 @@ public final class PainlessClass { this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles); this.setterMethodHandles = Collections.unmodifiableMap(setterMethodHandles); - this.functionalMethod = functionalMethod; + this.functionalInterfaceMethod = functionalInterfaceMethod; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java index 2f41ed5dca8f1..a61215e9ed749 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java @@ -24,7 +24,8 @@ import java.util.Map; final class PainlessClassBuilder { - final Map constructors; + final Map constructors; + final Map staticMethods; final Map methods; @@ -34,10 +35,11 @@ final class PainlessClassBuilder { final Map getterMethodHandles; final Map setterMethodHandles; - PainlessMethod functionalMethod; + PainlessMethod functionalInterfaceMethod; PainlessClassBuilder() { constructors = new HashMap<>(); + staticMethods = new HashMap<>(); methods = new HashMap<>(); @@ -47,11 +49,11 @@ final class PainlessClassBuilder { getterMethodHandles = new HashMap<>(); setterMethodHandles = new HashMap<>(); - functionalMethod = null; + functionalInterfaceMethod = null; } PainlessClass build() { return new PainlessClass(constructors, staticMethods, methods, staticFields, fields, - getterMethodHandles, setterMethodHandles, functionalMethod); + getterMethodHandles, setterMethodHandles, functionalInterfaceMethod); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessConstructor.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessConstructor.java new file mode 100644 index 0000000000000..76597c1a29d65 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessConstructor.java @@ -0,0 +1,39 @@ +/* + * 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.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.util.List; + +public class PainlessConstructor { + public final Constructor javaConstructor; + public final List> typeParameters; + public final MethodHandle methodHandle; + public final MethodType methodType; + + PainlessConstructor(Constructor javaConstructor, List> typeParameters, MethodHandle methodHandle, MethodType methodType) { + this.javaConstructor = javaConstructor; + this.typeParameters = typeParameters; + this.methodHandle = methodHandle; + this.methodType = methodType; + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessField.java index f316e1438ecb9..a55d6c3730ebd 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessField.java @@ -20,24 +20,20 @@ package org.elasticsearch.painless.lookup; import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; public final class PainlessField { - public final String name; - public final Class target; - public final Class clazz; - public final String javaName; - public final int modifiers; - public final MethodHandle getter; - public final MethodHandle setter; + public final Field javaField; + public final Class typeParameter; - PainlessField(String name, String javaName, Class target, Class clazz, int modifiers, - MethodHandle getter, MethodHandle setter) { - this.name = name; - this.javaName = javaName; - this.target = target; - this.clazz = clazz; - this.modifiers = modifiers; - this.getter = getter; - this.setter = setter; + public final MethodHandle getterMethodHandle; + public final MethodHandle setterMethodHandle; + + PainlessField(Field javaField, Class typeParameter, MethodHandle getterMethodHandle, MethodHandle setterMethodHandle) { + this.javaField = javaField; + this.typeParameter = typeParameter; + + this.getterMethodHandle = getterMethodHandle; + this.setterMethodHandle = setterMethodHandle; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java index 67c04498a5894..16b8ac14f14f2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java @@ -19,41 +19,221 @@ package org.elasticsearch.painless.lookup; -import java.util.Collection; +import java.lang.invoke.MethodHandle; import java.util.Collections; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; -/** - * The entire API for Painless. Also used as a whitelist for checking for legal - * methods and fields during at both compile-time and runtime. - */ -public final class PainlessLookup { +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessConstructorKey; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessFieldKey; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessMethodKey; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToBoxedType; - public Collection> getStructs() { - return classesToPainlessClasses.keySet(); - } +public final class PainlessLookup { private final Map> canonicalClassNamesToClasses; private final Map, PainlessClass> classesToPainlessClasses; PainlessLookup(Map> canonicalClassNamesToClasses, Map, PainlessClass> classesToPainlessClasses) { + Objects.requireNonNull(canonicalClassNamesToClasses); + Objects.requireNonNull(classesToPainlessClasses); + this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses); this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses); } - public Class getClassFromBinaryName(String painlessType) { - return canonicalClassNamesToClasses.get(painlessType.replace('$', '.')); + public boolean isValidCanonicalClassName(String canonicalClassName) { + Objects.requireNonNull(canonicalClassName); + + return canonicalClassNamesToClasses.containsKey(canonicalClassName); + } + + public Class canonicalTypeNameToType(String canonicalTypeName) { + Objects.requireNonNull(canonicalTypeName); + + return PainlessLookupUtility.canonicalTypeNameToType(canonicalTypeName, canonicalClassNamesToClasses); + } + + public Set> getClasses() { + return classesToPainlessClasses.keySet(); + } + + public PainlessClass lookupPainlessClass(Class targetClass) { + return classesToPainlessClasses.get(targetClass); + } + + public PainlessConstructor lookupPainlessConstructor(String targetCanonicalClassName, int constructorArity) { + Objects.requireNonNull(targetCanonicalClassName); + + Class targetClass = canonicalTypeNameToType(targetCanonicalClassName); + + if (targetClass == null) { + return null; + } + + return lookupPainlessConstructor(targetClass, constructorArity); + } + + public PainlessConstructor lookupPainlessConstructor(Class targetClass, int constructorArity) { + Objects.requireNonNull(targetClass); + + PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass); + String painlessConstructorKey = buildPainlessConstructorKey(constructorArity); + + if (targetPainlessClass == null) { + return null; + } + + PainlessConstructor painlessConstructor = targetPainlessClass.constructors.get(painlessConstructorKey); + + if (painlessConstructor == null) { + return null; + } + + return painlessConstructor; + } + + public PainlessMethod lookupPainlessMethod(String targetCanonicalClassName, boolean isStatic, String methodName, int methodArity) { + Objects.requireNonNull(targetCanonicalClassName); + + Class targetClass = canonicalTypeNameToType(targetCanonicalClassName); + + if (targetClass == null) { + return null; + } + + return lookupPainlessMethod(targetClass, isStatic, methodName, methodArity); + } + + public PainlessMethod lookupPainlessMethod(Class targetClass, boolean isStatic, String methodName, int methodArity) { + Objects.requireNonNull(targetClass); + Objects.requireNonNull(methodName); + + if (targetClass.isPrimitive()) { + targetClass = typeToBoxedType(targetClass); + } + + PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass); + String painlessMethodKey = buildPainlessMethodKey(methodName, methodArity); + + if (targetPainlessClass == null) { + return null; + } + + return isStatic ? + targetPainlessClass.staticMethods.get(painlessMethodKey) : + targetPainlessClass.methods.get(painlessMethodKey); + } + + public PainlessField lookupPainlessField(String targetCanonicalClassName, boolean isStatic, String fieldName) { + Objects.requireNonNull(targetCanonicalClassName); + + Class targetClass = canonicalTypeNameToType(targetCanonicalClassName); + + if (targetClass == null) { + return null; + } + + return lookupPainlessField(targetClass, isStatic, fieldName); + } + + public PainlessField lookupPainlessField(Class targetClass, boolean isStatic, String fieldName) { + Objects.requireNonNull(targetClass); + Objects.requireNonNull(fieldName); + + PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass); + String painlessFieldKey = buildPainlessFieldKey(fieldName); + + if (targetPainlessClass == null) { + return null; + } + + PainlessField painlessField = isStatic ? + targetPainlessClass.staticFields.get(painlessFieldKey) : + targetPainlessClass.fields.get(painlessFieldKey); + + if (painlessField == null) { + return null; + } + + return painlessField; + } + + public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class targetClass) { + PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass); + + if (targetPainlessClass == null) { + return null; + } + + return targetPainlessClass.functionalInterfaceMethod; + } + + public PainlessMethod lookupRuntimePainlessMethod(Class originalTargetClass, String methodName, int methodArity) { + Objects.requireNonNull(originalTargetClass); + Objects.requireNonNull(methodName); + + String painlessMethodKey = buildPainlessMethodKey(methodName, methodArity); + Function objectLookup = targetPainlessClass -> targetPainlessClass.methods.get(painlessMethodKey); + + return lookupRuntimePainlessObject(originalTargetClass, objectLookup); } - public boolean isSimplePainlessType(String painlessType) { - return canonicalClassNamesToClasses.containsKey(painlessType); + public MethodHandle lookupRuntimeGetterMethodHandle(Class originalTargetClass, String getterName) { + Objects.requireNonNull(originalTargetClass); + Objects.requireNonNull(getterName); + + Function objectLookup = targetPainlessClass -> targetPainlessClass.getterMethodHandles.get(getterName); + + return lookupRuntimePainlessObject(originalTargetClass, objectLookup); } - public PainlessClass getPainlessStructFromJavaClass(Class clazz) { - return classesToPainlessClasses.get(clazz); + public MethodHandle lookupRuntimeSetterMethodHandle(Class originalTargetClass, String setterName) { + Objects.requireNonNull(originalTargetClass); + Objects.requireNonNull(setterName); + + Function objectLookup = targetPainlessClass -> targetPainlessClass.setterMethodHandles.get(setterName); + + return lookupRuntimePainlessObject(originalTargetClass, objectLookup); } - public Class getJavaClassFromPainlessType(String painlessType) { - return PainlessLookupUtility.canonicalTypeNameToType(painlessType, canonicalClassNamesToClasses); + private T lookupRuntimePainlessObject(Class originalTargetClass, Function objectLookup) { + Class currentTargetClass = originalTargetClass; + + while (currentTargetClass != null) { + PainlessClass targetPainlessClass = classesToPainlessClasses.get(currentTargetClass); + + if (targetPainlessClass != null) { + T painlessObject = objectLookup.apply(targetPainlessClass); + + if (painlessObject != null) { + return painlessObject; + } + } + + currentTargetClass = currentTargetClass.getSuperclass(); + } + + currentTargetClass = originalTargetClass; + + while (currentTargetClass != null) { + for (Class targetInterface : currentTargetClass.getInterfaces()) { + PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetInterface); + + if (targetPainlessClass != null) { + T painlessObject = objectLookup.apply(targetPainlessClass); + + if (painlessObject != null) { + return painlessObject; + } + } + } + + currentTargetClass = currentTargetClass.getSuperclass(); + } + + return null; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java index 3675cc7cd0f20..0346867d35aa3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java @@ -27,6 +27,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -39,15 +40,47 @@ import java.util.Objects; import java.util.regex.Pattern; -import static org.elasticsearch.painless.lookup.PainlessLookupUtility.CONSTRUCTOR_NAME; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.DEF_CLASS_NAME; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessConstructorKey; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessFieldKey; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessMethodKey; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToJavaType; import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCanonicalTypeNames; -public class PainlessLookupBuilder { +public final class PainlessLookupBuilder { + + private static class PainlessConstructorCacheKey { + + private final Class targetType; + private final List> typeParameters; + + private PainlessConstructorCacheKey(Class targetType, List> typeParameters) { + this.targetType = targetType; + this.typeParameters = Collections.unmodifiableList(typeParameters); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + PainlessConstructorCacheKey that = (PainlessConstructorCacheKey)object; + + return Objects.equals(targetType, that.targetType) && + Objects.equals(typeParameters, that.typeParameters); + } + + @Override + public int hashCode() { + return Objects.hash(targetType, typeParameters); + } + } private static class PainlessMethodCacheKey { @@ -119,8 +152,9 @@ public int hashCode() { } } - private static final Map painlessMethodCache = new HashMap<>(); - private static final Map painlessFieldCache = new HashMap<>(); + private static final Map painlessConstuctorCache = new HashMap<>(); + private static final Map painlessMethodCache = new HashMap<>(); + private static final Map painlessFieldCache = 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]*$"); @@ -132,35 +166,35 @@ public static PainlessLookup buildFromWhitelists(List whitelists) { try { for (Whitelist whitelist : whitelists) { - for (WhitelistClass whitelistClass : whitelist.whitelistStructs) { + for (WhitelistClass whitelistClass : whitelist.whitelistClasses) { origin = whitelistClass.origin; painlessLookupBuilder.addPainlessClass( - whitelist.javaClassLoader, whitelistClass.javaClassName, whitelistClass.onlyFQNJavaClassName == false); + whitelist.classLoader, whitelistClass.javaClassName, whitelistClass.noImport == false); } } for (Whitelist whitelist : whitelists) { - for (WhitelistClass whitelistClass : whitelist.whitelistStructs) { + for (WhitelistClass whitelistClass : whitelist.whitelistClasses) { String targetCanonicalClassName = whitelistClass.javaClassName.replace('$', '.'); for (WhitelistConstructor whitelistConstructor : whitelistClass.whitelistConstructors) { origin = whitelistConstructor.origin; painlessLookupBuilder.addPainlessConstructor( - targetCanonicalClassName, whitelistConstructor.painlessParameterTypeNames); + targetCanonicalClassName, whitelistConstructor.canonicalTypeNameParameters); } for (WhitelistMethod whitelistMethod : whitelistClass.whitelistMethods) { origin = whitelistMethod.origin; painlessLookupBuilder.addPainlessMethod( - whitelist.javaClassLoader, targetCanonicalClassName, whitelistMethod.javaAugmentedClassName, - whitelistMethod.javaMethodName, whitelistMethod.painlessReturnTypeName, - whitelistMethod.painlessParameterTypeNames); + whitelist.classLoader, targetCanonicalClassName, whitelistMethod.augmentedCanonicalClassName, + whitelistMethod.methodName, whitelistMethod.returnCanonicalTypeName, + whitelistMethod.canonicalTypeNameParameters); } for (WhitelistField whitelistField : whitelistClass.whitelistFields) { origin = whitelistField.origin; painlessLookupBuilder.addPainlessField( - targetCanonicalClassName, whitelistField.javaFieldName, whitelistField.painlessFieldTypeName); + targetCanonicalClassName, whitelistField.fieldName, whitelistField.canonicalTypeNameParameter); } } } @@ -186,8 +220,12 @@ private Class canonicalTypeNameToType(String canonicalTypeName) { return PainlessLookupUtility.canonicalTypeNameToType(canonicalTypeName, canonicalClassNamesToClasses); } - private void validateType(Class type) { - PainlessLookupUtility.validateType(type, classesToPainlessClassBuilders.keySet()); + private boolean isValidType(Class type) { + while (type.getComponentType() != null) { + type = type.getComponentType(); + } + + return classesToPainlessClassBuilders.containsKey(type); } public void addPainlessClass(ClassLoader classLoader, String javaClassName, boolean importClassName) { @@ -277,27 +315,28 @@ public void addPainlessClass(Class clazz, boolean importClassName) { } } - public void addPainlessConstructor(String targetCanonicalClassName, List typeNameParameters) { + public void addPainlessConstructor(String targetCanonicalClassName, List canonicalTypeNameParameters) { Objects.requireNonNull(targetCanonicalClassName); - Objects.requireNonNull(typeNameParameters); + Objects.requireNonNull(canonicalTypeNameParameters); Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName); if (targetClass == null) { throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found" + - "for constructor [[" + targetCanonicalClassName + "], " + typeNameParameters + "]"); + "for constructor [[" + targetCanonicalClassName + "], " + canonicalTypeNameParameters + "]"); } - List> typeParameters = new ArrayList<>(typeNameParameters.size()); + List> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size()); - for (String typeNameParameter : typeNameParameters) { - try { - Class typeParameter = canonicalTypeNameToType(typeNameParameter); - typeParameters.add(typeParameter); - } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException("type parameter [" + typeNameParameter + "] not found " + - "for constructor [[" + targetCanonicalClassName + "], " + typeNameParameters + "]", iae); + for (String canonicalTypeNameParameter : canonicalTypeNameParameters) { + Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter); + + if (typeParameter == null) { + throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found " + + "for constructor [[" + targetCanonicalClassName + "], " + canonicalTypeNameParameters + "]"); } + + typeParameters.add(typeParameter); } addPainlessConstructor(targetClass, typeParameters); @@ -323,11 +362,9 @@ public void addPainlessConstructor(Class targetClass, List> typePara List> javaTypeParameters = new ArrayList<>(typeParametersSize); for (Class typeParameter : typeParameters) { - try { - validateType(typeParameter); - } catch (IllegalArgumentException iae) { + if (isValidType(typeParameter) == false) { throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " + - "for constructor [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]", iae); + "for constructor [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]"); } javaTypeParameters.add(typeToJavaType(typeParameter)); @@ -342,11 +379,10 @@ public void addPainlessConstructor(Class targetClass, List> typePara "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme); } - String painlessMethodKey = buildPainlessMethodKey(CONSTRUCTOR_NAME, typeParametersSize); - PainlessMethod painlessConstructor = painlessClassBuilder.constructors.get(painlessMethodKey); + String painlessConstructorKey = buildPainlessConstructorKey(typeParametersSize); + PainlessConstructor painlessConstructor = painlessClassBuilder.constructors.get(painlessConstructorKey); if (painlessConstructor == null) { - org.objectweb.asm.commons.Method asmConstructor = org.objectweb.asm.commons.Method.getMethod(javaConstructor); MethodHandle methodHandle; try { @@ -356,35 +392,36 @@ public void addPainlessConstructor(Class targetClass, List> typePara "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", iae); } - painlessConstructor = painlessMethodCache.computeIfAbsent( - new PainlessMethodCacheKey(targetClass, CONSTRUCTOR_NAME, typeParameters), - key -> new PainlessMethod(CONSTRUCTOR_NAME, targetClass, null, void.class, typeParameters, - asmConstructor, javaConstructor.getModifiers(), methodHandle) + MethodType methodType = methodHandle.type(); + + painlessConstructor = painlessConstuctorCache.computeIfAbsent( + new PainlessConstructorCacheKey(targetClass, typeParameters), + key -> new PainlessConstructor(javaConstructor, typeParameters, methodHandle, methodType) ); - painlessClassBuilder.constructors.put(painlessMethodKey, painlessConstructor); - } else if (painlessConstructor.arguments.equals(typeParameters) == false){ + painlessClassBuilder.constructors.put(painlessConstructorKey, painlessConstructor); + } else if (painlessConstructor.typeParameters.equals(typeParameters) == false){ throw new IllegalArgumentException("cannot have constructors " + "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] and " + - "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(painlessConstructor.arguments) + "] " + + "[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(painlessConstructor.typeParameters) + "] " + "with the same arity and different type parameters"); } } public void addPainlessMethod(ClassLoader classLoader, String targetCanonicalClassName, String augmentedCanonicalClassName, - String methodName, String returnCanonicalTypeName, List typeNameParameters) { + String methodName, String returnCanonicalTypeName, List canonicalTypeNameParameters) { Objects.requireNonNull(classLoader); Objects.requireNonNull(targetCanonicalClassName); Objects.requireNonNull(methodName); Objects.requireNonNull(returnCanonicalTypeName); - Objects.requireNonNull(typeNameParameters); + Objects.requireNonNull(canonicalTypeNameParameters); Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName); if (targetClass == null) { throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for method " + - "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]"); + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]"); } Class augmentedClass = null; @@ -394,29 +431,28 @@ public void addPainlessMethod(ClassLoader classLoader, String targetCanonicalCla augmentedClass = Class.forName(augmentedCanonicalClassName, true, classLoader); } catch (ClassNotFoundException cnfe) { throw new IllegalArgumentException("augmented class [" + augmentedCanonicalClassName + "] not found for method " + - "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]", cnfe); + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]", cnfe); } } - List> typeParameters = new ArrayList<>(typeNameParameters.size()); + List> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size()); - for (String typeNameParameter : typeNameParameters) { - try { - Class typeParameter = canonicalTypeNameToType(typeNameParameter); - typeParameters.add(typeParameter); - } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException("parameter type [" + typeNameParameter + "] not found for method " + - "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]", iae); + for (String canonicalTypeNameParameter : canonicalTypeNameParameters) { + Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter); + + if (typeParameter == null) { + throw new IllegalArgumentException("parameter type [" + canonicalTypeNameParameter + "] not found for method " + + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]"); } + + typeParameters.add(typeParameter); } - Class returnType; + Class returnType = canonicalTypeNameToType(returnCanonicalTypeName); - try { - returnType = canonicalTypeNameToType(returnCanonicalTypeName); - } catch (IllegalArgumentException iae) { + if (returnType == null) { throw new IllegalArgumentException("parameter type [" + returnCanonicalTypeName + "] not found for method " + - "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typeNameParameters + "]", iae); + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]"); } addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters); @@ -456,22 +492,18 @@ public void addPainlessMethod(Class targetClass, Class augmentedClass, Str } for (Class typeParameter : typeParameters) { - try { - validateType(typeParameter); - } catch (IllegalArgumentException iae) { + if (isValidType(typeParameter) == false) { throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " + "not found for method [[" + targetCanonicalClassName + "], [" + methodName + "], " + - typesToCanonicalTypeNames(typeParameters) + "]", iae); + typesToCanonicalTypeNames(typeParameters) + "]"); } javaTypeParameters.add(typeToJavaType(typeParameter)); } - try { - validateType(returnType); - } catch (IllegalArgumentException iae) { + if (isValidType(returnType) == false) { throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for method " + - "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]", iae); + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]"); } Method javaMethod; @@ -506,7 +538,6 @@ public void addPainlessMethod(Class targetClass, Class augmentedClass, Str PainlessMethod painlessMethod = painlessClassBuilder.staticMethods.get(painlessMethodKey); if (painlessMethod == null) { - org.objectweb.asm.commons.Method asmMethod = org.objectweb.asm.commons.Method.getMethod(javaMethod); MethodHandle methodHandle; try { @@ -516,28 +547,27 @@ public void addPainlessMethod(Class targetClass, Class augmentedClass, Str "[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", iae); } + MethodType methodType = methodHandle.type(); + painlessMethod = painlessMethodCache.computeIfAbsent( new PainlessMethodCacheKey(targetClass, methodName, typeParameters), - key -> new PainlessMethod(methodName, targetClass, null, returnType, - typeParameters, asmMethod, javaMethod.getModifiers(), methodHandle)); + key -> new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType)); painlessClassBuilder.staticMethods.put(painlessMethodKey, painlessMethod); - } else if ((painlessMethod.name.equals(methodName) && painlessMethod.rtn == returnType && - painlessMethod.arguments.equals(typeParameters)) == false) { + } else if (painlessMethod.returnType == returnType && painlessMethod.typeParameters.equals(typeParameters) == false) { throw new IllegalArgumentException("cannot have static methods " + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + "[" + typeToCanonicalTypeName(returnType) + "], " + typesToCanonicalTypeNames(typeParameters) + "] and " + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + - "[" + typeToCanonicalTypeName(painlessMethod.rtn) + "], " + - typesToCanonicalTypeNames(painlessMethod.arguments) + "] " + + "[" + typeToCanonicalTypeName(painlessMethod.returnType) + "], " + + typesToCanonicalTypeNames(painlessMethod.typeParameters) + "] " + "with the same arity and different return type or type parameters"); } } else { PainlessMethod painlessMethod = painlessClassBuilder.staticMethods.get(painlessMethodKey); if (painlessMethod == null) { - org.objectweb.asm.commons.Method asmMethod = org.objectweb.asm.commons.Method.getMethod(javaMethod); MethodHandle methodHandle; if (augmentedClass == null) { @@ -557,30 +587,30 @@ public void addPainlessMethod(Class targetClass, Class augmentedClass, Str } } + MethodType methodType = methodHandle.type(); + painlessMethod = painlessMethodCache.computeIfAbsent( new PainlessMethodCacheKey(targetClass, methodName, typeParameters), - key -> new PainlessMethod(methodName, targetClass, augmentedClass, returnType, - typeParameters, asmMethod, javaMethod.getModifiers(), methodHandle)); + key -> new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType)); painlessClassBuilder.methods.put(painlessMethodKey, painlessMethod); - } else if ((painlessMethod.name.equals(methodName) && painlessMethod.rtn == returnType && - painlessMethod.arguments.equals(typeParameters)) == false) { + } else if (painlessMethod.returnType == returnType && painlessMethod.typeParameters.equals(typeParameters) == false) { throw new IllegalArgumentException("cannot have methods " + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + "[" + typeToCanonicalTypeName(returnType) + "], " + typesToCanonicalTypeNames(typeParameters) + "] and " + "[[" + targetCanonicalClassName + "], [" + methodName + "], " + - "[" + typeToCanonicalTypeName(painlessMethod.rtn) + "], " + - typesToCanonicalTypeNames(painlessMethod.arguments) + "] " + + "[" + typeToCanonicalTypeName(painlessMethod.returnType) + "], " + + typesToCanonicalTypeNames(painlessMethod.typeParameters) + "] " + "with the same arity and different return type or type parameters"); } } } - public void addPainlessField(String targetCanonicalClassName, String fieldName, String typeNameParameter) { + public void addPainlessField(String targetCanonicalClassName, String fieldName, String canonicalTypeNameParameter) { Objects.requireNonNull(targetCanonicalClassName); Objects.requireNonNull(fieldName); - Objects.requireNonNull(typeNameParameter); + Objects.requireNonNull(canonicalTypeNameParameter); Class targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName); @@ -588,12 +618,10 @@ public void addPainlessField(String targetCanonicalClassName, String fieldName, throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] not found"); } - Class typeParameter; + Class typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter); - try { - typeParameter = canonicalTypeNameToType(typeNameParameter); - } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException("type parameter [" + typeNameParameter + "] not found " + + if (typeParameter == null) { + throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found " + "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]"); } @@ -624,11 +652,9 @@ public void addPainlessField(Class targetClass, String fieldName, Class ty throw new IllegalArgumentException("class [" + targetCanonicalClassName + "] not found"); } - try { - validateType(typeParameter); - } catch (IllegalArgumentException iae) { + if (isValidType(typeParameter) == false) { throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " + - "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]", iae); + "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]"); } Field javaField; @@ -646,11 +672,20 @@ public void addPainlessField(Class targetClass, String fieldName, Class ty "for field [[" + targetCanonicalClassName + "], [" + fieldName + "]"); } + MethodHandle methodHandleGetter; + + try { + methodHandleGetter = MethodHandles.publicLookup().unreflectGetter(javaField); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException( + "getter method handle not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]"); + } + String painlessFieldKey = buildPainlessFieldKey(fieldName); if (Modifier.isStatic(javaField.getModifiers())) { if (Modifier.isFinal(javaField.getModifiers()) == false) { - throw new IllegalArgumentException("static field [[" + targetCanonicalClassName + "]. [" + fieldName + "]] must be final"); + throw new IllegalArgumentException("static field [[" + targetCanonicalClassName + "], [" + fieldName + "]] must be final"); } PainlessField painlessField = painlessClassBuilder.staticFields.get(painlessFieldKey); @@ -658,28 +693,18 @@ public void addPainlessField(Class targetClass, String fieldName, Class ty if (painlessField == null) { painlessField = painlessFieldCache.computeIfAbsent( new PainlessFieldCacheKey(targetClass, fieldName, typeParameter), - key -> new PainlessField(fieldName, javaField.getName(), targetClass, - typeParameter, javaField.getModifiers(), null, null)); + key -> new PainlessField(javaField, typeParameter, methodHandleGetter, null)); painlessClassBuilder.staticFields.put(painlessFieldKey, painlessField); - } else if (painlessField.clazz != typeParameter) { + } else if (painlessField.typeParameter != typeParameter) { throw new IllegalArgumentException("cannot have static fields " + "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" + typeToCanonicalTypeName(typeParameter) + "] and " + - "[[" + targetCanonicalClassName + "], [" + painlessField.name + "], " + - typeToCanonicalTypeName(painlessField.clazz) + "] " + - "with the same and different type parameters"); + "[[" + targetCanonicalClassName + "], [" + painlessField.javaField.getName() + "], " + + typeToCanonicalTypeName(painlessField.typeParameter) + "] " + + "with the same name and different type parameters"); } } else { - MethodHandle methodHandleGetter; - - try { - methodHandleGetter = MethodHandles.publicLookup().unreflectGetter(javaField); - } catch (IllegalAccessException iae) { - throw new IllegalArgumentException( - "getter method handle not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]"); - } - MethodHandle methodHandleSetter; try { @@ -694,17 +719,16 @@ public void addPainlessField(Class targetClass, String fieldName, Class ty if (painlessField == null) { painlessField = painlessFieldCache.computeIfAbsent( new PainlessFieldCacheKey(targetClass, painlessFieldKey, typeParameter), - key -> new PainlessField(fieldName, javaField.getName(), targetClass, - typeParameter, javaField.getModifiers(), methodHandleGetter, methodHandleSetter)); + key -> new PainlessField(javaField, typeParameter, methodHandleGetter, methodHandleSetter)); painlessClassBuilder.fields.put(fieldName, painlessField); - } else if (painlessField.clazz != typeParameter) { + } else if (painlessField.typeParameter != typeParameter) { throw new IllegalArgumentException("cannot have fields " + "[[" + targetCanonicalClassName + "], [" + fieldName + "], [" + typeToCanonicalTypeName(typeParameter) + "] and " + - "[[" + targetCanonicalClassName + "], [" + painlessField.name + "], " + - typeToCanonicalTypeName(painlessField.clazz) + "] " + - "with the same and different type parameters"); + "[[" + targetCanonicalClassName + "], [" + painlessField.javaField.getName() + "], " + + typeToCanonicalTypeName(painlessField.typeParameter) + "] " + + "with the same name and different type parameters"); } } } @@ -768,8 +792,8 @@ private void copyPainlessClassMembers(Class originalClass, Class targetCla PainlessMethod newPainlessMethod = painlessMethodEntry.getValue(); PainlessMethod existingPainlessMethod = targetPainlessClassBuilder.methods.get(painlessMethodKey); - if (existingPainlessMethod == null || existingPainlessMethod.target != newPainlessMethod.target && - existingPainlessMethod.target.isAssignableFrom(newPainlessMethod.target)) { + if (existingPainlessMethod == null || existingPainlessMethod.targetClass != newPainlessMethod.targetClass && + existingPainlessMethod.targetClass.isAssignableFrom(newPainlessMethod.targetClass)) { targetPainlessClassBuilder.methods.put(painlessMethodKey, newPainlessMethod); } } @@ -779,8 +803,9 @@ private void copyPainlessClassMembers(Class originalClass, Class targetCla PainlessField newPainlessField = painlessFieldEntry.getValue(); PainlessField existingPainlessField = targetPainlessClassBuilder.fields.get(painlessFieldKey); - if (existingPainlessField == null || existingPainlessField.target != newPainlessField.target && - existingPainlessField.target.isAssignableFrom(newPainlessField.target)) { + if (existingPainlessField == null || + existingPainlessField.javaField.getDeclaringClass() != newPainlessField.javaField.getDeclaringClass() && + existingPainlessField.javaField.getDeclaringClass().isAssignableFrom(newPainlessField.javaField.getDeclaringClass())) { targetPainlessClassBuilder.fields.put(painlessFieldKey, newPainlessField); } } @@ -794,27 +819,27 @@ private void cacheRuntimeHandles() { private void cacheRuntimeHandles(PainlessClassBuilder painlessClassBuilder) { for (PainlessMethod painlessMethod : painlessClassBuilder.methods.values()) { - String methodName = painlessMethod.name; - int typeParametersSize = painlessMethod.arguments.size(); + String methodName = painlessMethod.javaMethod.getName(); + int typeParametersSize = painlessMethod.typeParameters.size(); if (typeParametersSize == 0 && methodName.startsWith("get") && methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3))) { painlessClassBuilder.getterMethodHandles.putIfAbsent( - Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), painlessMethod.handle); + Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), painlessMethod.methodHandle); } else if (typeParametersSize == 0 && methodName.startsWith("is") && methodName.length() > 2 && Character.isUpperCase(methodName.charAt(2))) { painlessClassBuilder.getterMethodHandles.putIfAbsent( - Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3), painlessMethod.handle); + Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3), painlessMethod.methodHandle); } else if (typeParametersSize == 1 && methodName.startsWith("set") && methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3))) { painlessClassBuilder.setterMethodHandles.putIfAbsent( - Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), painlessMethod.handle); + Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4), painlessMethod.methodHandle); } } for (PainlessField painlessField : painlessClassBuilder.fields.values()) { - painlessClassBuilder.getterMethodHandles.put(painlessField.name, painlessField.getter); - painlessClassBuilder.setterMethodHandles.put(painlessField.name, painlessField.setter); + painlessClassBuilder.getterMethodHandles.put(painlessField.javaField.getName(), painlessField.getterMethodHandle); + painlessClassBuilder.setterMethodHandles.put(painlessField.javaField.getName(), painlessField.setterMethodHandle); } } @@ -844,7 +869,7 @@ private void setFunctionalInterfaceMethod(Class targetClass, PainlessClassBui } else if (javaMethods.size() == 1) { java.lang.reflect.Method javaMethod = javaMethods.get(0); String painlessMethodKey = buildPainlessMethodKey(javaMethod.getName(), javaMethod.getParameterCount()); - painlessClassBuilder.functionalMethod = painlessClassBuilder.methods.get(painlessMethodKey); + painlessClassBuilder.functionalInterfaceMethod = painlessClassBuilder.methods.get(painlessMethodKey); } } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java index 86d3f87663867..f2eb434516961 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupUtility.java @@ -20,7 +20,6 @@ package org.elasticsearch.painless.lookup; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; @@ -101,45 +100,47 @@ public static Class canonicalTypeNameToType(String canonicalTypeName, Map type) { String canonicalTypeName = type.getCanonicalName(); - if (canonicalTypeName.startsWith(def.class.getCanonicalName())) { + if (canonicalTypeName == null) { + canonicalTypeName = ANONYMOUS_CLASS_NAME; + } else if (canonicalTypeName.startsWith(def.class.getCanonicalName())) { canonicalTypeName = canonicalTypeName.replace(def.class.getCanonicalName(), DEF_CLASS_NAME); } @@ -252,22 +255,6 @@ public static Class typeToJavaType(Class type) { return type; } - /** - * Ensures a type exists based on the terminology specified as part of {@link PainlessLookupUtility}. Throws an - * {@link IllegalArgumentException} if the type does not exist. - */ - public static void validateType(Class type, Collection> classes) { - String canonicalTypeName = typeToCanonicalTypeName(type); - - while (type.getComponentType() != null) { - type = type.getComponentType(); - } - - if (classes.contains(type) == false) { - throw new IllegalArgumentException("type [" + canonicalTypeName + "] not found"); - } - } - /** * Converts a type to its boxed type equivalent if one exists based on the terminology specified as part of * {@link PainlessLookupUtility}. Otherwise, this behaves as an identity function. @@ -336,6 +323,13 @@ public static boolean isConstantType(Class type) { type == String.class; } + /** + * Constructs a painless constructor key used to lookup painless constructors from a painless class. + */ + public static String buildPainlessConstructorKey(int constructorArity) { + return CONSTRUCTOR_NAME + "/" + constructorArity; + } + /** * Constructs a painless method key used to lookup painless methods from a painless class. */ @@ -350,6 +344,11 @@ public static String buildPainlessFieldKey(String fieldName) { return fieldName; } + /** + * The name for an anonymous class. + */ + public static final String ANONYMOUS_CLASS_NAME = "$anonymous"; + /** * The def type name as specified in the source for a script. */ diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessMethod.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessMethod.java index 3321de94a267f..9dd143a402865 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessMethod.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessMethod.java @@ -19,113 +19,28 @@ package org.elasticsearch.painless.lookup; -import org.elasticsearch.painless.MethodWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; -import java.lang.reflect.Modifier; +import java.lang.reflect.Method; import java.util.Collections; import java.util.List; public class PainlessMethod { - public final String name; - public final Class target; - public final Class augmentation; - public final Class rtn; - public final List> arguments; - public final org.objectweb.asm.commons.Method method; - public final int modifiers; - public final MethodHandle handle; - - public PainlessMethod(String name, Class target, Class augmentation, Class rtn, List> arguments, - org.objectweb.asm.commons.Method method, int modifiers, MethodHandle handle) { - this.name = name; - this.augmentation = augmentation; - this.target = target; - this.rtn = rtn; - this.arguments = Collections.unmodifiableList(arguments); - this.method = method; - this.modifiers = modifiers; - this.handle = handle; - } - - /** - * Returns MethodType for this method. - *

      - * This works even for user-defined Methods (where the MethodHandle is null). - */ - public MethodType getMethodType() { - // we have a methodhandle already (e.g. whitelisted class) - // just return its type - if (handle != null) { - return handle.type(); - } - // otherwise compute it - final Class params[]; - final Class returnValue; - if (augmentation != null) { - // static method disguised as virtual/interface method - params = new Class[1 + arguments.size()]; - params[0] = augmentation; - for (int i = 0; i < arguments.size(); i++) { - params[i + 1] = PainlessLookupUtility.typeToJavaType(arguments.get(i)); - } - returnValue = PainlessLookupUtility.typeToJavaType(rtn); - } else if (Modifier.isStatic(modifiers)) { - // static method: straightforward copy - params = new Class[arguments.size()]; - for (int i = 0; i < arguments.size(); i++) { - params[i] = PainlessLookupUtility.typeToJavaType(arguments.get(i)); - } - returnValue = PainlessLookupUtility.typeToJavaType(rtn); - } else if ("".equals(name)) { - // constructor: returns the owner class - params = new Class[arguments.size()]; - for (int i = 0; i < arguments.size(); i++) { - params[i] = PainlessLookupUtility.typeToJavaType(arguments.get(i)); - } - returnValue = target; - } else { - // virtual/interface method: add receiver class - params = new Class[1 + arguments.size()]; - params[0] = target; - for (int i = 0; i < arguments.size(); i++) { - params[i + 1] = PainlessLookupUtility.typeToJavaType(arguments.get(i)); - } - returnValue = PainlessLookupUtility.typeToJavaType(rtn); - } - return MethodType.methodType(returnValue, params); - } - - public void write(MethodWriter writer) { - final org.objectweb.asm.Type type; - final Class clazz; - if (augmentation != null) { - assert Modifier.isStatic(modifiers); - clazz = augmentation; - type = org.objectweb.asm.Type.getType(augmentation); - } else { - clazz = target; - type = Type.getType(target); - } - - if (Modifier.isStatic(modifiers)) { - // invokeStatic assumes that the owner class is not an interface, so this is a - // special case for interfaces where the interface method boolean needs to be set to - // true to reference the appropriate class constant when calling a static interface - // method since java 8 did not check, but java 9 and 10 do - if (Modifier.isInterface(clazz.getModifiers())) { - writer.visitMethodInsn(Opcodes.INVOKESTATIC, - type.getInternalName(), name, getMethodType().toMethodDescriptorString(), true); - } else { - writer.invokeStatic(type, method); - } - } else if (Modifier.isInterface(clazz.getModifiers())) { - writer.invokeInterface(type, method); - } else { - writer.invokeVirtual(type, method); - } + public final Method javaMethod; + public final Class targetClass; + public final Class returnType; + public final List> typeParameters; + public final MethodHandle methodHandle; + public final MethodType methodType; + + public PainlessMethod(Method javaMethod, Class targetClass, Class returnType, List> typeParameters, + MethodHandle methodHandle, MethodType methodType) { + + this.javaMethod = javaMethod; + this.targetClass = targetClass; + this.returnType = returnType; + this.typeParameters = Collections.unmodifiableList(typeParameters); + this.methodHandle = methodHandle; + this.methodType = methodType; } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java index 098c75386e1a6..1f9973df19224 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java @@ -21,10 +21,10 @@ import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; +import org.elasticsearch.painless.Locals.LocalMethod; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessLookupUtility; -import org.elasticsearch.painless.lookup.PainlessMethod; +import org.objectweb.asm.commons.Method; import java.util.List; import java.util.Objects; @@ -40,7 +40,7 @@ public final class ECallLocal extends AExpression { private final String name; private final List arguments; - private PainlessMethod method = null; + private LocalMethod method = null; public ECallLocal(Location location, String name, List arguments) { super(location); @@ -58,8 +58,7 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - String methodKey = PainlessLookupUtility.buildPainlessMethodKey(name, arguments.size()); - method = locals.getMethod(methodKey); + method = locals.getMethod(name, arguments.size()); if (method == null) { throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments.")); @@ -68,14 +67,14 @@ void analyze(Locals locals) { for (int argument = 0; argument < arguments.size(); ++argument) { AExpression expression = arguments.get(argument); - expression.expected = method.arguments.get(argument); + expression.expected = method.typeParameters.get(argument); expression.internal = true; expression.analyze(locals); arguments.set(argument, expression.cast(locals)); } statement = true; - actual = method.rtn; + actual = method.returnType; } @Override @@ -86,7 +85,7 @@ void write(MethodWriter writer, Globals globals) { argument.write(writer, globals); } - writer.invokeStatic(CLASS_TYPE, method.method); + writer.invokeStatic(CLASS_TYPE, new Method(method.name, method.methodType.toMethodDescriptorString())); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java index 7b35bc1b48ee5..a649fa7611c65 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java @@ -19,7 +19,6 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.Globals; @@ -35,8 +34,6 @@ import java.util.Objects; import java.util.Set; -import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE; - /** * Represents a capturing function reference. */ @@ -76,23 +73,8 @@ void analyze(Locals locals) { defPointer = null; // static case if (captured.clazz != def.class) { - try { - ref = new FunctionRef(locals.getPainlessLookup(), expected, - PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1); - - // check casts between the interface method and the delegate method are legal - for (int i = 0; i < ref.interfaceMethod.arguments.size(); ++i) { - Class from = ref.interfaceMethod.arguments.get(i); - Class to = ref.delegateMethod.arguments.get(i); - AnalyzerCaster.getLegalCast(location, from, to, false, true); - } - - if (ref.interfaceMethod.rtn != void.class) { - AnalyzerCaster.getLegalCast(location, ref.delegateMethod.rtn, ref.interfaceMethod.rtn, false, true); - } - } catch (IllegalArgumentException e) { - throw createError(e); - } + ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location, + expected, PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1); } actual = expected; } @@ -114,17 +96,7 @@ void write(MethodWriter writer, Globals globals) { } else { // typed interface, typed implementation writer.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot()); - writer.invokeDynamic( - ref.interfaceMethodName, - ref.factoryDescriptor, - LAMBDA_BOOTSTRAP_HANDLE, - ref.interfaceType, - ref.delegateClassName, - ref.delegateInvokeType, - ref.delegateMethodName, - ref.delegateType, - ref.isDelegateInterface ? 1 : 0 - ); + writer.invokeLambdaCall(ref); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java index b07613714b8ef..08236a965fe52 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java @@ -63,6 +63,6 @@ void write(MethodWriter writer, Globals globals) { @Override public String toString() { - return singleLineToString(PainlessLookupUtility.typeToCanonicalTypeName(cast.to), child); + return singleLineToString(PainlessLookupUtility.typeToCanonicalTypeName(cast.targetType), child); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java index d19068f8fa6a2..3ad3018c61e34 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java @@ -49,9 +49,9 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - try { - actual = locals.getPainlessLookup().getJavaClassFromPainlessType(type); - } catch (IllegalArgumentException exception) { + actual = locals.getPainlessLookup().canonicalTypeNameToType(type); + + if (actual == null) { throw createError(new IllegalArgumentException("Not a type [" + type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java index d787db5d41c92..c97cc66c7c7ca 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java @@ -19,21 +19,16 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessLookupUtility; -import org.elasticsearch.painless.lookup.PainlessMethod; import org.objectweb.asm.Type; import java.util.Objects; import java.util.Set; -import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE; - /** * Represents a function reference. */ @@ -62,40 +57,7 @@ void analyze(Locals locals) { defPointer = "S" + type + "." + call + ",0"; } else { defPointer = null; - try { - if ("this".equals(type)) { - // user's own function - PainlessMethod interfaceMethod = locals.getPainlessLookup().getPainlessStructFromJavaClass(expected).functionalMethod; - if (interfaceMethod == null) { - throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " + - "to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"); - } - PainlessMethod delegateMethod = - locals.getMethod(PainlessLookupUtility.buildPainlessMethodKey(call, interfaceMethod.arguments.size())); - if (delegateMethod == null) { - throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " + - "to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], function not found"); - } - ref = new FunctionRef(expected, interfaceMethod, delegateMethod, 0); - - // check casts between the interface method and the delegate method are legal - for (int i = 0; i < interfaceMethod.arguments.size(); ++i) { - Class from = interfaceMethod.arguments.get(i); - Class to = delegateMethod.arguments.get(i); - AnalyzerCaster.getLegalCast(location, from, to, false, true); - } - - if (interfaceMethod.rtn != void.class) { - AnalyzerCaster.getLegalCast(location, delegateMethod.rtn, interfaceMethod.rtn, false, true); - } - } else { - // whitelist lookup - ref = new FunctionRef(locals.getPainlessLookup(), expected, type, call, 0); - } - - } catch (IllegalArgumentException e) { - throw createError(e); - } + ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location, expected, type, call, 0); actual = expected; } } @@ -104,17 +66,7 @@ void analyze(Locals locals) { void write(MethodWriter writer, Globals globals) { if (ref != null) { writer.writeDebugInfo(location); - writer.invokeDynamic( - ref.interfaceMethodName, - ref.factoryDescriptor, - LAMBDA_BOOTSTRAP_HANDLE, - ref.interfaceType, - ref.delegateClassName, - ref.delegateInvokeType, - ref.delegateMethodName, - ref.delegateType, - ref.isDelegateInterface ? 1 : 0 - ); + writer.invokeLambdaCall(ref); } else { // TODO: don't do this: its just to cutover :) writer.push((String)null); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java index 2fa8ca8ca9513..73e4f176ea1ba 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EInstanceof.java @@ -54,12 +54,11 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - Class clazz; // ensure the specified type is part of the definition - try { - clazz = locals.getPainlessLookup().getJavaClassFromPainlessType(this.type); - } catch (IllegalArgumentException exception) { + Class clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type); + + if (clazz == null) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java index ab1442be805eb..af906416ca7bc 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java @@ -19,7 +19,6 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; @@ -39,8 +38,6 @@ import java.util.Objects; import java.util.Set; -import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE; - /** * Lambda expression node. *

      @@ -118,29 +115,30 @@ void analyze(Locals locals) { actualParamTypeStrs.add(type); } } + } else { // we know the method statically, infer return type and any unknown/def types - interfaceMethod = locals.getPainlessLookup().getPainlessStructFromJavaClass(expected).functionalMethod; + interfaceMethod = locals.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(expected); if (interfaceMethod == null) { throw createError(new IllegalArgumentException("Cannot pass lambda to " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface")); } // check arity before we manipulate parameters - if (interfaceMethod.arguments.size() != paramTypeStrs.size()) - throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name + + if (interfaceMethod.typeParameters.size() != paramTypeStrs.size()) + throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.javaMethod.getName() + "] in [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "]"); // for method invocation, its allowed to ignore the return value - if (interfaceMethod.rtn == void.class) { + if (interfaceMethod.returnType == void.class) { returnType = def.class; } else { - returnType = interfaceMethod.rtn; + returnType = interfaceMethod.returnType; } // replace any null types with the actual type actualParamTypeStrs = new ArrayList<>(paramTypeStrs.size()); for (int i = 0; i < paramTypeStrs.size(); i++) { String paramType = paramTypeStrs.get(i); if (paramType == null) { - actualParamTypeStrs.add(PainlessLookupUtility.typeToCanonicalTypeName(interfaceMethod.arguments.get(i))); + actualParamTypeStrs.add(PainlessLookupUtility.typeToCanonicalTypeName(interfaceMethod.typeParameters.get(i))); } else { actualParamTypeStrs.add(paramType); } @@ -172,7 +170,7 @@ void analyze(Locals locals) { desugared = new SFunction(reserved, location, PainlessLookupUtility.typeToCanonicalTypeName(returnType), name, paramTypes, paramNames, statements, true); desugared.generateSignature(locals.getPainlessLookup()); - desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, + desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.name, returnType, desugared.parameters, captures.size(), reserved.getMaxLoopCounter())); // setup method reference to synthetic method @@ -182,23 +180,8 @@ void analyze(Locals locals) { defPointer = "Sthis." + name + "," + captures.size(); } else { defPointer = null; - try { - ref = new FunctionRef(expected, interfaceMethod, desugared.method, captures.size()); - } catch (IllegalArgumentException e) { - throw createError(e); - } - - // check casts between the interface method and the delegate method are legal - for (int i = 0; i < interfaceMethod.arguments.size(); ++i) { - Class from = interfaceMethod.arguments.get(i); - Class to = desugared.parameters.get(i + captures.size()).clazz; - AnalyzerCaster.getLegalCast(location, from, to, false, true); - } - - if (interfaceMethod.rtn != void.class) { - AnalyzerCaster.getLegalCast(location, desugared.rtnType, interfaceMethod.rtn, false, true); - } - + ref = FunctionRef.create( + locals.getPainlessLookup(), locals.getMethods(), location, expected, "this", desugared.name, captures.size()); actual = expected; } } @@ -214,17 +197,7 @@ void write(MethodWriter writer, Globals globals) { writer.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot()); } - writer.invokeDynamic( - ref.interfaceMethodName, - ref.factoryDescriptor, - LAMBDA_BOOTSTRAP_HANDLE, - ref.interfaceType, - ref.delegateClassName, - ref.delegateInvokeType, - ref.delegateMethodName, - ref.delegateType, - ref.isDelegateInterface ? 1 : 0 - ); + writer.invokeLambdaCall(ref); } else { // placeholder writer.push((String)null); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java index e0af653d2098a..8c9154aaaf304 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EListInit.java @@ -23,22 +23,25 @@ import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessLookupUtility; +import org.elasticsearch.painless.lookup.PainlessConstructor; import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.lookup.def; import org.objectweb.asm.Type; +import org.objectweb.asm.commons.Method; import java.util.ArrayList; import java.util.List; import java.util.Set; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; + /** * Represents a list initialization shortcut. */ public final class EListInit extends AExpression { private final List values; - private PainlessMethod constructor = null; + private PainlessConstructor constructor = null; private PainlessMethod method = null; public EListInit(Location location, List values) { @@ -62,18 +65,17 @@ void analyze(Locals locals) { actual = ArrayList.class; - constructor = locals.getPainlessLookup().getPainlessStructFromJavaClass(actual).constructors - .get(PainlessLookupUtility.buildPainlessMethodKey("", 0)); + constructor = locals.getPainlessLookup().lookupPainlessConstructor(actual, 0); if (constructor == null) { - throw createError(new IllegalStateException("Illegal tree structure.")); + throw createError(new IllegalArgumentException( + "constructor [" + typeToCanonicalTypeName(actual) + ", /0] not found")); } - method = locals.getPainlessLookup().getPainlessStructFromJavaClass(actual).methods - .get(PainlessLookupUtility.buildPainlessMethodKey("add", 1)); + method = locals.getPainlessLookup().lookupPainlessMethod(actual, false, "add", 1); if (method == null) { - throw createError(new IllegalStateException("Illegal tree structure.")); + throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(actual) + ", add/1] not found")); } for (int index = 0; index < values.size(); ++index) { @@ -92,12 +94,13 @@ void write(MethodWriter writer, Globals globals) { writer.newInstance(MethodWriter.getType(actual)); writer.dup(); - writer.invokeConstructor(Type.getType(constructor.target), constructor.method); + writer.invokeConstructor( + Type.getType(constructor.javaConstructor.getDeclaringClass()), Method.getMethod(constructor.javaConstructor)); for (AExpression value : values) { writer.dup(); value.write(writer, globals); - method.write(writer); + writer.invokeMethodCall(method); writer.pop(); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java index d81f08dc3cc54..11c12b2cd0a96 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EMapInit.java @@ -23,15 +23,18 @@ import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessLookupUtility; +import org.elasticsearch.painless.lookup.PainlessConstructor; import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.lookup.def; import org.objectweb.asm.Type; +import org.objectweb.asm.commons.Method; import java.util.HashMap; import java.util.List; import java.util.Set; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; + /** * Represents a map initialization shortcut. */ @@ -39,7 +42,7 @@ public final class EMapInit extends AExpression { private final List keys; private final List values; - private PainlessMethod constructor = null; + private PainlessConstructor constructor = null; private PainlessMethod method = null; public EMapInit(Location location, List keys, List values) { @@ -68,18 +71,17 @@ void analyze(Locals locals) { actual = HashMap.class; - constructor = locals.getPainlessLookup().getPainlessStructFromJavaClass(actual).constructors - .get(PainlessLookupUtility.buildPainlessMethodKey("", 0)); + constructor = locals.getPainlessLookup().lookupPainlessConstructor(actual, 0); if (constructor == null) { - throw createError(new IllegalStateException("Illegal tree structure.")); + throw createError(new IllegalArgumentException( + "constructor [" + typeToCanonicalTypeName(actual) + ", /0] not found")); } - method = locals.getPainlessLookup().getPainlessStructFromJavaClass(actual).methods - .get(PainlessLookupUtility.buildPainlessMethodKey("put", 2)); + method = locals.getPainlessLookup().lookupPainlessMethod(actual, false, "put", 2); if (method == null) { - throw createError(new IllegalStateException("Illegal tree structure.")); + throw createError(new IllegalArgumentException("method [" + typeToCanonicalTypeName(actual) + ", put/2] not found")); } if (keys.size() != values.size()) { @@ -111,7 +113,8 @@ void write(MethodWriter writer, Globals globals) { writer.newInstance(MethodWriter.getType(actual)); writer.dup(); - writer.invokeConstructor(Type.getType(constructor.target), constructor.method); + writer.invokeConstructor( + Type.getType(constructor.javaConstructor.getDeclaringClass()), Method.getMethod(constructor.javaConstructor)); for (int index = 0; index < keys.size(); ++index) { AExpression key = keys.get(index); @@ -120,7 +123,7 @@ void write(MethodWriter writer, Globals globals) { writer.dup(); key.write(writer, globals); value.write(writer, globals); - method.write(writer); + writer.invokeMethodCall(method); writer.pop(); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java index f9bd4cebc3fed..cef005de9c3bc 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArray.java @@ -54,15 +54,13 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - if (!read) { - throw createError(new IllegalArgumentException("A newly created array must be read from.")); + if (!read) { + throw createError(new IllegalArgumentException("A newly created array must be read from.")); } - Class clazz; + Class clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type); - try { - clazz = locals.getPainlessLookup().getJavaClassFromPainlessType(this.type); - } catch (IllegalArgumentException exception) { + if (clazz == null) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java index f092a17c9fc4a..9423ed5d109de 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewObj.java @@ -23,15 +23,17 @@ import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessClass; +import org.elasticsearch.painless.lookup.PainlessConstructor; import org.elasticsearch.painless.lookup.PainlessLookupUtility; -import org.elasticsearch.painless.lookup.PainlessMethod; import org.objectweb.asm.Type; +import org.objectweb.asm.commons.Method; import java.util.List; import java.util.Objects; import java.util.Set; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; + /** * Represents and object instantiation. */ @@ -40,7 +42,7 @@ public final class ENewObj extends AExpression { private final String type; private final List arguments; - private PainlessMethod constructor; + private PainlessConstructor constructor; public ENewObj(Location location, String type, List arguments) { super(location); @@ -58,39 +60,38 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - try { - actual = locals.getPainlessLookup().getJavaClassFromPainlessType(this.type); - } catch (IllegalArgumentException exception) { + actual = locals.getPainlessLookup().canonicalTypeNameToType(this.type); + + if (actual == null) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } - PainlessClass struct = locals.getPainlessLookup().getPainlessStructFromJavaClass(actual); - constructor = struct.constructors.get(PainlessLookupUtility.buildPainlessMethodKey("", arguments.size())); + constructor = locals.getPainlessLookup().lookupPainlessConstructor(actual, arguments.size()); - if (constructor != null) { - Class[] types = new Class[constructor.arguments.size()]; - constructor.arguments.toArray(types); + if (constructor == null) { + throw createError(new IllegalArgumentException( + "constructor [" + typeToCanonicalTypeName(actual) + ", /" + arguments.size() + "] not found")); + } - if (constructor.arguments.size() != arguments.size()) { - throw createError(new IllegalArgumentException( - "When calling constructor on type [" + PainlessLookupUtility.typeToCanonicalTypeName(actual) + "] " + - "expected [" + constructor.arguments.size() + "] arguments, but found [" + arguments.size() + "].")); - } + Class[] types = new Class[constructor.typeParameters.size()]; + constructor.typeParameters.toArray(types); - for (int argument = 0; argument < arguments.size(); ++argument) { - AExpression expression = arguments.get(argument); + if (constructor.typeParameters.size() != arguments.size()) { + throw createError(new IllegalArgumentException( + "When calling constructor on type [" + PainlessLookupUtility.typeToCanonicalTypeName(actual) + "] " + + "expected [" + constructor.typeParameters.size() + "] arguments, but found [" + arguments.size() + "].")); + } - expression.expected = types[argument]; - expression.internal = true; - expression.analyze(locals); - arguments.set(argument, expression.cast(locals)); - } + for (int argument = 0; argument < arguments.size(); ++argument) { + AExpression expression = arguments.get(argument); - statement = true; - } else { - throw createError(new IllegalArgumentException( - "Unknown new call on type [" + PainlessLookupUtility.typeToCanonicalTypeName(actual) + "].")); + expression.expected = types[argument]; + expression.internal = true; + expression.analyze(locals); + arguments.set(argument, expression.cast(locals)); } + + statement = true; } @Override @@ -107,7 +108,8 @@ void write(MethodWriter writer, Globals globals) { argument.write(writer, globals); } - writer.invokeConstructor(Type.getType(constructor.target), constructor.method); + writer.invokeConstructor( + Type.getType(constructor.javaConstructor.getDeclaringClass()), Method.getMethod(constructor.javaConstructor)); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java index a556b3ad315c6..0d8c94db0f1fc 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EStatic.java @@ -47,9 +47,9 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - try { - actual = locals.getPainlessLookup().getJavaClassFromPainlessType(type); - } catch (IllegalArgumentException exception) { + actual = locals.getPainlessLookup().canonicalTypeNameToType(type); + + if (actual == null) { throw createError(new IllegalArgumentException("Not a type [" + type + "].")); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java index 56bc18eadbd61..25ae1ed97742a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PCallInvoke.java @@ -23,8 +23,6 @@ import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessClass; -import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.lookup.def; @@ -32,6 +30,8 @@ import java.util.Objects; import java.util.Set; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; + /** * Represents a method call and defers to a child subnode. */ @@ -66,26 +66,18 @@ void analyze(Locals locals) { prefix.expected = prefix.actual; prefix = prefix.cast(locals); - if (prefix.actual.isArray()) { - throw createError(new IllegalArgumentException("Illegal call [" + name + "] on array type.")); - } - - PainlessClass struct = locals.getPainlessLookup().getPainlessStructFromJavaClass(prefix.actual); - - if (prefix.actual.isPrimitive()) { - struct = locals.getPainlessLookup().getPainlessStructFromJavaClass(PainlessLookupUtility.typeToBoxedType(prefix.actual)); - } + if (prefix.actual == def.class) { + sub = new PSubDefCall(location, name, arguments); + } else { + PainlessMethod method = + locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, prefix instanceof EStatic, name, arguments.size()); - String methodKey = PainlessLookupUtility.buildPainlessMethodKey(name, arguments.size()); - PainlessMethod method = prefix instanceof EStatic ? struct.staticMethods.get(methodKey) : struct.methods.get(methodKey); + if (method == null) { + throw createError(new IllegalArgumentException( + "method [" + typeToCanonicalTypeName(prefix.actual) + ", " + name + "/" + arguments.size() + "] not found")); + } - if (method != null) { sub = new PSubCallInvoke(location, method, prefix.actual, arguments); - } else if (prefix.actual == def.class) { - sub = new PSubDefCall(location, name, arguments); - } else { - throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments " + - "on type [" + PainlessLookupUtility.typeToCanonicalTypeName(prefix.actual) + "].")); } if (nullSafe) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java index b322d5b1f287f..7efd6a29899c4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PField.java @@ -23,7 +23,6 @@ import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessClass; import org.elasticsearch.painless.lookup.PainlessField; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; @@ -34,6 +33,8 @@ import java.util.Objects; import java.util.Set; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; + /** * Represents a field load/store and defers to a child subnode. */ @@ -67,26 +68,25 @@ void analyze(Locals locals) { } else if (prefix.actual == def.class) { sub = new PSubDefField(location, value); } else { - PainlessClass struct = locals.getPainlessLookup().getPainlessStructFromJavaClass(prefix.actual); - PainlessField field = prefix instanceof EStatic ? struct.staticFields.get(value) : struct.fields.get(value); + PainlessField field = locals.getPainlessLookup().lookupPainlessField(prefix.actual, prefix instanceof EStatic, value); - if (field != null) { - sub = new PSubField(location, field); - } else { - PainlessMethod getter = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey( - "get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0)); + if (field == null) { + PainlessMethod getter; + PainlessMethod setter; + + getter = locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, false, + "get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0); if (getter == null) { - getter = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey( - "is" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0)); + getter = locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, false, + "is" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0); } - PainlessMethod setter = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey( - "set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 1)); + setter = locals.getPainlessLookup().lookupPainlessMethod(prefix.actual, false, + "set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0); if (getter != null || setter != null) { - sub = new PSubShortcut( - location, value, PainlessLookupUtility.typeToCanonicalTypeName(prefix.actual), getter, setter); + sub = new PSubShortcut(location, value, PainlessLookupUtility.typeToCanonicalTypeName(prefix.actual), getter, setter); } else { EConstant index = new EConstant(location, value); index.analyze(locals); @@ -99,12 +99,14 @@ void analyze(Locals locals) { sub = new PSubListShortcut(location, prefix.actual, index); } } - } - } - if (sub == null) { - throw createError(new IllegalArgumentException( - "Unknown field [" + value + "] for type [" + PainlessLookupUtility.typeToCanonicalTypeName(prefix.actual) + "].")); + if (sub == null) { + throw createError(new IllegalArgumentException( + "field [" + typeToCanonicalTypeName(prefix.actual) + ", " + value + "] not found")); + } + } else { + sub = new PSubField(location, field); + } } if (nullSafe) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java index 237efa61ffa7d..fe2ae52603b57 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubCallInvoke.java @@ -56,14 +56,14 @@ void analyze(Locals locals) { for (int argument = 0; argument < arguments.size(); ++argument) { AExpression expression = arguments.get(argument); - expression.expected = method.arguments.get(argument); + expression.expected = method.typeParameters.get(argument); expression.internal = true; expression.analyze(locals); arguments.set(argument, expression.cast(locals)); } statement = true; - actual = method.rtn; + actual = method.returnType; } @Override @@ -78,11 +78,11 @@ void write(MethodWriter writer, Globals globals) { argument.write(writer, globals); } - method.write(writer); + writer.invokeMethodCall(method); } @Override public String toString() { - return singleLineToStringWithOptionalArgs(arguments, prefix, method.name); + return singleLineToStringWithOptionalArgs(arguments, prefix, method.javaMethod.getName()); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java index 007a599e9f842..9e09f810250f0 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubField.java @@ -51,22 +51,24 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - if (write && Modifier.isFinal(field.modifiers)) { - throw createError(new IllegalArgumentException("Cannot write to read-only field [" + field.name + "] for type " + - "[" + PainlessLookupUtility.typeToCanonicalTypeName(field.clazz) + "].")); + if (write && Modifier.isFinal(field.javaField.getModifiers())) { + throw createError(new IllegalArgumentException("Cannot write to read-only field [" + field.javaField.getName() + "] " + + "for type [" + PainlessLookupUtility.typeToCanonicalTypeName(field.javaField.getDeclaringClass()) + "].")); } - actual = field.clazz; + actual = field.typeParameter; } @Override void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isStatic(field.modifiers)) { - writer.getStatic(Type.getType(field.target), field.javaName, MethodWriter.getType(field.clazz)); + if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) { + writer.getStatic(Type.getType( + field.javaField.getDeclaringClass()), field.javaField.getName(), MethodWriter.getType(field.typeParameter)); } else { - writer.getField(Type.getType(field.target), field.javaName, MethodWriter.getType(field.clazz)); + writer.getField(Type.getType( + field.javaField.getDeclaringClass()), field.javaField.getName(), MethodWriter.getType(field.typeParameter)); } } @@ -94,10 +96,12 @@ void setup(MethodWriter writer, Globals globals) { void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isStatic(field.modifiers)) { - writer.getStatic(Type.getType(field.target), field.javaName, MethodWriter.getType(field.clazz)); + if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) { + writer.getStatic(Type.getType( + field.javaField.getDeclaringClass()), field.javaField.getName(), MethodWriter.getType(field.typeParameter)); } else { - writer.getField(Type.getType(field.target), field.javaName, MethodWriter.getType(field.clazz)); + writer.getField(Type.getType( + field.javaField.getDeclaringClass()), field.javaField.getName(), MethodWriter.getType(field.typeParameter)); } } @@ -105,15 +109,17 @@ void load(MethodWriter writer, Globals globals) { void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isStatic(field.modifiers)) { - writer.putStatic(Type.getType(field.target), field.javaName, MethodWriter.getType(field.clazz)); + if (java.lang.reflect.Modifier.isStatic(field.javaField.getModifiers())) { + writer.putStatic(Type.getType( + field.javaField.getDeclaringClass()), field.javaField.getName(), MethodWriter.getType(field.typeParameter)); } else { - writer.putField(Type.getType(field.target), field.javaName, MethodWriter.getType(field.clazz)); + writer.putField(Type.getType( + field.javaField.getDeclaringClass()), field.javaField.getName(), MethodWriter.getType(field.typeParameter)); } } @Override public String toString() { - return singleLineToString(prefix, field.name); + return singleLineToString(prefix, field.javaField.getName()); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java index 509aad6415349..3bc4913fde940 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubListShortcut.java @@ -24,7 +24,6 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.WriterConstants; -import org.elasticsearch.painless.lookup.PainlessClass; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; @@ -56,23 +55,22 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - PainlessClass struct = locals.getPainlessLookup().getPainlessStructFromJavaClass(targetClass); String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass); - getter = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey("get", 1)); - setter = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey("set", 2)); + getter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1); + setter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "set", 2); - if (getter != null && (getter.rtn == void.class || getter.arguments.size() != 1 || - getter.arguments.get(0) != int.class)) { + if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 || + getter.typeParameters.get(0) != int.class)) { throw createError(new IllegalArgumentException("Illegal list get shortcut for type [" + canonicalClassName + "].")); } - if (setter != null && (setter.arguments.size() != 2 || setter.arguments.get(0) != int.class)) { + if (setter != null && (setter.typeParameters.size() != 2 || setter.typeParameters.get(0) != int.class)) { throw createError(new IllegalArgumentException("Illegal list set shortcut for type [" + canonicalClassName + "].")); } - if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0)) - || !getter.rtn.equals(setter.arguments.get(1)))) { + if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0)) + || !getter.returnType.equals(setter.typeParameters.get(1)))) { throw createError(new IllegalArgumentException("Shortcut argument types must match.")); } @@ -81,7 +79,7 @@ void analyze(Locals locals) { index.analyze(locals); index = index.cast(locals); - actual = setter != null ? setter.arguments.get(1) : getter.rtn; + actual = setter != null ? setter.typeParameters.get(1) : getter.returnType; } else { throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + canonicalClassName + "].")); } @@ -119,21 +117,18 @@ void setup(MethodWriter writer, Globals globals) { @Override void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); + writer.invokeMethodCall(getter); - getter.write(writer); - - if (getter.rtn == getter.handle.type().returnType()) { - writer.checkCast(MethodWriter.getType(getter.rtn)); + if (getter.returnType == getter.javaMethod.getReturnType()) { + writer.checkCast(MethodWriter.getType(getter.returnType)); } } @Override void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - - setter.write(writer); - - writer.writePop(MethodWriter.getType(setter.rtn).getSize()); + writer.invokeMethodCall(setter); + writer.writePop(MethodWriter.getType(setter.returnType).getSize()); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java index 2d7f2250c6c38..0a0f099bd6841 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubMapShortcut.java @@ -23,7 +23,6 @@ import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.lookup.PainlessClass; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; @@ -55,31 +54,30 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - PainlessClass struct = locals.getPainlessLookup().getPainlessStructFromJavaClass(targetClass); String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass); - getter = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey("get", 1)); - setter = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey("put", 2)); + getter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "get", 1); + setter = locals.getPainlessLookup().lookupPainlessMethod(targetClass, false, "put", 2); - if (getter != null && (getter.rtn == void.class || getter.arguments.size() != 1)) { + if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1)) { throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + canonicalClassName + "].")); } - if (setter != null && setter.arguments.size() != 2) { + if (setter != null && setter.typeParameters.size() != 2) { throw createError(new IllegalArgumentException("Illegal map set shortcut for type [" + canonicalClassName + "].")); } - if (getter != null && setter != null && - (!getter.arguments.get(0).equals(setter.arguments.get(0)) || !getter.rtn.equals(setter.arguments.get(1)))) { + if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0)) || + !getter.returnType.equals(setter.typeParameters.get(1)))) { throw createError(new IllegalArgumentException("Shortcut argument types must match.")); } if ((read || write) && (!read || getter != null) && (!write || setter != null)) { - index.expected = setter != null ? setter.arguments.get(0) : getter.arguments.get(0); + index.expected = setter != null ? setter.typeParameters.get(0) : getter.typeParameters.get(0); index.analyze(locals); index = index.cast(locals); - actual = setter != null ? setter.arguments.get(1) : getter.rtn; + actual = setter != null ? setter.typeParameters.get(1) : getter.returnType; } else { throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + canonicalClassName + "].")); } @@ -90,11 +88,10 @@ void write(MethodWriter writer, Globals globals) { index.write(writer, globals); writer.writeDebugInfo(location); + writer.invokeMethodCall(getter); - getter.write(writer); - - if (getter.rtn != getter.handle.type().returnType()) { - writer.checkCast(MethodWriter.getType(getter.rtn)); + if (getter.returnType != getter.javaMethod.getReturnType()) { + writer.checkCast(MethodWriter.getType(getter.returnType)); } } @@ -121,21 +118,18 @@ void setup(MethodWriter writer, Globals globals) { @Override void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); + writer.invokeMethodCall(getter); - getter.write(writer); - - if (getter.rtn != getter.handle.type().returnType()) { - writer.checkCast(MethodWriter.getType(getter.rtn)); + if (getter.returnType != getter.javaMethod.getReturnType()) { + writer.checkCast(MethodWriter.getType(getter.returnType)); } } @Override void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - - setter.write(writer); - - writer.writePop(MethodWriter.getType(setter.rtn).getSize()); + writer.invokeMethodCall(setter); + writer.writePop(MethodWriter.getType(setter.returnType).getSize()); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java index eb5668c554c20..1697566047798 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/PSubShortcut.java @@ -53,22 +53,22 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - if (getter != null && (getter.rtn == void.class || !getter.arguments.isEmpty())) { + if (getter != null && (getter.returnType == void.class || !getter.typeParameters.isEmpty())) { throw createError(new IllegalArgumentException( "Illegal get shortcut on field [" + value + "] for type [" + type + "].")); } - if (setter != null && (setter.rtn != void.class || setter.arguments.size() != 1)) { + if (setter != null && (setter.returnType != void.class || setter.typeParameters.size() != 1)) { throw createError(new IllegalArgumentException( "Illegal set shortcut on field [" + value + "] for type [" + type + "].")); } - if (getter != null && setter != null && setter.arguments.get(0) != getter.rtn) { + if (getter != null && setter != null && setter.typeParameters.get(0) != getter.returnType) { throw createError(new IllegalArgumentException("Shortcut argument types must match.")); } if ((getter != null || setter != null) && (!read || getter != null) && (!write || setter != null)) { - actual = setter != null ? setter.arguments.get(0) : getter.rtn; + actual = setter != null ? setter.typeParameters.get(0) : getter.returnType; } else { throw createError(new IllegalArgumentException("Illegal shortcut on field [" + value + "] for type [" + type + "].")); } @@ -78,10 +78,10 @@ void analyze(Locals locals) { void write(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - getter.write(writer); + writer.invokeMethodCall(getter); - if (!getter.rtn.equals(getter.handle.type().returnType())) { - writer.checkCast(MethodWriter.getType(getter.rtn)); + if (!getter.returnType.equals(getter.javaMethod.getReturnType())) { + writer.checkCast(MethodWriter.getType(getter.returnType)); } } @@ -109,10 +109,10 @@ void setup(MethodWriter writer, Globals globals) { void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - getter.write(writer); + writer.invokeMethodCall(getter); - if (getter.rtn != getter.handle.type().returnType()) { - writer.checkCast(MethodWriter.getType(getter.rtn)); + if (getter.returnType != getter.javaMethod.getReturnType()) { + writer.checkCast(MethodWriter.getType(getter.returnType)); } } @@ -120,9 +120,9 @@ void load(MethodWriter writer, Globals globals) { void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - setter.write(writer); + writer.invokeMethodCall(setter); - writer.writePop(MethodWriter.getType(setter.rtn).getSize()); + writer.writePop(MethodWriter.getType(setter.returnType).getSize()); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java index 8a703c80cba2f..0c8ba5de6b2cf 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java @@ -64,11 +64,9 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - Class clazz; + Class clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type); - try { - clazz = locals.getPainlessLookup().getJavaClassFromPainlessType(this.type); - } catch (IllegalArgumentException exception) { + if (clazz == null) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java index fb92c20e89e01..7ead673c70b7a 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java @@ -59,11 +59,9 @@ void extractVariables(Set variables) { @Override void analyze(Locals locals) { - Class clazz; + Class clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type); - try { - clazz = locals.getPainlessLookup().getJavaClassFromPainlessType(this.type); - } catch (IllegalArgumentException exception) { + if (clazz == null) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java index 9ff57e6b913cc..cf41105c4fe36 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java @@ -68,11 +68,9 @@ void analyze(Locals locals) { expression.expected = expression.actual; expression = expression.cast(locals); - Class clazz; + Class clazz = locals.getPainlessLookup().canonicalTypeNameToType(this.type); - try { - clazz = locals.getPainlessLookup().getJavaClassFromPainlessType(this.type); - } catch (IllegalArgumentException exception) { + if (clazz == null) { throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index 7c243e296c7e3..6fe09627f9dfd 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -20,25 +20,19 @@ package org.elasticsearch.painless.node; import org.elasticsearch.painless.CompilerSettings; -import org.elasticsearch.painless.Constant; -import org.elasticsearch.painless.Def; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals.Parameter; import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; -import org.elasticsearch.painless.WriterConstants; import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupUtility; -import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.node.SSource.Reserved; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; import java.lang.invoke.MethodType; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -48,7 +42,6 @@ import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableSet; -import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE; /** * Represents a user-defined function. @@ -92,9 +85,12 @@ public int getMaxLoopCounter() { private final List statements; public final boolean synthetic; - Class rtnType = null; + Class returnType; + List> typeParameters; + MethodType methodType; + + org.objectweb.asm.commons.Method method; List parameters = new ArrayList<>(); - PainlessMethod method = null; private Variable loop = null; @@ -119,9 +115,9 @@ void extractVariables(Set variables) { } void generateSignature(PainlessLookup painlessLookup) { - try { - rtnType = painlessLookup.getJavaClassFromPainlessType(rtnTypeStr); - } catch (IllegalArgumentException exception) { + returnType = painlessLookup.canonicalTypeNameToType(rtnTypeStr); + + if (returnType == null) { throw createError(new IllegalArgumentException("Illegal return type [" + rtnTypeStr + "] for function [" + name + "].")); } @@ -133,21 +129,22 @@ void generateSignature(PainlessLookup painlessLookup) { List> paramTypes = new ArrayList<>(); for (int param = 0; param < this.paramTypeStrs.size(); ++param) { - try { - Class paramType = painlessLookup.getJavaClassFromPainlessType(this.paramTypeStrs.get(param)); + Class paramType = painlessLookup.canonicalTypeNameToType(this.paramTypeStrs.get(param)); - paramClasses[param] = PainlessLookupUtility.typeToJavaType(paramType); - paramTypes.add(paramType); - parameters.add(new Parameter(location, paramNameStrs.get(param), paramType)); - } catch (IllegalArgumentException exception) { + if (paramType == null) { throw createError(new IllegalArgumentException( "Illegal parameter type [" + this.paramTypeStrs.get(param) + "] for function [" + name + "].")); } + + paramClasses[param] = PainlessLookupUtility.typeToJavaType(paramType); + paramTypes.add(paramType); + parameters.add(new Parameter(location, paramNameStrs.get(param), paramType)); } - org.objectweb.asm.commons.Method method = new org.objectweb.asm.commons.Method(name, MethodType.methodType( - PainlessLookupUtility.typeToJavaType(rtnType), paramClasses).toMethodDescriptorString()); - this.method = new PainlessMethod(name, null, null, rtnType, paramTypes, method, Modifier.STATIC | Modifier.PRIVATE, null); + typeParameters = paramTypes; + methodType = MethodType.methodType(PainlessLookupUtility.typeToJavaType(returnType), paramClasses); + method = new org.objectweb.asm.commons.Method(name, MethodType.methodType( + PainlessLookupUtility.typeToJavaType(returnType), paramClasses).toMethodDescriptorString()); } @Override @@ -175,7 +172,7 @@ void analyze(Locals locals) { allEscape = statement.allEscape; } - if (!methodEscape && rtnType != void.class) { + if (!methodEscape && returnType != void.class) { throw createError(new IllegalArgumentException("Not all paths provide a return value for method [" + name + "].")); } @@ -190,7 +187,7 @@ void write (ClassVisitor writer, CompilerSettings settings, Globals globals) { if (synthetic) { access |= Opcodes.ACC_SYNTHETIC; } - final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements(), settings); + final MethodWriter function = new MethodWriter(access, method, writer, globals.getStatements(), settings); function.visitCode(); write(function, globals); function.endMethod(); @@ -210,25 +207,12 @@ void write(MethodWriter function, Globals globals) { } if (!methodEscape) { - if (rtnType == void.class) { + if (returnType == void.class) { function.returnValue(); } else { throw createError(new IllegalStateException("Illegal tree structure.")); } } - - String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size()); - globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE, - staticHandleFieldName, this::initializeConstant)); - } - - private void initializeConstant(MethodWriter writer) { - final Handle handle = new Handle(Opcodes.H_INVOKESTATIC, - CLASS_TYPE.getInternalName(), - name, - method.method.getDescriptor(), - false); - writer.push(handle); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java index c354e78a961a3..0f7445a38c44c 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java @@ -23,6 +23,7 @@ import org.elasticsearch.painless.Constant; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Locals; +import org.elasticsearch.painless.Locals.LocalMethod; import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.MethodWriter; @@ -30,8 +31,6 @@ import org.elasticsearch.painless.SimpleChecksAdapter; import org.elasticsearch.painless.WriterConstants; import org.elasticsearch.painless.lookup.PainlessLookup; -import org.elasticsearch.painless.lookup.PainlessLookupUtility; -import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.node.SFunction.FunctionReserved; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -70,6 +69,7 @@ import static org.elasticsearch.painless.WriterConstants.GET_NAME_METHOD; import static org.elasticsearch.painless.WriterConstants.GET_SOURCE_METHOD; import static org.elasticsearch.painless.WriterConstants.GET_STATEMENTS_METHOD; +import static org.elasticsearch.painless.WriterConstants.MAP_TYPE; import static org.elasticsearch.painless.WriterConstants.OUT_OF_MEMORY_ERROR_TYPE; import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE; import static org.elasticsearch.painless.WriterConstants.PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD; @@ -164,27 +164,31 @@ void extractVariables(Set variables) { throw new IllegalStateException("Illegal tree structure."); } - public void analyze(PainlessLookup painlessLookup) { - Map methods = new HashMap<>(); + public Map analyze(PainlessLookup painlessLookup) { + Map methods = new HashMap<>(); for (SFunction function : functions) { function.generateSignature(painlessLookup); - String key = PainlessLookupUtility.buildPainlessMethodKey(function.name, function.parameters.size()); + String key = Locals.buildLocalMethodKey(function.name, function.parameters.size()); - if (methods.put(key, function.method) != null) { + if (methods.put(key, + new LocalMethod(function.name, function.returnType, function.typeParameters, function.methodType)) != null) { throw createError(new IllegalArgumentException("Duplicate functions with name [" + function.name + "].")); } } - analyze(Locals.newProgramScope(painlessLookup, methods.values())); + Locals locals = Locals.newProgramScope(painlessLookup, methods.values()); + analyze(locals); + + return locals.getMethods(); } @Override void analyze(Locals program) { for (SFunction function : functions) { Locals functionLocals = - Locals.newFunctionScope(program, function.rtnType, function.parameters, function.reserved.getMaxLoopCounter()); + Locals.newFunctionScope(program, function.returnType, function.parameters, function.reserved.getMaxLoopCounter()); function.analyze(functionLocals); } @@ -253,6 +257,7 @@ public void write() { globals.getStatements(), settings); bootstrapDef.visitCode(); bootstrapDef.getStatic(CLASS_TYPE, "$DEFINITION", DEFINITION_TYPE); + bootstrapDef.getStatic(CLASS_TYPE, "$LOCALS", MAP_TYPE); bootstrapDef.loadArgs(); bootstrapDef.invokeStatic(DEF_BOOTSTRAP_DELEGATE_TYPE, DEF_BOOTSTRAP_DELEGATE_METHOD); bootstrapDef.returnValue(); @@ -263,8 +268,9 @@ public void write() { visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$SOURCE", STRING_TYPE.getDescriptor(), null, null).visitEnd(); visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$STATEMENTS", BITSET_TYPE.getDescriptor(), null, null).visitEnd(); - // Write the static variable used by the method to bootstrap def calls + // Write the static variables used by the method to bootstrap def calls visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$DEFINITION", DEFINITION_TYPE.getDescriptor(), null, null).visitEnd(); + visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$LOCALS", MAP_TYPE.getDescriptor(), null, null).visitEnd(); org.objectweb.asm.commons.Method init; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java index 12e3154eb562e..46dfa056874f2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java @@ -40,6 +40,7 @@ import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT; import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT; import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE; +import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; /** * Represents a for-each loop for iterables. @@ -76,12 +77,11 @@ void analyze(Locals locals) { if (expression.actual == def.class) { method = null; } else { - method = locals.getPainlessLookup().getPainlessStructFromJavaClass(expression.actual).methods - .get(PainlessLookupUtility.buildPainlessMethodKey("iterator", 0)); + method = locals.getPainlessLookup().lookupPainlessMethod(expression.actual, false, "iterator", 0); if (method == null) { - throw createError(new IllegalArgumentException("Unable to create iterator for the type " + - "[" + PainlessLookupUtility.typeToCanonicalTypeName(expression.actual) + "].")); + throw createError(new IllegalArgumentException( + "method [" + typeToCanonicalTypeName(expression.actual) + ", iterator/0] not found")); } } @@ -99,7 +99,7 @@ void write(MethodWriter writer, Globals globals) { .getMethodType(org.objectweb.asm.Type.getType(Iterator.class), org.objectweb.asm.Type.getType(Object.class)); writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR); } else { - method.write(writer); + writer.invokeMethodCall(method); } writer.visitVarInsn(MethodWriter.getType(iterator.clazz).getOpcode(Opcodes.ISTORE), iterator.getSlot()); diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt index 8491d15c27eb9..f424f6acf25aa 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt @@ -77,8 +77,8 @@ class org.elasticsearch.index.fielddata.ScriptDocValues$Longs { } class org.elasticsearch.index.fielddata.ScriptDocValues$Dates { - org.joda.time.ReadableDateTime get(int) - org.joda.time.ReadableDateTime getValue() + Object get(int) + Object getValue() List getValues() } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java index 34bc2c78de662..58864d73c4120 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java @@ -35,8 +35,8 @@ private static void assertCast(Class actual, Class expected, boolean mustB } PainlessCast cast = AnalyzerCaster.getLegalCast(location, actual, expected, true, false); - assertEquals(actual, cast.from); - assertEquals(expected, cast.to); + assertEquals(actual, cast.originalType); + assertEquals(expected, cast.targetType); if (mustBeExplicit) { ClassCastException error = expectThrows(ClassCastException.class, @@ -44,8 +44,8 @@ private static void assertCast(Class actual, Class expected, boolean mustB assertTrue(error.getMessage().startsWith("Cannot cast")); } else { cast = AnalyzerCaster.getLegalCast(location, actual, expected, false, false); - assertEquals(actual, cast.from); - assertEquals(expected, cast.to); + assertEquals(actual, cast.originalType); + assertEquals(expected, cast.targetType); } } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java index c852d5a41dec1..c68302bde56f2 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java @@ -69,7 +69,7 @@ public Map getTestMap() { } public void testGets() { - Compiler compiler = new Compiler(Gets.class, painlessLookup); + Compiler compiler = new Compiler(Gets.class, null, null, painlessLookup); Map map = new HashMap<>(); map.put("s", 1); @@ -87,7 +87,7 @@ public abstract static class NoArgs { public abstract Object execute(); } public void testNoArgs() { - Compiler compiler = new Compiler(NoArgs.class, painlessLookup); + Compiler compiler = new Compiler(NoArgs.class, null, null, painlessLookup); assertEquals(1, ((NoArgs)scriptEngine.compile(compiler, null, "1", emptyMap())).execute()); assertEquals("foo", ((NoArgs)scriptEngine.compile(compiler, null, "'foo'", emptyMap())).execute()); @@ -111,13 +111,13 @@ public abstract static class OneArg { public abstract Object execute(Object arg); } public void testOneArg() { - Compiler compiler = new Compiler(OneArg.class, painlessLookup); + Compiler compiler = new Compiler(OneArg.class, null, null, painlessLookup); Object rando = randomInt(); assertEquals(rando, ((OneArg)scriptEngine.compile(compiler, null, "arg", emptyMap())).execute(rando)); rando = randomAlphaOfLength(5); assertEquals(rando, ((OneArg)scriptEngine.compile(compiler, null, "arg", emptyMap())).execute(rando)); - Compiler noargs = new Compiler(NoArgs.class, painlessLookup); + Compiler noargs = new Compiler(NoArgs.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, () -> scriptEngine.compile(noargs, null, "doc", emptyMap())); assertEquals("Variable [doc] is not defined.", e.getMessage()); @@ -132,7 +132,7 @@ public abstract static class ArrayArg { public abstract Object execute(String[] arg); } public void testArrayArg() { - Compiler compiler = new Compiler(ArrayArg.class, painlessLookup); + Compiler compiler = new Compiler(ArrayArg.class, null, null, painlessLookup); String rando = randomAlphaOfLength(5); assertEquals(rando, ((ArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new String[] {rando, "foo"})); } @@ -142,7 +142,7 @@ public abstract static class PrimitiveArrayArg { public abstract Object execute(int[] arg); } public void testPrimitiveArrayArg() { - Compiler compiler = new Compiler(PrimitiveArrayArg.class, painlessLookup); + Compiler compiler = new Compiler(PrimitiveArrayArg.class, null, null, painlessLookup); int rando = randomInt(); assertEquals(rando, ((PrimitiveArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new int[] {rando, 10})); } @@ -152,7 +152,7 @@ public abstract static class DefArrayArg { public abstract Object execute(Object[] arg); } public void testDefArrayArg() { - Compiler compiler = new Compiler(DefArrayArg.class, painlessLookup); + Compiler compiler = new Compiler(DefArrayArg.class, null, null, painlessLookup); Object rando = randomInt(); assertEquals(rando, ((DefArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap())).execute(new Object[] {rando, 10})); rando = randomAlphaOfLength(5); @@ -170,7 +170,7 @@ public abstract static class ManyArgs { public abstract boolean needsD(); } public void testManyArgs() { - Compiler compiler = new Compiler(ManyArgs.class, painlessLookup); + Compiler compiler = new Compiler(ManyArgs.class, null, null, painlessLookup); int rando = randomInt(); assertEquals(rando, ((ManyArgs)scriptEngine.compile(compiler, null, "a", emptyMap())).execute(rando, 0, 0, 0)); assertEquals(10, ((ManyArgs)scriptEngine.compile(compiler, null, "a + b + c + d", emptyMap())).execute(1, 2, 3, 4)); @@ -198,7 +198,7 @@ public abstract static class VarargTest { public abstract Object execute(String... arg); } public void testVararg() { - Compiler compiler = new Compiler(VarargTest.class, painlessLookup); + Compiler compiler = new Compiler(VarargTest.class, null, null, painlessLookup); assertEquals("foo bar baz", ((VarargTest)scriptEngine.compile(compiler, null, "String.join(' ', Arrays.asList(arg))", emptyMap())) .execute("foo", "bar", "baz")); } @@ -214,7 +214,7 @@ public Object executeWithASingleOne(int a, int b, int c) { } } public void testDefaultMethods() { - Compiler compiler = new Compiler(DefaultMethods.class, painlessLookup); + Compiler compiler = new Compiler(DefaultMethods.class, null, null, painlessLookup); int rando = randomInt(); assertEquals(rando, ((DefaultMethods)scriptEngine.compile(compiler, null, "a", emptyMap())).execute(rando, 0, 0, 0)); assertEquals(rando, ((DefaultMethods)scriptEngine.compile(compiler, null, "a", emptyMap())).executeWithASingleOne(rando, 0, 0)); @@ -228,7 +228,7 @@ public abstract static class ReturnsVoid { public abstract void execute(Map map); } public void testReturnsVoid() { - Compiler compiler = new Compiler(ReturnsVoid.class, painlessLookup); + Compiler compiler = new Compiler(ReturnsVoid.class, null, null, painlessLookup); Map map = new HashMap<>(); ((ReturnsVoid)scriptEngine.compile(compiler, null, "map.a = 'foo'", emptyMap())).execute(map); assertEquals(singletonMap("a", "foo"), map); @@ -247,7 +247,7 @@ public abstract static class ReturnsPrimitiveBoolean { public abstract boolean execute(); } public void testReturnsPrimitiveBoolean() { - Compiler compiler = new Compiler(ReturnsPrimitiveBoolean.class, painlessLookup); + Compiler compiler = new Compiler(ReturnsPrimitiveBoolean.class, null, null, painlessLookup); assertEquals(true, ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "true", emptyMap())).execute()); assertEquals(false, ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "false", emptyMap())).execute()); @@ -289,7 +289,7 @@ public abstract static class ReturnsPrimitiveInt { public abstract int execute(); } public void testReturnsPrimitiveInt() { - Compiler compiler = new Compiler(ReturnsPrimitiveInt.class, painlessLookup); + Compiler compiler = new Compiler(ReturnsPrimitiveInt.class, null, null, painlessLookup); assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "1", emptyMap())).execute()); assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "(int) 1L", emptyMap())).execute()); @@ -331,7 +331,7 @@ public abstract static class ReturnsPrimitiveFloat { public abstract float execute(); } public void testReturnsPrimitiveFloat() { - Compiler compiler = new Compiler(ReturnsPrimitiveFloat.class, painlessLookup); + Compiler compiler = new Compiler(ReturnsPrimitiveFloat.class, null, null, painlessLookup); assertEquals(1.1f, ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "1.1f", emptyMap())).execute(), 0); assertEquals(1.1f, ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "(float) 1.1d", emptyMap())).execute(), 0); @@ -362,7 +362,7 @@ public abstract static class ReturnsPrimitiveDouble { public abstract double execute(); } public void testReturnsPrimitiveDouble() { - Compiler compiler = new Compiler(ReturnsPrimitiveDouble.class, painlessLookup); + Compiler compiler = new Compiler(ReturnsPrimitiveDouble.class, null, null, painlessLookup); assertEquals(1.0, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1", emptyMap())).execute(), 0); assertEquals(1.0, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1L", emptyMap())).execute(), 0); @@ -396,7 +396,7 @@ public abstract static class NoArgumentsConstant { public abstract Object execute(String foo); } public void testNoArgumentsConstant() { - Compiler compiler = new Compiler(NoArgumentsConstant.class, painlessLookup); + Compiler compiler = new Compiler(NoArgumentsConstant.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> scriptEngine.compile(compiler, null, "1", emptyMap())); assertThat(e.getMessage(), startsWith( @@ -409,7 +409,7 @@ public abstract static class WrongArgumentsConstant { public abstract Object execute(String foo); } public void testWrongArgumentsConstant() { - Compiler compiler = new Compiler(WrongArgumentsConstant.class, painlessLookup); + Compiler compiler = new Compiler(WrongArgumentsConstant.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> scriptEngine.compile(compiler, null, "1", emptyMap())); assertThat(e.getMessage(), startsWith( @@ -422,7 +422,7 @@ public abstract static class WrongLengthOfArgumentConstant { public abstract Object execute(String foo); } public void testWrongLengthOfArgumentConstant() { - Compiler compiler = new Compiler(WrongLengthOfArgumentConstant.class, painlessLookup); + Compiler compiler = new Compiler(WrongLengthOfArgumentConstant.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> scriptEngine.compile(compiler, null, "1", emptyMap())); assertThat(e.getMessage(), startsWith("[" + WrongLengthOfArgumentConstant.class.getName() + "#ARGUMENTS] has length [2] but [" @@ -434,7 +434,7 @@ public abstract static class UnknownArgType { public abstract Object execute(UnknownArgType foo); } public void testUnknownArgType() { - Compiler compiler = new Compiler(UnknownArgType.class, painlessLookup); + Compiler compiler = new Compiler(UnknownArgType.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> scriptEngine.compile(compiler, null, "1", emptyMap())); assertEquals("[foo] is of unknown type [" + UnknownArgType.class.getName() + ". Painless interfaces can only accept arguments " @@ -446,7 +446,7 @@ public abstract static class UnknownReturnType { public abstract UnknownReturnType execute(String foo); } public void testUnknownReturnType() { - Compiler compiler = new Compiler(UnknownReturnType.class, painlessLookup); + Compiler compiler = new Compiler(UnknownReturnType.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> scriptEngine.compile(compiler, null, "1", emptyMap())); assertEquals("Painless can only implement execute methods returning a whitelisted type but [" + UnknownReturnType.class.getName() @@ -458,7 +458,7 @@ public abstract static class UnknownArgTypeInArray { public abstract Object execute(UnknownArgTypeInArray[] foo); } public void testUnknownArgTypeInArray() { - Compiler compiler = new Compiler(UnknownArgTypeInArray.class, painlessLookup); + Compiler compiler = new Compiler(UnknownArgTypeInArray.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> scriptEngine.compile(compiler, null, "1", emptyMap())); assertEquals("[foo] is of unknown type [" + UnknownArgTypeInArray.class.getName() + ". Painless interfaces can only accept " @@ -470,7 +470,7 @@ public abstract static class TwoExecuteMethods { public abstract Object execute(boolean foo); } public void testTwoExecuteMethods() { - Compiler compiler = new Compiler(TwoExecuteMethods.class, painlessLookup); + Compiler compiler = new Compiler(TwoExecuteMethods.class, null, null, painlessLookup); Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> scriptEngine.compile(compiler, null, "null", emptyMap())); assertEquals("Painless can only implement interfaces that have a single method named [execute] but [" diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ContextExampleTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ContextExampleTests.java new file mode 100644 index 0000000000000..15eed75bcb8df --- /dev/null +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ContextExampleTests.java @@ -0,0 +1,311 @@ +/* + * 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; + +/** + * These tests run the Painless scripts used in the context docs against + * slightly modified data designed around unit tests rather than a fully- + * running Elasticsearch server. + */ +public class ContextExampleTests extends ScriptTestCase { + + // **** Docs Generator Code **** + + /* + + import java.io.FileWriter; + import java.io.IOException; + + public class Generator { + + public final static String[] theatres = new String[] {"Down Port", "Graye", "Skyline", "Courtyard"}; + public final static String[] plays = new String[] {"Driving", "Pick It Up", "Sway and Pull", "Harriot", + "The Busline", "Ants Underground", "Exploria", "Line and Single", "Shafted", "Sunnyside Down", + "Test Run", "Auntie Jo"}; + public final static String[] actors = new String[] {"James Holland", "Krissy Smith", "Joe Muir", "Ryan Earns", + "Joel Madigan", "Jessica Brown", "Baz Knight", "Jo Hangum", "Rachel Grass", "Phoebe Miller", "Sarah Notch", + "Brayden Green", "Joshua Iller", "Jon Hittle", "Rob Kettleman", "Laura Conrad", "Simon Hower", "Nora Blue", + "Mike Candlestick", "Jacey Bell"}; + + public static void writeSeat(FileWriter writer, int id, String theatre, String play, String[] actors, + String date, String time, int row, int number, double cost, boolean sold) throws IOException { + StringBuilder builder = new StringBuilder(); + builder.append("{ \"create\" : { \"_index\" : \"seats\", \"_type\" : \"seat\", \"_id\" : \""); + builder.append(id); + builder.append("\" } }\n"); + builder.append("{ \"theatre\" : \""); + builder.append(theatre); + builder.append("\", \"play\" : \""); + builder.append(play); + builder.append("\", \"actors\": [ \""); + for (String actor : actors) { + builder.append(actor); + if (actor.equals(actors[actors.length - 1]) == false) { + builder.append("\", \""); + } + } + builder.append("\" ], \"date\": \""); + builder.append(date); + builder.append("\", \"time\": \""); + builder.append(time); + builder.append("\", \"row\": "); + builder.append(row); + builder.append(", \"number\": "); + builder.append(number); + builder.append(", \"cost\": "); + builder.append(cost); + builder.append(", \"sold\": "); + builder.append(sold ? "true" : "false"); + builder.append(" }\n"); + writer.write(builder.toString()); + } + + public static void main(String args[]) throws IOException { + FileWriter writer = new FileWriter("/home/jdconrad/test/seats.json"); + int id = 0; + + for (int playCount = 0; playCount < 12; ++playCount) { + String play = plays[playCount]; + String theatre; + String[] actor; + int startMonth; + int endMonth; + String time; + + if (playCount == 0) { + theatre = theatres[0]; + actor = new String[] {actors[0], actors[1], actors[2], actors[3]}; + startMonth = 4; + endMonth = 5; + time = "3:00PM"; + } else if (playCount == 1) { + theatre = theatres[0]; + actor = new String[] {actors[4], actors[5], actors[6], actors[7], actors[8], actors[9]}; + startMonth = 4; + endMonth = 6; + time = "8:00PM"; + } else if (playCount == 2) { + theatre = theatres[0]; + actor = new String[] {actors[0], actors[1], actors[2], actors[3], + actors[4], actors[5], actors[6], actors[7]}; + startMonth = 6; + endMonth = 8; + time = "3:00 PM"; + } else if (playCount == 3) { + theatre = theatres[0]; + actor = new String[] {actors[9], actors[10], actors[11], actors[12], actors[13], actors[14], + actors[15], actors[16], actors[17], actors[18], actors[19]}; + startMonth = 7; + endMonth = 8; + time = "8:00PM"; + } else if (playCount == 4) { + theatre = theatres[0]; + actor = new String[] {actors[13], actors[14], actors[15], actors[17], actors[18], actors[19]}; + startMonth = 8; + endMonth = 10; + time = "3:00PM"; + } else if (playCount == 5) { + theatre = theatres[0]; + actor = new String[] {actors[8], actors[9], actors[10], actors[11], actors[12]}; + startMonth = 8; + endMonth = 10; + time = "8:00PM"; + } else if (playCount == 6) { + theatre = theatres[1]; + actor = new String[] {actors[10], actors[11], actors[12], actors[13], actors[14], actors[15], actors[16]}; + startMonth = 4; + endMonth = 5; + time = "11:00AM"; + } else if (playCount == 7) { + theatre = theatres[1]; + actor = new String[] {actors[17], actors[18]}; + startMonth = 6; + endMonth = 9; + time = "2:00PM"; + } else if (playCount == 8) { + theatre = theatres[1]; + actor = new String[] {actors[0], actors[1], actors[2], actors[3], actors[16]}; + startMonth = 10; + endMonth = 11; + time = "11:00AM"; + } else if (playCount == 9) { + theatre = theatres[2]; + actor = new String[] {actors[1], actors[2], actors[3], actors[17], actors[18], actors[19]}; + startMonth = 3; + endMonth = 6; + time = "4:00PM"; + } else if (playCount == 10) { + theatre = theatres[2]; + actor = new String[] {actors[2], actors[3], actors[4], actors[5]}; + startMonth = 7; + endMonth = 8; + time = "7:30PM"; + } else if (playCount == 11) { + theatre = theatres[2]; + actor = new String[] {actors[7], actors[13], actors[14], actors[15], actors[16], actors[17]}; + startMonth = 9; + endMonth = 12; + time = "5:40PM"; + } else { + throw new RuntimeException("too many plays"); + } + + int rows; + int number; + + if (playCount < 6) { + rows = 3; + number = 12; + } else if (playCount < 9) { + rows = 5; + number = 9; + } else if (playCount < 12) { + rows = 11; + number = 15; + } else { + throw new RuntimeException("too many seats"); + } + + for (int month = startMonth; month <= endMonth; ++month) { + for (int day = 1; day <= 14; ++day) { + for (int row = 1; row <= rows; ++row) { + for (int count = 1; count <= number; ++count) { + String date = "2018-" + month + "-" + day; + double cost = (25 - row) * 1.25; + + writeSeat(writer, ++id, theatre, play, actor, date, time, row, count, cost, false); + } + } + } + } + } + + writer.write("\n"); + writer.close(); + } + } + + */ + + // **** Initial Mappings **** + + /* + + curl -X PUT "localhost:9200/seats" -H 'Content-Type: application/json' -d' + { + "mappings": { + "seat": { + "properties": { + "theatre": { "type": "keyword" }, + "play": { "type": "text" }, + "actors": { "type": "text" }, + "row": { "type": "integer" }, + "number": { "type": "integer" }, + "cost": { "type": "double" }, + "sold": { "type": "boolean" }, + "datetime": { "type": "date" }, + "date": { "type": "keyword" }, + "time": { "type": "keyword" } + } + } + } + } + ' + + */ + + // Create Ingest to Modify Dates: + + /* + + curl -X PUT "localhost:9200/_ingest/pipeline/seats" -H 'Content-Type: application/json' -d' + { + "description": "update datetime for seats", + "processors": [ + { + "script": { + "source": "String[] split(String s, char d) { int count = 0; for (char c : s.toCharArray()) { if (c == d) { ++count; } } if (count == 0) { return new String[] {s}; } String[] r = new String[count + 1]; int i0 = 0, i1 = 0; count = 0; for (char c : s.toCharArray()) { if (c == d) { r[count++] = s.substring(i0, i1); i0 = i1 + 1; } ++i1; } r[count] = s.substring(i0, i1); return r; } String[] dateSplit = split(ctx.date, (char)\"-\"); String year = dateSplit[0].trim(); String month = dateSplit[1].trim(); if (month.length() == 1) { month = \"0\" + month; } String day = dateSplit[2].trim(); if (day.length() == 1) { day = \"0\" + day; } boolean pm = ctx.time.substring(ctx.time.length() - 2).equals(\"PM\"); String[] timeSplit = split(ctx.time.substring(0, ctx.time.length() - 2), (char)\":\"); int hours = Integer.parseInt(timeSplit[0].trim()); int minutes = Integer.parseInt(timeSplit[1].trim()); if (pm) { hours += 12; } String dts = year + \"-\" + month + \"-\" + day + \"T\" + (hours < 10 ? \"0\" + hours : \"\" + hours) + \":\" + (minutes < 10 ? \"0\" + minutes : \"\" + minutes) + \":00+08:00\"; ZonedDateTime dt = ZonedDateTime.parse(dts, DateTimeFormatter.ISO_OFFSET_DATE_TIME); ctx.datetime = dt.getLong(ChronoField.INSTANT_SECONDS)*1000L;" + } + } + ] + } + ' + + */ + + public void testIngestProcessorScript() { + assertEquals(1535785200000L, + exec("String[] split(String s, char d) {" + + " int count = 0;" + + " for (char c : s.toCharArray()) {" + + " if (c == d) {" + + " ++count;" + + " }" + + " }" + + " if (count == 0) {" + + " return new String[] {s};" + + " }" + + " String[] r = new String[count + 1];" + + " int i0 = 0, i1 = 0;" + + " count = 0;" + + " for (char c : s.toCharArray()) {" + + " if (c == d) {" + + " r[count++] = s.substring(i0, i1);" + + " i0 = i1 + 1;" + + " }" + + " ++i1;" + + " }" + + " r[count] = s.substring(i0, i1);" + + " return r;" + + "}" + + "def x = ['date': '2018-9-1', 'time': '3:00 PM'];" + + "String[] dateSplit = split(x.date, (char)'-');" + + "String year = dateSplit[0].trim();" + + "String month = dateSplit[1].trim();" + + "if (month.length() == 1) {" + + " month = '0' + month;" + + "}" + + "String day = dateSplit[2].trim();" + + "if (day.length() == 1) {" + + " day = '0' + day;" + + "}" + + "boolean pm = x.time.substring(x.time.length() - 2).equals('PM');" + + "String[] timeSplit = split(x.time.substring(0, x.time.length() - 2), (char)':');" + + "int hours = Integer.parseInt(timeSplit[0].trim());" + + "String minutes = timeSplit[1].trim();" + + "if (pm) {" + + " hours += 12;" + + "}" + + "String dts = year + '-' + month + '-' + day + " + + "'T' + (hours < 10 ? '0' + hours : '' + hours) + ':' + minutes + ':00+08:00';" + + "ZonedDateTime dt = ZonedDateTime.parse(dts, DateTimeFormatter.ISO_OFFSET_DATE_TIME);" + + "return dt.getLong(ChronoField.INSTANT_SECONDS) * 1000L" + ) + ); + } + + // Post Generated Data: + + /* + + curl -XPOST localhost:9200/seats/seat/_bulk?pipeline=seats -H "Content-Type: application/x-ndjson" --data-binary "@/home/jdconrad/test/seats.json" + + */ +} diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java index 48af3898e0952..ae33ebfb6e9c5 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java @@ -40,7 +40,7 @@ static String toString(Class iface, String source, CompilerSettings settings) PrintWriter outputWriter = new PrintWriter(output); Textifier textifier = new Textifier(); try { - new Compiler(iface, PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS)) + new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS)) .compile("", source, settings, textifier); } catch (RuntimeException e) { textifier.print(outputWriter); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java index 1ef855d561c52..88d257a067205 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java @@ -38,6 +38,7 @@ public class DefBootstrapTests extends ESTestCase { /** calls toString() on integers, twice */ public void testOneType() throws Throwable { CallSite site = DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "toString", MethodType.methodType(String.class, Object.class), @@ -58,6 +59,7 @@ public void testOneType() throws Throwable { public void testTwoTypes() throws Throwable { CallSite site = DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "toString", MethodType.methodType(String.class, Object.class), @@ -83,6 +85,7 @@ public void testTooManyTypes() throws Throwable { // if this changes, test must be rewritten assertEquals(5, DefBootstrap.PIC.MAX_DEPTH); CallSite site = DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "toString", MethodType.methodType(String.class, Object.class), @@ -109,6 +112,7 @@ public void testTooManyTypes() throws Throwable { /** test that we revert to the megamorphic classvalue cache and that it works as expected */ public void testMegamorphic() throws Throwable { DefBootstrap.PIC site = (DefBootstrap.PIC) DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "size", MethodType.methodType(int.class, Object.class), @@ -130,7 +134,7 @@ public void testMegamorphic() throws Throwable { final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> { Integer.toString((int)handle.invokeExact(new Object())); }); - assertEquals("Unable to find dynamic method [size] with [0] arguments for class [java.lang.Object].", iae.getMessage()); + assertEquals("dynamic method [java.lang.Object, size/0] not found", iae.getMessage()); assertTrue("Does not fail inside ClassValue.computeValue()", Arrays.stream(iae.getStackTrace()).anyMatch(e -> { return e.getMethodName().equals("computeValue") && e.getClassName().startsWith("org.elasticsearch.painless.DefBootstrap$PIC$"); @@ -141,6 +145,7 @@ public void testMegamorphic() throws Throwable { public void testNullGuardAdd() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, Object.class, Object.class), @@ -153,6 +158,7 @@ public void testNullGuardAdd() throws Throwable { public void testNullGuardAddWhenCached() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, Object.class, Object.class), @@ -166,6 +172,7 @@ public void testNullGuardAddWhenCached() throws Throwable { public void testNullGuardEq() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "eq", MethodType.methodType(boolean.class, Object.class, Object.class), @@ -179,6 +186,7 @@ public void testNullGuardEq() throws Throwable { public void testNullGuardEqWhenCached() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "eq", MethodType.methodType(boolean.class, Object.class, Object.class), @@ -197,6 +205,7 @@ public void testNullGuardEqWhenCached() throws Throwable { public void testNoNullGuardAdd() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, int.class, Object.class), @@ -211,6 +220,7 @@ public void testNoNullGuardAdd() throws Throwable { public void testNoNullGuardAddWhenCached() throws Throwable { DefBootstrap.MIC site = (DefBootstrap.MIC) DefBootstrap.bootstrap(painlessLookup, + Collections.emptyMap(), MethodHandles.publicLookup(), "add", MethodType.methodType(Object.class, int.class, Object.class), diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionRefTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionRefTests.java index fd47db6b83d41..5829593f52441 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionRefTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionRefTests.java @@ -27,7 +27,6 @@ import static java.util.Collections.singletonMap; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.startsWith; public class FunctionRefTests extends ScriptTestCase { @@ -193,14 +192,15 @@ public void testMethodMissing() { Exception e = expectScriptThrows(IllegalArgumentException.class, () -> { exec("List l = [2, 1]; l.sort(Integer::bogus); return l.get(0);"); }); - assertThat(e.getMessage(), startsWith("Unknown reference")); + assertThat(e.getMessage(), containsString("function reference [Integer::bogus/2] matching [java.util.Comparator")); } public void testQualifiedMethodMissing() { Exception e = expectScriptThrows(IllegalArgumentException.class, () -> { exec("List l = [2, 1]; l.sort(org.joda.time.ReadableDateTime::bogus); return l.get(0);", false); }); - assertThat(e.getMessage(), startsWith("Unknown reference")); + assertThat(e.getMessage(), + containsString("function reference [org.joda.time.ReadableDateTime::bogus/2] matching [java.util.Comparator")); } public void testClassMissing() { @@ -223,11 +223,12 @@ public void testNotFunctionalInterface() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("List l = new ArrayList(); l.add(2); l.add(1); l.add(Integer::bogus); return l.get(0);"); }); - assertThat(expected.getMessage(), containsString("Cannot convert function reference")); + assertThat(expected.getMessage(), + containsString("cannot convert function reference [Integer::bogus] to a non-functional interface [def]")); } public void testIncompatible() { - expectScriptThrows(BootstrapMethodError.class, () -> { + expectScriptThrows(ClassCastException.class, () -> { exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);"); }); } @@ -236,28 +237,32 @@ public void testWrongArity() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("Optional.empty().orElseGet(String::startsWith);"); }); - assertThat(expected.getMessage(), containsString("Unknown reference")); + assertThat(expected.getMessage(), + containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier")); } public void testWrongArityNotEnough() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);"); }); - assertTrue(expected.getMessage().contains("Unknown reference")); + assertThat(expected.getMessage(), containsString( + "function reference [String::isEmpty/2] matching [java.util.Comparator")); } public void testWrongArityDef() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);"); }); - assertThat(expected.getMessage(), containsString("Unknown reference")); + assertThat(expected.getMessage(), + containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier")); } public void testWrongArityNotEnoughDef() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);"); }); - assertThat(expected.getMessage(), containsString("Unknown reference")); + assertThat(expected.getMessage(), + containsString("function reference [String::isEmpty/2] matching [java.util.Comparator")); } public void testReturnVoid() { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/LambdaTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/LambdaTests.java index 20e257e574709..1f1a6f95b3608 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/LambdaTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/LambdaTests.java @@ -184,7 +184,7 @@ public void testWrongArityDef() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("def y = Optional.empty(); return y.orElseGet(x -> x);"); }); - assertTrue(expected.getMessage(), expected.getMessage().contains("Incorrect number of parameters")); + assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments")); } public void testWrongArityNotEnough() { @@ -200,7 +200,7 @@ public void testWrongArityNotEnoughDef() { exec("def l = new ArrayList(); l.add(1); l.add(1); " + "return l.stream().mapToInt(() -> 5).sum();"); }); - assertTrue(expected.getMessage().contains("Incorrect number of parameters")); + assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments")); } public void testLambdaInFunction() { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/OverloadTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/OverloadTests.java index fce827e686caa..52c28799fae34 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/OverloadTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/OverloadTests.java @@ -23,12 +23,12 @@ public class OverloadTests extends ScriptTestCase { public void testMethod() { - assertEquals(2, exec("return 'abc123abc'.indexOf('c');")); - assertEquals(8, exec("return 'abc123abc'.indexOf('c', 3);")); + //assertEquals(2, exec("return 'abc123abc'.indexOf('c');")); + //assertEquals(8, exec("return 'abc123abc'.indexOf('c', 3);")); IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("return 'abc123abc'.indexOf('c', 3, 'bogus');"); }); - assertTrue(expected.getMessage().contains("[indexOf] with [3] arguments")); + assertTrue(expected.getMessage().contains("[java.lang.String, indexOf/3]")); } public void testMethodDynamic() { @@ -37,7 +37,7 @@ public void testMethodDynamic() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("def x = 'abc123abc'; return x.indexOf('c', 3, 'bogus');"); }); - assertTrue(expected.getMessage().contains("dynamic method [indexOf]")); + assertTrue(expected.getMessage().contains("dynamic method [java.lang.String, indexOf/3] not found")); } public void testConstructor() { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java index ff0d423117564..1460d5f2359b6 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.painless.lookup.PainlessClass; +import org.elasticsearch.painless.lookup.PainlessConstructor; import org.elasticsearch.painless.lookup.PainlessField; import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupBuilder; @@ -44,9 +45,9 @@ import java.util.Map; import java.util.TreeMap; import java.util.function.Consumer; +import java.util.stream.Collectors; import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.toList; /** * Generates an API reference from the method and type whitelists in {@link PainlessLookup}. @@ -55,9 +56,10 @@ public class PainlessDocGenerator { private static final PainlessLookup PAINLESS_LOOKUP = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS); private static final Logger logger = ESLoggerFactory.getLogger(PainlessDocGenerator.class); - private static final Comparator FIELD_NAME = comparing(f -> f.name); - private static final Comparator METHOD_NAME = comparing(m -> m.name); - private static final Comparator NUMBER_OF_ARGS = comparing(m -> m.arguments.size()); + private static final Comparator FIELD_NAME = comparing(f -> f.javaField.getName()); + private static final Comparator METHOD_NAME = comparing(m -> m.javaMethod.getName()); + private static final Comparator METHOD_NUMBER_OF_PARAMS = comparing(m -> m.typeParameters.size()); + private static final Comparator CONSTRUCTOR_NUMBER_OF_PARAMS = comparing(m -> m.typeParameters.size()); public static void main(String[] args) throws IOException { Path apiRootPath = PathUtils.get(args[0]); @@ -72,9 +74,10 @@ public static void main(String[] args) throws IOException { Files.newOutputStream(indexPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), false, StandardCharsets.UTF_8.name())) { emitGeneratedWarning(indexStream); - List> classes = PAINLESS_LOOKUP.getStructs().stream().sorted(comparing(Class::getCanonicalName)).collect(toList()); + List> classes = PAINLESS_LOOKUP.getClasses().stream().sorted( + Comparator.comparing(Class::getCanonicalName)).collect(Collectors.toList()); for (Class clazz : classes) { - PainlessClass struct = PAINLESS_LOOKUP.getPainlessStructFromJavaClass(clazz); + PainlessClass struct = PAINLESS_LOOKUP.lookupPainlessClass(clazz); String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(clazz); if (clazz.isPrimitive()) { @@ -103,16 +106,19 @@ public static void main(String[] args) throws IOException { Consumer documentField = field -> PainlessDocGenerator.documentField(typeStream, field); Consumer documentMethod = method -> PainlessDocGenerator.documentMethod(typeStream, method); + Consumer documentConstructor = + constructor -> PainlessDocGenerator.documentConstructor(typeStream, constructor); struct.staticFields.values().stream().sorted(FIELD_NAME).forEach(documentField); struct.fields.values().stream().sorted(FIELD_NAME).forEach(documentField); - struct.staticMethods.values().stream().sorted(METHOD_NAME.thenComparing(NUMBER_OF_ARGS)).forEach(documentMethod); - struct.constructors.values().stream().sorted(NUMBER_OF_ARGS).forEach(documentMethod); + struct.staticMethods.values().stream().sorted( + METHOD_NAME.thenComparing(METHOD_NUMBER_OF_PARAMS)).forEach(documentMethod); + struct.constructors.values().stream().sorted(CONSTRUCTOR_NUMBER_OF_PARAMS).forEach(documentConstructor); Map> inherited = new TreeMap<>(); - struct.methods.values().stream().sorted(METHOD_NAME.thenComparing(NUMBER_OF_ARGS)).forEach(method -> { - if (method.target == clazz) { + struct.methods.values().stream().sorted(METHOD_NAME.thenComparing(METHOD_NUMBER_OF_PARAMS)).forEach(method -> { + if (method.targetClass == clazz) { documentMethod(typeStream, method); } else { - inherited.put(canonicalClassName, method.target); + inherited.put(canonicalClassName, method.targetClass); } }); @@ -142,17 +148,17 @@ private static void documentField(PrintStream stream, PainlessField field) { emitAnchor(stream, field); stream.print("]]"); - if (Modifier.isStatic(field.modifiers)) { + if (Modifier.isStatic(field.javaField.getModifiers())) { stream.print("static "); } - emitType(stream, field.clazz); + emitType(stream, field.typeParameter); stream.print(' '); String javadocRoot = javadocRoot(field); emitJavadocLink(stream, javadocRoot, field); stream.print('['); - stream.print(field.name); + stream.print(field.javaField.getName()); stream.print(']'); if (javadocRoot.equals("java8")) { @@ -164,6 +170,41 @@ private static void documentField(PrintStream stream, PainlessField field) { stream.println(); } + /** + * Document a constructor. + */ + private static void documentConstructor(PrintStream stream, PainlessConstructor constructor) { + stream.print("* ++[["); + emitAnchor(stream, constructor); + stream.print("]]"); + + String javadocRoot = javadocRoot(constructor.javaConstructor.getDeclaringClass()); + emitJavadocLink(stream, javadocRoot, constructor); + stream.print('['); + + stream.print(constructorName(constructor)); + + stream.print("]("); + boolean first = true; + for (Class arg : constructor.typeParameters) { + if (first) { + first = false; + } else { + stream.print(", "); + } + emitType(stream, arg); + } + stream.print(")++"); + + if (javadocRoot.equals("java8")) { + stream.print(" ("); + emitJavadocLink(stream, "java9", constructor); + stream.print("[java 9])"); + } + + stream.println(); + } + /** * Document a method. */ @@ -172,14 +213,12 @@ private static void documentMethod(PrintStream stream, PainlessMethod method) { emitAnchor(stream, method); stream.print("]]"); - if (null == method.augmentation && Modifier.isStatic(method.modifiers)) { + if (method.targetClass == method.javaMethod.getDeclaringClass() && Modifier.isStatic(method.javaMethod.getModifiers())) { stream.print("static "); } - if (false == method.name.equals("")) { - emitType(stream, method.rtn); - stream.print(' '); - } + emitType(stream, method.returnType); + stream.print(' '); String javadocRoot = javadocRoot(method); emitJavadocLink(stream, javadocRoot, method); @@ -189,7 +228,7 @@ private static void documentMethod(PrintStream stream, PainlessMethod method) { stream.print("]("); boolean first = true; - for (Class arg : method.arguments) { + for (Class arg : method.typeParameters) { if (first) { first = false; } else { @@ -216,28 +255,43 @@ private static void emitAnchor(PrintStream stream, Class clazz) { stream.print(PainlessLookupUtility.typeToCanonicalTypeName(clazz).replace('.', '-')); } + /** + * Anchor text for a {@link PainlessConstructor}. + */ + private static void emitAnchor(PrintStream stream, PainlessConstructor constructor) { + emitAnchor(stream, constructor.javaConstructor.getDeclaringClass()); + stream.print('-'); + stream.print(constructorName(constructor)); + stream.print('-'); + stream.print(constructor.typeParameters.size()); + } + /** * Anchor text for a {@link PainlessMethod}. */ private static void emitAnchor(PrintStream stream, PainlessMethod method) { - emitAnchor(stream, method.target); + emitAnchor(stream, method.targetClass); stream.print('-'); stream.print(methodName(method)); stream.print('-'); - stream.print(method.arguments.size()); + stream.print(method.typeParameters.size()); } /** * Anchor text for a {@link PainlessField}. */ private static void emitAnchor(PrintStream stream, PainlessField field) { - emitAnchor(stream, field.target); + emitAnchor(stream, field.javaField.getDeclaringClass()); stream.print('-'); - stream.print(field.name); + stream.print(field.javaField.getName()); + } + + private static String constructorName(PainlessConstructor constructor) { + return PainlessLookupUtility.typeToCanonicalTypeName(constructor.javaConstructor.getDeclaringClass()); } private static String methodName(PainlessMethod method) { - return method.name.equals("") ? PainlessLookupUtility.typeToCanonicalTypeName(method.target) : method.name; + return PainlessLookupUtility.typeToCanonicalTypeName(method.targetClass); } /** @@ -269,6 +323,34 @@ private static void emitStruct(PrintStream stream, Class clazz) { } } + /** + * Emit an external link to Javadoc for a {@link PainlessMethod}. + * + * @param root name of the root uri variable + */ + private static void emitJavadocLink(PrintStream stream, String root, PainlessConstructor constructor) { + stream.print("link:{"); + stream.print(root); + stream.print("-javadoc}/"); + stream.print(classUrlPath(constructor.javaConstructor.getDeclaringClass())); + stream.print(".html#"); + stream.print(constructorName(constructor)); + stream.print("%2D"); + boolean first = true; + for (Class clazz: constructor.typeParameters) { + if (first) { + first = false; + } else { + stream.print("%2D"); + } + stream.print(clazz.getName()); + if (clazz.isArray()) { + stream.print(":A"); + } + } + stream.print("%2D"); + } + /** * Emit an external link to Javadoc for a {@link PainlessMethod}. * @@ -278,16 +360,16 @@ private static void emitJavadocLink(PrintStream stream, String root, PainlessMet stream.print("link:{"); stream.print(root); stream.print("-javadoc}/"); - stream.print(classUrlPath(method.augmentation != null ? method.augmentation : method.target)); + stream.print(classUrlPath(method.javaMethod.getDeclaringClass())); stream.print(".html#"); stream.print(methodName(method)); stream.print("%2D"); boolean first = true; - if (method.augmentation != null) { + if (method.targetClass != method.javaMethod.getDeclaringClass()) { first = false; - stream.print(method.target.getName()); + stream.print(method.javaMethod.getDeclaringClass().getName()); } - for (Class clazz: method.arguments) { + for (Class clazz: method.typeParameters) { if (first) { first = false; } else { @@ -310,26 +392,26 @@ private static void emitJavadocLink(PrintStream stream, String root, PainlessFie stream.print("link:{"); stream.print(root); stream.print("-javadoc}/"); - stream.print(classUrlPath(field.target)); + stream.print(classUrlPath(field.javaField.getDeclaringClass())); stream.print(".html#"); - stream.print(field.javaName); + stream.print(field.javaField.getName()); } /** * Pick the javadoc root for a {@link PainlessMethod}. */ private static String javadocRoot(PainlessMethod method) { - if (method.augmentation != null) { + if (method.targetClass != method.javaMethod.getDeclaringClass()) { return "painless"; } - return javadocRoot(method.target); + return javadocRoot(method.targetClass); } /** * Pick the javadoc root for a {@link PainlessField}. */ private static String javadocRoot(PainlessField field) { - return javadocRoot(field.target); + return javadocRoot(field.javaField.getDeclaringClass()); } /** diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/RegexTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/RegexTests.java index 911a50468cc17..8143c39ce6f6b 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/RegexTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/RegexTests.java @@ -252,7 +252,7 @@ public void testCantUsePatternCompile() { IllegalArgumentException e = expectScriptThrows(IllegalArgumentException.class, () -> { exec("Pattern.compile('aa')"); }); - assertEquals("Unknown call [compile] with [1] arguments on type [java.util.regex.Pattern].", e.getMessage()); + assertTrue(e.getMessage().contains("[java.util.regex.Pattern, compile/1]")); } public void testBadRegexPattern() { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java index 8eeb25c9676c7..f2d93aa759d07 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java @@ -219,7 +219,7 @@ public void testIllegalDynamicMethod() { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { exec("def x = 'test'; return x.getClass().toString()"); }); - assertTrue(expected.getMessage().contains("Unable to find dynamic method")); + assertTrue(expected.getMessage().contains("dynamic method [java.lang.String, getClass/0] not found")); } public void testDynamicNPE() { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java index c64014d81a5de..12d57fab11d98 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java @@ -162,12 +162,12 @@ public void testECapturingFunctionRef() { public void testECast() { Location l = new Location(getTestName(), 0); AExpression child = new EConstant(l, "test"); - PainlessCast cast = PainlessCast.standard(String.class, Integer.class, true); + PainlessCast cast = PainlessCast.originalTypetoTargetType(String.class, Integer.class, true); assertEquals("(ECast java.lang.Integer (EConstant String 'test'))", new ECast(l, child, cast).toString()); l = new Location(getTestName(), 1); child = new EBinary(l, Operation.ADD, new EConstant(l, "test"), new EConstant(l, 12)); - cast = PainlessCast.standard(Integer.class, Boolean.class, true); + cast = PainlessCast.originalTypetoTargetType(Integer.class, Boolean.class, true); assertEquals("(ECast java.lang.Boolean (EBinary (EConstant String 'test') + (EConstant Integer 12)))", new ECast(l, child, cast).toString()); } @@ -404,7 +404,7 @@ public void testPSubBrace() { public void testPSubCallInvoke() { Location l = new Location(getTestName(), 0); - PainlessClass c = painlessLookup.getPainlessStructFromJavaClass(Integer.class); + PainlessClass c = painlessLookup.lookupPainlessClass(Integer.class); PainlessMethod m = c.methods.get(PainlessLookupUtility.buildPainlessMethodKey("toString", 0)); PSubCallInvoke node = new PSubCallInvoke(l, m, null, emptyList()); node.prefix = new EVariable(l, "a"); @@ -459,7 +459,7 @@ public void testPSubDefField() { public void testPSubField() { Location l = new Location(getTestName(), 0); - PainlessClass s = painlessLookup.getPainlessStructFromJavaClass(Boolean.class); + PainlessClass s = painlessLookup.lookupPainlessClass(Boolean.class); PainlessField f = s.staticFields.get("TRUE"); PSubField node = new PSubField(l, f); node.prefix = new EStatic(l, "Boolean"); @@ -497,7 +497,7 @@ public void testPSubMapShortcut() { public void testPSubShortcut() { Location l = new Location(getTestName(), 0); - PainlessClass s = painlessLookup.getPainlessStructFromJavaClass(FeatureTest.class); + PainlessClass s = painlessLookup.lookupPainlessClass(FeatureTest.class); PainlessMethod getter = s.methods.get(PainlessLookupUtility.buildPainlessMethodKey("getX", 0)); PainlessMethod setter = s.methods.get(PainlessLookupUtility.buildPainlessMethodKey("setX", 1)); PSubShortcut node = new PSubShortcut(l, "x", FeatureTest.class.getName(), getter, setter); diff --git a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/15_update.yml b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/15_update.yml index 20047e7d4825d..f2e1cb616b980 100644 --- a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/15_update.yml +++ b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/15_update.yml @@ -132,7 +132,7 @@ body: script: lang: painless - source: "for (def key : params.keySet()) { ctx._source[key] = params[key]}" + source: "ctx._source.ctx = ctx" params: { bar: 'xxx' } - match: { error.root_cause.0.type: "remote_transport_exception" } diff --git a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/20_scriptfield.yml b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/20_scriptfield.yml index 2914e8a916ec6..3be6601521e87 100644 --- a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/20_scriptfield.yml +++ b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/20_scriptfield.yml @@ -108,7 +108,7 @@ setup: script_fields: bar: script: - source: "doc.date.value.dayOfWeek" + source: "doc.date.value.dayOfWeek.value" - match: { hits.hits.0.fields.bar.0: 7} @@ -123,7 +123,7 @@ setup: source: > StringBuilder b = new StringBuilder(); for (def date : doc.dates) { - b.append(" ").append(date.getDayOfWeek()); + b.append(" ").append(date.getDayOfWeek().value); } return b.toString().trim() diff --git a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/50_script_doc_values.yml b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/50_script_doc_values.yml index 617b8df61b6bd..4c3c204d2d9ca 100644 --- a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/50_script_doc_values.yml +++ b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/50_script_doc_values.yml @@ -95,7 +95,7 @@ setup: field: script: source: "doc.date.get(0)" - - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12.000Z' } + - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12Z' } - do: search: @@ -104,7 +104,7 @@ setup: field: script: source: "doc.date.value" - - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12.000Z' } + - match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12Z' } --- "geo_point": diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java index f764364380fcf..546677a2be4f4 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java @@ -88,9 +88,9 @@ protected Collection> getPlugins() { } @Override - protected Settings indexSettings() { + protected Settings createTestIndexSettings() { return Settings.builder() - .put(super.indexSettings()) + .put(super.createTestIndexSettings()) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .build(); } diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java index 63d94f39b2231..6d6822007eee3 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java @@ -73,9 +73,9 @@ protected Collection> getPlugins() { } @Override - protected Settings indexSettings() { + protected Settings createTestIndexSettings() { return Settings.builder() - .put(super.indexSettings()) + .put(super.createTestIndexSettings()) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .build(); } diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java index 5a128f4d305fb..83441ef92d2b4 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java @@ -62,9 +62,9 @@ protected Collection> getPlugins() { } @Override - protected Settings indexSettings() { + protected Settings createTestIndexSettings() { return Settings.builder() - .put(super.indexSettings()) + .put(super.createTestIndexSettings()) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .build(); } diff --git a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java index e28fbead29abd..f18efe4585bc9 100644 --- a/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java +++ b/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java @@ -756,7 +756,7 @@ public BitSetProducer bitsetFilter(Query query) { @Override @SuppressWarnings("unchecked") public > IFD getForField(MappedFieldType fieldType) { - IndexFieldData.Builder builder = fieldType.fielddataBuilder(shardContext.getFullyQualifiedIndexName()); + IndexFieldData.Builder builder = fieldType.fielddataBuilder(shardContext.getFullyQualifiedIndex().getName()); IndexFieldDataCache cache = new IndexFieldDataCache.None(); CircuitBreakerService circuitBreaker = new NoneCircuitBreakerService(); return (IFD) builder.build(shardContext.getIndexSettings(), fieldType, cache, circuitBreaker, @@ -764,5 +764,4 @@ public > IFD getForField(MappedFieldType fieldType } }; } - } diff --git a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java index 5bd83b6c19a2e..731a27aa72c84 100644 --- a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java +++ b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollAction.java @@ -48,9 +48,9 @@ import org.elasticsearch.index.mapper.TypeFieldMapper; import org.elasticsearch.index.mapper.VersionFieldMapper; import org.elasticsearch.index.reindex.ScrollableHitSource.SearchFailure; -import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptService; +import org.elasticsearch.script.UpdateScript; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.threadpool.ThreadPool; @@ -746,7 +746,7 @@ public abstract static class ScriptApplier implements BiFunction params; - private ExecutableScript executable; + private UpdateScript executable; private Map context; public ScriptApplier(WorkerBulkByScrollTaskState taskWorker, @@ -766,7 +766,7 @@ public RequestWrapper apply(RequestWrapper request, ScrollableHitSource.Hi return request; } if (executable == null) { - ExecutableScript.Factory factory = scriptService.compile(script, ExecutableScript.UPDATE_CONTEXT); + UpdateScript.Factory factory = scriptService.compile(script, UpdateScript.CONTEXT); executable = factory.newInstance(params); } if (context == null) { @@ -787,8 +787,7 @@ public RequestWrapper apply(RequestWrapper request, ScrollableHitSource.Hi OpType oldOpType = OpType.INDEX; context.put("op", oldOpType.toString()); - executable.setNextVar("ctx", context); - executable.run(); + executable.execute(context); String newOp = (String) context.remove("op"); if (newOp == null) { diff --git a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportDeleteByQueryAction.java b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportDeleteByQueryAction.java index c1defe56adc6f..706f2c0b8f8f1 100644 --- a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportDeleteByQueryAction.java +++ b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportDeleteByQueryAction.java @@ -27,13 +27,13 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.script.ScriptService; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import java.util.function.Supplier; public class TransportDeleteByQueryAction extends HandledTransportAction { @@ -46,7 +46,7 @@ public class TransportDeleteByQueryAction extends HandledTransportAction) DeleteByQueryRequest::new); + (Writeable.Reader) DeleteByQueryRequest::new); this.threadPool = threadPool; this.client = client; this.scriptService = scriptService; diff --git a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportReindexAction.java b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportReindexAction.java index e54b5f50ae674..1d80c28b58568 100644 --- a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportReindexAction.java +++ b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportReindexAction.java @@ -39,6 +39,7 @@ import org.elasticsearch.action.bulk.BulkItemResponse.Failure; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.index.reindex.ScrollableHitSource.SearchFailure; import org.elasticsearch.action.index.IndexRequest; @@ -104,7 +105,7 @@ public class TransportReindexAction extends HandledTransportAction)ReindexRequest::new); this.threadPool = threadPool; this.clusterService = clusterService; this.scriptService = scriptService; diff --git a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportUpdateByQueryAction.java b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportUpdateByQueryAction.java index 34ae3fdd0c62f..00d14822ba091 100644 --- a/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportUpdateByQueryAction.java +++ b/modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportUpdateByQueryAction.java @@ -29,6 +29,7 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.VersionType; import org.elasticsearch.index.mapper.IdFieldMapper; @@ -43,7 +44,6 @@ import java.util.Map; import java.util.function.BiFunction; -import java.util.function.Supplier; public class TransportUpdateByQueryAction extends HandledTransportAction { @@ -56,7 +56,7 @@ public class TransportUpdateByQueryAction extends HandledTransportAction) UpdateByQueryRequest::new); + (Writeable.Reader) UpdateByQueryRequest::new); this.threadPool = threadPool; this.client = client; this.scriptService = scriptService; diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionScriptTestCase.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionScriptTestCase.java index 955c99568c7d2..94f375e9333ed 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionScriptTestCase.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/AbstractAsyncBulkByScrollActionScriptTestCase.java @@ -26,8 +26,10 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptService; +import org.elasticsearch.script.UpdateScript; import org.junit.Before; +import java.util.Collections; import java.util.Map; import java.util.function.Consumer; @@ -54,10 +56,16 @@ public void setupScriptService() { protected T applyScript(Consumer> scriptBody) { IndexRequest index = new IndexRequest("index", "type", "1").source(singletonMap("foo", "bar")); ScrollableHitSource.Hit doc = new ScrollableHitSource.BasicHit("test", "type", "id", 0); - ExecutableScript executableScript = new SimpleExecutableScript(scriptBody); - ExecutableScript.Factory factory = params -> executableScript; - when(scriptService.compile(any(), eq(ExecutableScript.CONTEXT))).thenReturn(factory); - when(scriptService.compile(any(), eq(ExecutableScript.UPDATE_CONTEXT))).thenReturn(factory); + UpdateScript updateScript = new UpdateScript(Collections.emptyMap()) { + @Override + public void execute(Map ctx) { + scriptBody.accept(ctx); + } + }; + UpdateScript.Factory factory = params -> updateScript; + ExecutableScript simpleExecutableScript = new SimpleExecutableScript(scriptBody); + when(scriptService.compile(any(), eq(ExecutableScript.CONTEXT))).thenReturn(params -> simpleExecutableScript); + when(scriptService.compile(any(), eq(UpdateScript.CONTEXT))).thenReturn(factory); AbstractAsyncBulkByScrollAction action = action(scriptService, request().setScript(mockScript(""))); RequestWrapper result = action.buildScriptApplier().apply(AbstractAsyncBulkByScrollAction.wrap(index), doc); return (result != null) ? (T) result.self() : null; diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/RoundTripTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/RoundTripTests.java index 2dc4b59e8d9f9..97809c9bc8dc3 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/RoundTripTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/RoundTripTests.java @@ -67,19 +67,17 @@ public void testReindexRequest() throws IOException { new RemoteInfo(randomAlphaOfLength(5), randomAlphaOfLength(5), port, null, query, username, password, headers, socketTimeout, connectTimeout)); } - ReindexRequest tripped = new ReindexRequest(); - roundTrip(reindex, tripped); + ReindexRequest tripped = new ReindexRequest(toInputByteStream(reindex)); assertRequestEquals(reindex, tripped); // Try slices=auto with a version that doesn't support it, which should fail reindex.setSlices(AbstractBulkByScrollRequest.AUTO_SLICES); - Exception e = expectThrows(IllegalArgumentException.class, () -> roundTrip(Version.V_6_0_0_alpha1, reindex, null)); + Exception e = expectThrows(IllegalArgumentException.class, () -> toInputByteStream(Version.V_6_0_0_alpha1, reindex)); assertEquals("Slices set as \"auto\" are not supported before version [6.1.0]. Found version [6.0.0-alpha1]", e.getMessage()); // Try regular slices with a version that doesn't support slices=auto, which should succeed - tripped = new ReindexRequest(); reindex.setSlices(between(1, Integer.MAX_VALUE)); - roundTrip(Version.V_6_0_0_alpha1, reindex, tripped); + tripped = new ReindexRequest(toInputByteStream(reindex)); assertRequestEquals(Version.V_6_0_0_alpha1, reindex, tripped); } @@ -89,20 +87,18 @@ public void testUpdateByQueryRequest() throws IOException { if (randomBoolean()) { update.setPipeline(randomAlphaOfLength(5)); } - UpdateByQueryRequest tripped = new UpdateByQueryRequest(); - roundTrip(update, tripped); + UpdateByQueryRequest tripped = new UpdateByQueryRequest(toInputByteStream(update)); assertRequestEquals(update, tripped); assertEquals(update.getPipeline(), tripped.getPipeline()); // Try slices=auto with a version that doesn't support it, which should fail update.setSlices(AbstractBulkByScrollRequest.AUTO_SLICES); - Exception e = expectThrows(IllegalArgumentException.class, () -> roundTrip(Version.V_6_0_0_alpha1, update, null)); + Exception e = expectThrows(IllegalArgumentException.class, () -> toInputByteStream(Version.V_6_0_0_alpha1, update)); assertEquals("Slices set as \"auto\" are not supported before version [6.1.0]. Found version [6.0.0-alpha1]", e.getMessage()); // Try regular slices with a version that doesn't support slices=auto, which should succeed - tripped = new UpdateByQueryRequest(); update.setSlices(between(1, Integer.MAX_VALUE)); - roundTrip(Version.V_6_0_0_alpha1, update, tripped); + tripped = new UpdateByQueryRequest(toInputByteStream(update)); assertRequestEquals(update, tripped); assertEquals(update.getPipeline(), tripped.getPipeline()); } @@ -110,19 +106,17 @@ public void testUpdateByQueryRequest() throws IOException { public void testDeleteByQueryRequest() throws IOException { DeleteByQueryRequest delete = new DeleteByQueryRequest(new SearchRequest()); randomRequest(delete); - DeleteByQueryRequest tripped = new DeleteByQueryRequest(); - roundTrip(delete, tripped); + DeleteByQueryRequest tripped = new DeleteByQueryRequest(toInputByteStream(delete)); assertRequestEquals(delete, tripped); // Try slices=auto with a version that doesn't support it, which should fail delete.setSlices(AbstractBulkByScrollRequest.AUTO_SLICES); - Exception e = expectThrows(IllegalArgumentException.class, () -> roundTrip(Version.V_6_0_0_alpha1, delete, null)); + Exception e = expectThrows(IllegalArgumentException.class, () -> toInputByteStream(Version.V_6_0_0_alpha1, delete)); assertEquals("Slices set as \"auto\" are not supported before version [6.1.0]. Found version [6.0.0-alpha1]", e.getMessage()); // Try regular slices with a version that doesn't support slices=auto, which should succeed - tripped = new DeleteByQueryRequest(); delete.setSlices(between(1, Integer.MAX_VALUE)); - roundTrip(Version.V_6_0_0_alpha1, delete, tripped); + tripped = new DeleteByQueryRequest(toInputByteStream(delete)); assertRequestEquals(delete, tripped); } @@ -198,23 +192,24 @@ public void testRethrottleRequest() throws IOException { request.setTaskId(new TaskId(randomAlphaOfLength(5), randomLong())); } RethrottleRequest tripped = new RethrottleRequest(); - roundTrip(request, tripped); + // We use readFrom here because Rethrottle does not support the Writeable.Reader interface + tripped.readFrom(toInputByteStream(request)); assertEquals(request.getRequestsPerSecond(), tripped.getRequestsPerSecond(), 0.00001); assertArrayEquals(request.getActions(), tripped.getActions()); assertEquals(request.getTaskId(), tripped.getTaskId()); } - private void roundTrip(Streamable example, Streamable empty) throws IOException { - roundTrip(Version.CURRENT, example, empty); + private StreamInput toInputByteStream(Streamable example) throws IOException { + return toInputByteStream(Version.CURRENT, example); } - private void roundTrip(Version version, Streamable example, Streamable empty) throws IOException { + private StreamInput toInputByteStream(Version version, Streamable example) throws IOException { BytesStreamOutput out = new BytesStreamOutput(); out.setVersion(version); example.writeTo(out); StreamInput in = out.bytes().streamInput(); in.setVersion(version); - empty.readFrom(in); + return in; } private Script randomScript() { diff --git a/modules/transport-netty4/build.gradle b/modules/transport-netty4/build.gradle index 5d4bcd7c10a84..12ce5ce7d4a8f 100644 --- a/modules/transport-netty4/build.gradle +++ b/modules/transport-netty4/build.gradle @@ -34,13 +34,13 @@ compileTestJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-tr dependencies { // network stack - compile "io.netty:netty-buffer:4.1.16.Final" - compile "io.netty:netty-codec:4.1.16.Final" - compile "io.netty:netty-codec-http:4.1.16.Final" - compile "io.netty:netty-common:4.1.16.Final" - compile "io.netty:netty-handler:4.1.16.Final" - compile "io.netty:netty-resolver:4.1.16.Final" - compile "io.netty:netty-transport:4.1.16.Final" + compile "io.netty:netty-buffer:4.1.28.Final" + compile "io.netty:netty-codec:4.1.28.Final" + compile "io.netty:netty-codec-http:4.1.28.Final" + compile "io.netty:netty-common:4.1.28.Final" + compile "io.netty:netty-handler:4.1.28.Final" + compile "io.netty:netty-resolver:4.1.28.Final" + compile "io.netty:netty-transport:4.1.28.Final" } dependencyLicenses { @@ -134,7 +134,6 @@ thirdPartyAudit.excludes = [ 'net.jpountz.xxhash.StreamingXXHash32', 'net.jpountz.xxhash.XXHashFactory', 'io.netty.internal.tcnative.CertificateRequestedCallback', - 'io.netty.internal.tcnative.CertificateRequestedCallback$KeyMaterial', 'io.netty.internal.tcnative.CertificateVerifier', 'io.netty.internal.tcnative.SessionTicketKey', 'io.netty.internal.tcnative.SniHostNameMatcher', @@ -161,6 +160,6 @@ thirdPartyAudit.excludes = [ 'org.conscrypt.AllocatedBuffer', 'org.conscrypt.BufferAllocator', - 'org.conscrypt.Conscrypt$Engines', + 'org.conscrypt.Conscrypt', 'org.conscrypt.HandshakeListener' ] diff --git a/modules/transport-netty4/licenses/netty-buffer-4.1.16.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-buffer-4.1.16.Final.jar.sha1 deleted file mode 100644 index c546222971985..0000000000000 --- a/modules/transport-netty4/licenses/netty-buffer-4.1.16.Final.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -63b5fa95c74785e16f2c30ce268bc222e35c8cb5 \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-buffer-4.1.28.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-buffer-4.1.28.Final.jar.sha1 new file mode 100644 index 0000000000000..f8a652d0dd1b4 --- /dev/null +++ b/modules/transport-netty4/licenses/netty-buffer-4.1.28.Final.jar.sha1 @@ -0,0 +1 @@ +d6c2d13492778009d33f60e05ed90bcb535d1fd1 \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-codec-4.1.16.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-codec-4.1.16.Final.jar.sha1 deleted file mode 100644 index 1e6c241ea0b17..0000000000000 --- a/modules/transport-netty4/licenses/netty-codec-4.1.16.Final.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d84a1f21768b7309c2954521cf5a1f46c2309eb1 \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-codec-4.1.28.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-codec-4.1.28.Final.jar.sha1 new file mode 100644 index 0000000000000..70799bf10328a --- /dev/null +++ b/modules/transport-netty4/licenses/netty-codec-4.1.28.Final.jar.sha1 @@ -0,0 +1 @@ +a38361d893900947524f8a9da980555950e73d6a \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-codec-http-4.1.16.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-codec-http-4.1.16.Final.jar.sha1 deleted file mode 100644 index 71c33af1c5fc2..0000000000000 --- a/modules/transport-netty4/licenses/netty-codec-http-4.1.16.Final.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d64312378b438dfdad84267c599a053327c6f02a \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-codec-http-4.1.28.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-codec-http-4.1.28.Final.jar.sha1 new file mode 100644 index 0000000000000..e1d34ebf89bb8 --- /dev/null +++ b/modules/transport-netty4/licenses/netty-codec-http-4.1.28.Final.jar.sha1 @@ -0,0 +1 @@ +897100c1022c780b0a436b9349e507e8fa9800dc \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-common-4.1.16.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-common-4.1.16.Final.jar.sha1 deleted file mode 100644 index 3edf5fcea59b3..0000000000000 --- a/modules/transport-netty4/licenses/netty-common-4.1.16.Final.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -177a6b30cca92f6f5f9873c9befd681377a4c328 \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-common-4.1.28.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-common-4.1.28.Final.jar.sha1 new file mode 100644 index 0000000000000..bc95142683242 --- /dev/null +++ b/modules/transport-netty4/licenses/netty-common-4.1.28.Final.jar.sha1 @@ -0,0 +1 @@ +df69ce8bb9b544a71e7bbee290253cf7c93e6bad \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-handler-4.1.16.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-handler-4.1.16.Final.jar.sha1 deleted file mode 100644 index cba27387268d1..0000000000000 --- a/modules/transport-netty4/licenses/netty-handler-4.1.16.Final.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -fec0e63e7dd7f4eeef7ea8dc47a1ff32dfc7ebc2 \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-handler-4.1.28.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-handler-4.1.28.Final.jar.sha1 new file mode 100644 index 0000000000000..80dc8b8f6fe95 --- /dev/null +++ b/modules/transport-netty4/licenses/netty-handler-4.1.28.Final.jar.sha1 @@ -0,0 +1 @@ +a035784682da0126bc25f10713dac732b5082a6d \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-resolver-4.1.16.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-resolver-4.1.16.Final.jar.sha1 deleted file mode 100644 index 3571d2ecfdc48..0000000000000 --- a/modules/transport-netty4/licenses/netty-resolver-4.1.16.Final.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -f6eb553b53fb3a90a8ac1170697093fed82eae28 \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-resolver-4.1.28.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-resolver-4.1.28.Final.jar.sha1 new file mode 100644 index 0000000000000..afe004bd71623 --- /dev/null +++ b/modules/transport-netty4/licenses/netty-resolver-4.1.28.Final.jar.sha1 @@ -0,0 +1 @@ +f33557dcb31fa20da075ac05e4808115e32ef9b7 \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-transport-4.1.16.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-transport-4.1.16.Final.jar.sha1 deleted file mode 100644 index e502d4c77084c..0000000000000 --- a/modules/transport-netty4/licenses/netty-transport-4.1.16.Final.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -3c8ee2c4d4a1cbb947a5c184c7aeb2204260958b \ No newline at end of file diff --git a/modules/transport-netty4/licenses/netty-transport-4.1.28.Final.jar.sha1 b/modules/transport-netty4/licenses/netty-transport-4.1.28.Final.jar.sha1 new file mode 100644 index 0000000000000..af19a16d6ed64 --- /dev/null +++ b/modules/transport-netty4/licenses/netty-transport-4.1.28.Final.jar.sha1 @@ -0,0 +1 @@ +d2ef28f49d726737f0ffe84bf66529b3bf6e0c0d \ No newline at end of file diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java index c8c6fceb54304..90138bc59906e 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java @@ -267,8 +267,12 @@ protected Netty4TcpServerChannel bind(String name, InetSocketAddress address) { return esChannel; } - ScheduledPing getPing() { - return scheduledPing; + long successfulPingCount() { + return successfulPings.count(); + } + + long failedPingCount() { + return failedPings.count(); } @Override diff --git a/modules/transport-netty4/src/main/plugin-metadata/plugin-security.policy b/modules/transport-netty4/src/main/plugin-metadata/plugin-security.policy index 32b2dc9bd1540..b8099025e47bf 100644 --- a/modules/transport-netty4/src/main/plugin-metadata/plugin-security.policy +++ b/modules/transport-netty4/src/main/plugin-metadata/plugin-security.policy @@ -23,6 +23,9 @@ grant codeBase "${codebase.netty-common}" { // netty makes and accepts socket connections permission java.net.SocketPermission "*", "accept,connect"; + + // Netty sets custom classloader for some of its internal threads + permission java.lang.RuntimePermission "*", "setContextClassLoader"; }; grant codeBase "${codebase.netty-transport}" { diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java index 094f339059876..a005ae1054662 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.http.netty4; import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.util.ReferenceCounted; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Settings; @@ -92,15 +93,19 @@ public void dispatchBadRequest(RestRequest request, RestChannel channel, ThreadC try (Netty4HttpClient nettyHttpClient = new Netty4HttpClient()) { final Collection responses = nettyHttpClient.get(transportAddress.address(), "/_cluster/settings?pretty=%"); - assertThat(responses, hasSize(1)); - assertThat(responses.iterator().next().status().code(), equalTo(400)); - final Collection responseBodies = Netty4HttpClient.returnHttpResponseBodies(responses); - assertThat(responseBodies, hasSize(1)); - assertThat(responseBodies.iterator().next(), containsString("\"type\":\"bad_parameter_exception\"")); - assertThat( + try { + assertThat(responses, hasSize(1)); + assertThat(responses.iterator().next().status().code(), equalTo(400)); + final Collection responseBodies = Netty4HttpClient.returnHttpResponseBodies(responses); + assertThat(responseBodies, hasSize(1)); + assertThat(responseBodies.iterator().next(), containsString("\"type\":\"bad_parameter_exception\"")); + assertThat( responseBodies.iterator().next(), containsString( - "\"reason\":\"java.lang.IllegalArgumentException: unterminated escape sequence at end of string: %\"")); + "\"reason\":\"java.lang.IllegalArgumentException: unterminated escape sequence at end of string: %\"")); + } finally { + responses.forEach(ReferenceCounted::release); + } } } } diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpClient.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpClient.java index 0fa331ba138f6..a595de3a47ed9 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpClient.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpClient.java @@ -58,6 +58,7 @@ import static io.netty.handler.codec.http.HttpHeaderNames.HOST; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.fail; /** * Tiny helper to send http requests over netty. @@ -145,7 +146,9 @@ private synchronized Collection sendRequests( for (HttpRequest request : requests) { channelFuture.channel().writeAndFlush(request); } - latch.await(30, TimeUnit.SECONDS); + if (latch.await(30L, TimeUnit.SECONDS) == false) { + fail("Failed to get all expected responses."); + } } finally { if (channelFuture != null) { diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpRequestSizeLimitIT.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpRequestSizeLimitIT.java index 269773fbb634c..ad81f5b3063f5 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpRequestSizeLimitIT.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpRequestSizeLimitIT.java @@ -20,6 +20,7 @@ package org.elasticsearch.http.netty4; import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.util.ReferenceCounted; import org.elasticsearch.ESNetty4IntegTestCase; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; @@ -88,12 +89,20 @@ public void testLimitsInFlightRequests() throws Exception { try (Netty4HttpClient nettyHttpClient = new Netty4HttpClient()) { Collection singleResponse = nettyHttpClient.post(transportAddress.address(), requests[0]); - assertThat(singleResponse, hasSize(1)); - assertAtLeastOnceExpectedStatus(singleResponse, HttpResponseStatus.OK); - - Collection multipleResponses = nettyHttpClient.post(transportAddress.address(), requests); - assertThat(multipleResponses, hasSize(requests.length)); - assertAtLeastOnceExpectedStatus(multipleResponses, HttpResponseStatus.SERVICE_UNAVAILABLE); + try { + assertThat(singleResponse, hasSize(1)); + assertAtLeastOnceExpectedStatus(singleResponse, HttpResponseStatus.OK); + + Collection multipleResponses = nettyHttpClient.post(transportAddress.address(), requests); + try { + assertThat(multipleResponses, hasSize(requests.length)); + assertAtLeastOnceExpectedStatus(multipleResponses, HttpResponseStatus.SERVICE_UNAVAILABLE); + } finally { + multipleResponses.forEach(ReferenceCounted::release); + } + } finally { + singleResponse.forEach(ReferenceCounted::release); + } } } @@ -113,8 +122,12 @@ public void testDoesNotLimitExcludedRequests() throws Exception { try (Netty4HttpClient nettyHttpClient = new Netty4HttpClient()) { Collection responses = nettyHttpClient.put(transportAddress.address(), requestUris); - assertThat(responses, hasSize(requestUris.length)); - assertAllInExpectedStatus(responses, HttpResponseStatus.OK); + try { + assertThat(responses, hasSize(requestUris.length)); + assertAllInExpectedStatus(responses, HttpResponseStatus.OK); + } finally { + responses.forEach(ReferenceCounted::release); + } } } diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java index 50be7f7ce4509..cb4d14038d24a 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java @@ -29,6 +29,7 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.util.ReferenceCounted; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Settings; @@ -98,8 +99,12 @@ public void testThatHttpPipeliningWorks() throws Exception { try (Netty4HttpClient nettyHttpClient = new Netty4HttpClient()) { Collection responses = nettyHttpClient.get(transportAddress.address(), requests.toArray(new String[]{})); - Collection responseBodies = Netty4HttpClient.returnHttpResponseBodies(responses); - assertThat(responseBodies, contains(requests.toArray())); + try { + Collection responseBodies = Netty4HttpClient.returnHttpResponseBodies(responses); + assertThat(responseBodies, contains(requests.toArray())); + } finally { + responses.forEach(ReferenceCounted::release); + } } } } diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java index bcf28506143bf..1c3c71d710d17 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java @@ -207,14 +207,23 @@ public void dispatchBadRequest(RestRequest request, RestChannel channel, ThreadC HttpUtil.setContentLength(request, contentLength); final FullHttpResponse response = client.post(remoteAddress.address(), request); - assertThat(response.status(), equalTo(expectedStatus)); - if (expectedStatus.equals(HttpResponseStatus.CONTINUE)) { - final FullHttpRequest continuationRequest = - new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/", Unpooled.EMPTY_BUFFER); - final FullHttpResponse continuationResponse = client.post(remoteAddress.address(), continuationRequest); - - assertThat(continuationResponse.status(), is(HttpResponseStatus.OK)); - assertThat(new String(ByteBufUtil.getBytes(continuationResponse.content()), StandardCharsets.UTF_8), is("done")); + try { + assertThat(response.status(), equalTo(expectedStatus)); + if (expectedStatus.equals(HttpResponseStatus.CONTINUE)) { + final FullHttpRequest continuationRequest = + new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/", Unpooled.EMPTY_BUFFER); + final FullHttpResponse continuationResponse = client.post(remoteAddress.address(), continuationRequest); + try { + assertThat(continuationResponse.status(), is(HttpResponseStatus.OK)); + assertThat( + new String(ByteBufUtil.getBytes(continuationResponse.content()), StandardCharsets.UTF_8), is("done") + ); + } finally { + continuationResponse.release(); + } + } + } finally { + response.release(); } } } @@ -280,10 +289,14 @@ public void dispatchBadRequest(final RestRequest request, final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url); final FullHttpResponse response = client.post(remoteAddress.address(), request); - assertThat(response.status(), equalTo(HttpResponseStatus.BAD_REQUEST)); - assertThat( + try { + assertThat(response.status(), equalTo(HttpResponseStatus.BAD_REQUEST)); + assertThat( new String(response.content().array(), Charset.forName("UTF-8")), containsString("you sent a bad request and you should feel bad")); + } finally { + response.release(); + } } } diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4PipeliningIT.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4PipeliningIT.java index ebb91d9663ed5..275442cd50aa0 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4PipeliningIT.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4PipeliningIT.java @@ -20,6 +20,7 @@ package org.elasticsearch.http.netty4; import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.util.ReferenceCounted; import org.elasticsearch.ESNetty4IntegTestCase; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; @@ -45,14 +46,18 @@ public void testThatNettyHttpServerSupportsPipelining() throws Exception { HttpServerTransport httpServerTransport = internalCluster().getInstance(HttpServerTransport.class); TransportAddress[] boundAddresses = httpServerTransport.boundAddress().boundAddresses(); - TransportAddress transportAddress = (TransportAddress) randomFrom(boundAddresses); + TransportAddress transportAddress = randomFrom(boundAddresses); try (Netty4HttpClient nettyHttpClient = new Netty4HttpClient()) { Collection responses = nettyHttpClient.get(transportAddress.address(), requests); - assertThat(responses, hasSize(5)); + try { + assertThat(responses, hasSize(5)); - Collection opaqueIds = Netty4HttpClient.returnOpaqueIds(responses); - assertOpaqueIdsInOrder(opaqueIds); + Collection opaqueIds = Netty4HttpClient.returnOpaqueIds(responses); + assertOpaqueIdsInOrder(opaqueIds); + } finally { + responses.forEach(ReferenceCounted::release); + } } } diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java index 01c5f5b617077..fae4082e81828 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4ScheduledPingTests.java @@ -26,16 +26,13 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; -import org.elasticsearch.tasks.Task; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TcpTransport; -import org.elasticsearch.transport.TransportChannel; import org.elasticsearch.transport.TransportException; import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.transport.TransportRequestHandler; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportResponseHandler; @@ -83,22 +80,19 @@ public void testScheduledPing() throws Exception { serviceB.connectToNode(nodeA); assertBusy(() -> { - assertThat(nettyA.getPing().getSuccessfulPings(), greaterThan(100L)); - assertThat(nettyB.getPing().getSuccessfulPings(), greaterThan(100L)); + assertThat(nettyA.successfulPingCount(), greaterThan(100L)); + assertThat(nettyB.successfulPingCount(), greaterThan(100L)); }); - assertThat(nettyA.getPing().getFailedPings(), equalTo(0L)); - assertThat(nettyB.getPing().getFailedPings(), equalTo(0L)); + assertThat(nettyA.failedPingCount(), equalTo(0L)); + assertThat(nettyB.failedPingCount(), equalTo(0L)); serviceA.registerRequestHandler("internal:sayHello", TransportRequest.Empty::new, ThreadPool.Names.GENERIC, - new TransportRequestHandler() { - @Override - public void messageReceived(TransportRequest.Empty request, TransportChannel channel, Task task) { - try { - channel.sendResponse(TransportResponse.Empty.INSTANCE, TransportResponseOptions.EMPTY); - } catch (IOException e) { - logger.error("Unexpected failure", e); - fail(e.getMessage()); - } + (request, channel, task) -> { + try { + channel.sendResponse(TransportResponse.Empty.INSTANCE, TransportResponseOptions.EMPTY); + } catch (IOException e) { + logger.error("Unexpected failure", e); + fail(e.getMessage()); } }); @@ -130,11 +124,11 @@ public void handleException(TransportException exp) { } assertBusy(() -> { - assertThat(nettyA.getPing().getSuccessfulPings(), greaterThan(200L)); - assertThat(nettyB.getPing().getSuccessfulPings(), greaterThan(200L)); + assertThat(nettyA.successfulPingCount(), greaterThan(200L)); + assertThat(nettyB.successfulPingCount(), greaterThan(200L)); }); - assertThat(nettyA.getPing().getFailedPings(), equalTo(0L)); - assertThat(nettyB.getPing().getFailedPings(), equalTo(0L)); + assertThat(nettyA.failedPingCount(), equalTo(0L)); + assertThat(nettyB.failedPingCount(), equalTo(0L)); Releasables.close(serviceA, serviceB); terminate(threadPool); diff --git a/plugins/analysis-icu/build.gradle b/plugins/analysis-icu/build.gradle index ad5a7b7c57b61..1883e3bf1b9d6 100644 --- a/plugins/analysis-icu/build.gradle +++ b/plugins/analysis-icu/build.gradle @@ -30,9 +30,9 @@ forbiddenApis { dependencies { compile "org.apache.lucene:lucene-analyzers-icu:${versions.lucene}" - compile 'com.ibm.icu:icu4j:62.1' + compile "com.ibm.icu:icu4j:${versions.icu4j}" } dependencyLicenses { mapping from: /lucene-.*/, to: 'lucene' -} \ No newline at end of file +} diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..1e79e1e70ef8f --- /dev/null +++ b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +a010e852be8d56efe1906e6da5292e4541239724 \ No newline at end of file diff --git a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.5.0-snapshot-608f0277b0.jar.sha1 b/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index 5b6947a9c7578..0000000000000 --- a/plugins/analysis-icu/licenses/lucene-analyzers-icu-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -7a37816def72a748416c4ae8b0f6817e30efb99f \ No newline at end of file diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..2d9669e436229 --- /dev/null +++ b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +88e0ed90d433a9088528485cd4f59311735d92a4 \ No newline at end of file diff --git a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.5.0-snapshot-608f0277b0.jar.sha1 b/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index d39638c188466..0000000000000 --- a/plugins/analysis-kuromoji/licenses/lucene-analyzers-kuromoji-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -ca7437178cdbf7b8bfe0d75c75e3c8eb93925724 \ No newline at end of file diff --git a/plugins/analysis-nori/licenses/lucene-analyzers-nori-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/plugins/analysis-nori/licenses/lucene-analyzers-nori-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..f7b8fdd4bc187 --- /dev/null +++ b/plugins/analysis-nori/licenses/lucene-analyzers-nori-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +0daec9ac3c4bba5f91b1bc413c651b7a98313982 \ No newline at end of file diff --git a/plugins/analysis-nori/licenses/lucene-analyzers-nori-7.5.0-snapshot-608f0277b0.jar.sha1 b/plugins/analysis-nori/licenses/lucene-analyzers-nori-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index 21c25d2bb2404..0000000000000 --- a/plugins/analysis-nori/licenses/lucene-analyzers-nori-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -3f5dec44f380d6d58bc1c8aec51964fcb5390b60 \ No newline at end of file diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..80cf627011b4e --- /dev/null +++ b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +f5af81eec04c1da0d6969cff18f360ff379b1bf7 \ No newline at end of file diff --git a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.5.0-snapshot-608f0277b0.jar.sha1 b/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index f58c597eadd6d..0000000000000 --- a/plugins/analysis-phonetic/licenses/lucene-analyzers-phonetic-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -453bf1d60df0415439095624e0b3e42492ad4716 \ No newline at end of file diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..14be684b96f3d --- /dev/null +++ b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +9e649088ee298293aa95a05391dff9cb0582648e \ No newline at end of file diff --git a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.5.0-snapshot-608f0277b0.jar.sha1 b/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index 8ccec8dbf3786..0000000000000 --- a/plugins/analysis-smartcn/licenses/lucene-analyzers-smartcn-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -70095a45257bca9f46629b5fb6cedf9eff5e2b07 \ No newline at end of file diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..ea55c790537f4 --- /dev/null +++ b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +47fb370054ba7413d050f13c177edf01180c31ca \ No newline at end of file diff --git a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.5.0-snapshot-608f0277b0.jar.sha1 b/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index ec9c33119f556..0000000000000 --- a/plugins/analysis-stempel/licenses/lucene-analyzers-stempel-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -7199d6962d268b7877f7b5160e98e4ff21cce5c7 \ No newline at end of file diff --git a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.5.0-snapshot-13b9e28f9d.jar.sha1 b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.5.0-snapshot-13b9e28f9d.jar.sha1 new file mode 100644 index 0000000000000..2d6f580c35a23 --- /dev/null +++ b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.5.0-snapshot-13b9e28f9d.jar.sha1 @@ -0,0 +1 @@ +bc0708acbac195772b67b5ad2e9c4683d27ff450 \ No newline at end of file diff --git a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.5.0-snapshot-608f0277b0.jar.sha1 b/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.5.0-snapshot-608f0277b0.jar.sha1 deleted file mode 100644 index ba9148ef1b32a..0000000000000 --- a/plugins/analysis-ukrainian/licenses/lucene-analyzers-morfologik-7.5.0-snapshot-608f0277b0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -12aff508d39d206a1aead5013ecd11882062eb06 \ No newline at end of file diff --git a/plugins/examples/custom-suggester/build.gradle b/plugins/examples/custom-suggester/build.gradle new file mode 100644 index 0000000000000..b36d5cd218d27 --- /dev/null +++ b/plugins/examples/custom-suggester/build.gradle @@ -0,0 +1,33 @@ +/* + * 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. + */ + +apply plugin: 'elasticsearch.esplugin' + +esplugin { + name 'custom-suggester' + description 'An example plugin showing how to write and register a custom suggester' + classname 'org.elasticsearch.example.customsuggester.CustomSuggesterPlugin' +} + +integTestCluster { + numNodes = 2 +} + +// this plugin has no unit tests, only rest tests +tasks.test.enabled = false \ No newline at end of file diff --git a/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggester.java b/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggester.java new file mode 100644 index 0000000000000..b6a5b5e8f842f --- /dev/null +++ b/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggester.java @@ -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.customsuggester; + +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.util.CharsRefBuilder; +import org.elasticsearch.common.text.Text; +import org.elasticsearch.search.suggest.Suggest; +import org.elasticsearch.search.suggest.Suggester; + +import java.util.Locale; + +public class CustomSuggester extends Suggester { + + // This is a pretty dumb implementation which returns the original text + fieldName + custom config option + 12 or 123 + @Override + public Suggest.Suggestion> innerExecute( + String name, + CustomSuggestionContext suggestion, + IndexSearcher searcher, + CharsRefBuilder spare) { + + // Get the suggestion context + String text = suggestion.getText().utf8ToString(); + + // create two suggestions with 12 and 123 appended + CustomSuggestion response = new CustomSuggestion(name, suggestion.getSize(), "suggestion-dummy-value"); + + CustomSuggestion.Entry entry = new CustomSuggestion.Entry(new Text(text), 0, text.length(), "entry-dummy-value"); + + String firstOption = + String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "12"); + CustomSuggestion.Entry.Option option12 = new CustomSuggestion.Entry.Option(new Text(firstOption), 0.9f, "option-dummy-value-1"); + entry.addOption(option12); + + String secondOption = + String.format(Locale.ROOT, "%s-%s-%s-%s", text, suggestion.getField(), suggestion.options.get("suffix"), "123"); + CustomSuggestion.Entry.Option option123 = new CustomSuggestion.Entry.Option(new Text(secondOption), 0.8f, "option-dummy-value-2"); + entry.addOption(option123); + + response.addTerm(entry); + + return response; + } +} diff --git a/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggesterPlugin.java b/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggesterPlugin.java new file mode 100644 index 0000000000000..91ffa672e5351 --- /dev/null +++ b/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggesterPlugin.java @@ -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.example.customsuggester; + +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.plugins.SearchPlugin; + +import java.util.Collections; +import java.util.List; + +public class CustomSuggesterPlugin extends Plugin implements SearchPlugin { + @Override + public List> getSuggesters() { + return Collections.singletonList( + new SearchPlugin.SuggesterSpec<>( + CustomSuggestionBuilder.SUGGESTION_NAME, + CustomSuggestionBuilder::new, + CustomSuggestionBuilder::fromXContent, + CustomSuggestion::new + ) + ); + } +} diff --git a/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggestion.java b/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggestion.java new file mode 100644 index 0000000000000..f7ec27b7af002 --- /dev/null +++ b/plugins/examples/custom-suggester/src/main/java/org/elasticsearch/example/customsuggester/CustomSuggestion.java @@ -0,0 +1,227 @@ +/* + * 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.customsuggester; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.text.Text; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.suggest.Suggest; + +import java.io.IOException; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; + +public class CustomSuggestion extends Suggest.Suggestion { + + public static final int TYPE = 999; + + public static final ParseField DUMMY = new ParseField("dummy"); + + private String dummy; + + public CustomSuggestion(String name, int size, String dummy) { + super(name, size); + this.dummy = dummy; + } + + public CustomSuggestion(StreamInput in) throws IOException { + super(in); + dummy = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(dummy); + } + + @Override + public String getWriteableName() { + return CustomSuggestionBuilder.SUGGESTION_NAME; + } + + @Override + public int getWriteableType() { + return TYPE; + } + + /** + * A meaningless value used to test that plugin suggesters can add fields to their Suggestion types + * + * This can't be serialized to xcontent because Suggestions appear in xcontent as an array of entries, so there is no place + * to add a custom field. But we can still use a custom field internally and use it to define a Suggestion's behavior + */ + public String getDummy() { + return dummy; + } + + @Override + protected Entry newEntry() { + return new Entry(); + } + + @Override + protected Entry newEntry(StreamInput in) throws IOException { + return new Entry(in); + } + + public static CustomSuggestion fromXContent(XContentParser parser, String name) throws IOException { + CustomSuggestion suggestion = new CustomSuggestion(name, -1, null); + parseEntries(parser, suggestion, Entry::fromXContent); + return suggestion; + } + + public static class Entry extends Suggest.Suggestion.Entry { + + private static final ObjectParser PARSER = new ObjectParser<>("CustomSuggestionEntryParser", true, Entry::new); + + static { + declareCommonFields(PARSER); + PARSER.declareString((entry, dummy) -> entry.dummy = dummy, DUMMY); + PARSER.declareObjectArray(Entry::addOptions, (p, c) -> Option.fromXContent(p), new ParseField(OPTIONS)); + } + + private String dummy; + + public Entry() {} + + public Entry(Text text, int offset, int length, String dummy) { + super(text, offset, length); + this.dummy = dummy; + } + + public Entry(StreamInput in) throws IOException { + super(in); + dummy = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(dummy); + } + + @Override + protected Option newOption() { + return new Option(); + } + + @Override + protected Option newOption(StreamInput in) throws IOException { + return new Option(in); + } + + /* + * the value of dummy will always be the same, so this just tests that we can merge entries with custom fields + */ + @Override + protected void merge(Suggest.Suggestion.Entry