diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java index 2ee524f93b7d8..feb38500c840f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java @@ -41,9 +41,20 @@ import org.elasticsearch.plugins.ScriptPlugin; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.script.AggregationScript; +import org.elasticsearch.script.BucketAggregationScript; +import org.elasticsearch.script.BucketAggregationSelectorScript; +import org.elasticsearch.script.FieldScript; +import org.elasticsearch.script.FilterScript; +import org.elasticsearch.script.IngestConditionalScript; +import org.elasticsearch.script.IngestScript; +import org.elasticsearch.script.NumberSortScript; import org.elasticsearch.script.ScoreScript; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptEngine; +import org.elasticsearch.script.ScriptedMetricAggContexts; +import org.elasticsearch.script.StringSortScript; +import org.elasticsearch.script.UpdateScript; import org.elasticsearch.search.aggregations.pipeline.MovingFunctionScript; import java.util.ArrayList; @@ -74,13 +85,34 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens // Moving Function Pipeline Agg List movFn = new ArrayList<>(Whitelist.BASE_WHITELISTS); movFn.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.aggs.movfn.txt")); + movFn.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.geo.txt")); map.put(MovingFunctionScript.CONTEXT, movFn); // Functions used for scoring docs List scoreFn = new ArrayList<>(Whitelist.BASE_WHITELISTS); scoreFn.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.score.txt")); + scoreFn.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.geo.txt")); map.put(ScoreScript.CONTEXT, scoreFn); + // All other contexts that need geo classes, methods, and fields + List geoWhitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS); + geoWhitelists.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.geo.txt")); + map.put(PainlessExecuteAction.PainlessTestScript.CONTEXT, geoWhitelists); + map.put(AggregationScript.CONTEXT, geoWhitelists); + map.put(BucketAggregationSelectorScript.CONTEXT, geoWhitelists); + map.put(BucketAggregationScript.CONTEXT, geoWhitelists); + map.put(ScriptedMetricAggContexts.InitScript.CONTEXT, geoWhitelists); + map.put(ScriptedMetricAggContexts.MapScript.CONTEXT, geoWhitelists); + map.put(ScriptedMetricAggContexts.CombineScript.CONTEXT, geoWhitelists); + map.put(ScriptedMetricAggContexts.ReduceScript.CONTEXT, geoWhitelists); + map.put(FieldScript.CONTEXT, geoWhitelists); + map.put(FilterScript.CONTEXT, geoWhitelists); + map.put(IngestConditionalScript.CONTEXT, geoWhitelists); + map.put(IngestScript.CONTEXT, geoWhitelists); + map.put(NumberSortScript.CONTEXT, geoWhitelists); + map.put(StringSortScript.CONTEXT, geoWhitelists); + map.put(UpdateScript.CONTEXT, geoWhitelists); + whitelists = map; } 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 6f4400cb4fbd4..17c5cd013d773 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 @@ -679,13 +679,15 @@ public void addPainlessField(Class targetClass, String fieldName, Class ty "with the same name and different type parameters"); } } else { - MethodHandle methodHandleSetter; + MethodHandle methodHandleSetter = null; - try { - methodHandleSetter = MethodHandles.publicLookup().unreflectSetter(javaField); - } catch (IllegalAccessException iae) { - throw new IllegalArgumentException( - "setter method handle not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]"); + if (Modifier.isFinal(javaField.getModifiers()) == false) { + try { + methodHandleSetter = MethodHandles.publicLookup().unreflectSetter(javaField); + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException( + "setter method handle not found for field [[" + targetCanonicalClassName + "], [" + fieldName + "]]"); + } } PainlessField existingPainlessField = painlessClassBuilder.fields.get(painlessFieldKey); diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.geo.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.geo.txt new file mode 100644 index 0000000000000..d171c63cd551f --- /dev/null +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.geo.txt @@ -0,0 +1,123 @@ +# +# 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. +# + +class org.elasticsearch.common.unit.DistanceUnit { + DistanceUnit INCH + DistanceUnit YARD + DistanceUnit FEET + DistanceUnit KILOMETERS + DistanceUnit NAUTICALMILES + DistanceUnit MILLIMETERS + DistanceUnit CENTIMETERS + DistanceUnit MILES + DistanceUnit METERS + double convert(double, DistanceUnit, DistanceUnit) + double parse(String, DistanceUnit, DistanceUnit) + DistanceUnit parseUnit(String, DistanceUnit) + DistanceUnit fromString(String) + double getEarthCircumference() + double getEarthRadius() + double getDistancePerDegree() + double toMeters(double) + double fromMeters(double) + double convert(double, DistanceUnit) + double parse(String, DistanceUnit) + String toString(double) +} + +class org.elasticsearch.common.unit.DistanceUnit$Distance { + double value + DistanceUnit unit + DistanceUnit.Distance parseDistance(String) + (double, DistanceUnit) + DistanceUnit.Distance convert(DistanceUnit) +} + +class org.elasticsearch.common.geo.GeoPoint { + GeoPoint parseFromLatLon(String) + GeoPoint fromGeohash(String) + () + (String) + (double, double) + GeoPoint reset(double, double) + GeoPoint resetLat(double) + GeoPoint resetLon(double) + GeoPoint resetFromString(String) + GeoPoint resetFromString(String, boolean) + GeoPoint resetFromCoordinates(String, boolean) + GeoPoint resetFromIndexHash(long) + GeoPoint resetFromGeoHash(String) + double getLat() + double getLon() + String getGeohash() +} + +class org.elasticsearch.common.geo.GeoUtils { + double MAX_LAT + double MIN_LAT + double MAX_LON + double MIN_LON + String LATITUDE + String LONGITUDE + String GEOHASH + double EARTH_SEMI_MAJOR_AXIS + double EARTH_SEMI_MINOR_AXIS + double EARTH_MEAN_RADIUS + double EARTH_AXIS_RATIO + double EARTH_EQUATOR + double EARTH_POLAR_DISTANCE + double TOLERANCE + boolean isValidLatitude(double) + boolean isValidLongitude(double) + double geoHashCellWidth(int) + double quadTreeCellWidth(int) + double geoHashCellHeight(int) + double quadTreeCellHeight(int) + double geoHashCellSize(int) + double quadTreeCellSize(int) + int quadTreeLevelsForPrecision(double) + int geoHashLevelsForPrecision(double) + double normalizeLon(double) + double normalizeLat(double) + void normalizePoint(GeoPoint) + void normalizePoint(GeoPoint, boolean, boolean) + double maxRadialDistanceMeters(double, double) + double arcDistance(double, double, double, double) + double planeDistance(double, double, double, double) + boolean rectangleContainsPoint(org.apache.lucene.geo.Rectangle, double, double) +} + +class org.apache.lucene.geo.Rectangle no_import { + double minLat + double maxLat + double minLon + double maxLon + (double, double, double, double) + boolean crossesDateline() + boolean containsPoint(double, double, double, double, double, double) + org.apache.lucene.geo.Rectangle fromPointDistance(double, double, double) + double axisLat(double, double) +} + +class org.elasticsearch.common.geo.GeoDistance { + GeoDistance PLANE + GeoDistance ARC + GeoDistance fromString(String) + double calculate(double, double, double, double, DistanceUnit) +} diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java index 6a7758251176b..5730945551012 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java @@ -139,4 +139,13 @@ public void testStatic() { assertEquals(10, exec("staticAddIntsTest(7, 3)")); assertEquals(15.5f, exec("staticAddFloatsTest(6.5f, 9.0f)")); } + + public void testReadOnlyField() { + assertEquals(10.0, exec("DistanceUnit.Distance d = new DistanceUnit.Distance(10.0, DistanceUnit.METERS); d.value")); + expectScriptThrows(IllegalArgumentException.class, + () -> exec("DistanceUnit.Distance d = new DistanceUnit.Distance(10.0, DistanceUnit.METERS); d.value = 5.0")); + assertEquals(10.0, exec("def d = new DistanceUnit.Distance(10.0, DistanceUnit.METERS); d.value")); + expectScriptThrows(IllegalArgumentException.class, + () -> exec("def d = new DistanceUnit.Distance(10.0, DistanceUnit.METERS); d.value = 5.0")); + } } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java index 19fece29e42b5..86d89392bff26 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java @@ -22,14 +22,14 @@ import junit.framework.AssertionFailedError; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.painless.antlr.Walker; -import org.elasticsearch.painless.lookup.PainlessLookup; -import org.elasticsearch.painless.lookup.PainlessLookupBuilder; import org.elasticsearch.painless.spi.Whitelist; +import org.elasticsearch.painless.spi.WhitelistLoader; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptException; import org.elasticsearch.test.ESTestCase; import org.junit.Before; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -45,8 +45,6 @@ * Typically just asserts the output of {@code exec()} */ public abstract class ScriptTestCase extends ESTestCase { - private static final PainlessLookup PAINLESS_LOOKUP = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS); - protected PainlessScriptEngine scriptEngine; @Before @@ -66,7 +64,9 @@ protected Settings scriptEngineSettings() { */ protected Map, List> scriptContexts() { Map, List> contexts = new HashMap<>(); - contexts.put(PainlessTestScript.CONTEXT, Whitelist.BASE_WHITELISTS); + List whitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS); + whitelists.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.geo.txt")); + contexts.put(PainlessTestScript.CONTEXT, whitelists); return contexts; } @@ -91,12 +91,13 @@ public Object exec(String script, Map vars, boolean picky) { public Object exec(String script, Map vars, Map compileParams, boolean picky) { // test for ambiguity errors before running the actual script if picky is true if (picky) { - ScriptClassInfo scriptClassInfo = new ScriptClassInfo(PAINLESS_LOOKUP, PainlessTestScript.class); + ScriptClassInfo scriptClassInfo = + new ScriptClassInfo(scriptEngine.getContextsToLookups().get(PainlessTestScript.CONTEXT), PainlessTestScript.class); CompilerSettings pickySettings = new CompilerSettings(); pickySettings.setPicky(true); pickySettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(scriptEngineSettings())); - Walker.buildPainlessTree( - scriptClassInfo, new MainMethodReserved(), getTestName(), script, pickySettings, PAINLESS_LOOKUP, null); + Walker.buildPainlessTree(scriptClassInfo, new MainMethodReserved(), getTestName(), script, + pickySettings, scriptEngine.getContextsToLookups().get(PainlessTestScript.CONTEXT), null); } // test actual script execution PainlessTestScript.Factory factory = scriptEngine.compile(null, script, PainlessTestScript.CONTEXT, compileParams);