/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.search.lookup.LeafDocLookup;
import org.elasticsearch.search.lookup.LeafSearchLookup;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* ScriptImpl can be used as either an {@link ExecutableScript} or a {@link LeafSearchScript}
* to run a previously compiled Painless script.
*/
final class ScriptImpl implements ExecutableScript, LeafSearchScript {
/**
* The Painless script that can be run.
*/
private final GenericElasticsearchScript script;
/**
* A map that can be used to access input parameters at run-time.
*/
private final Map<String, Object> variables;
/**
* The lookup is used to access search field values at run-time.
*/
private final LeafSearchLookup lookup;
/**
* the 'doc' object accessed by the script, if available.
*/
private final LeafDocLookup doc;
/**
* Looks up the {@code _score} from {@link #scorer} if {@code _score} is used, otherwise returns {@code 0.0}.
*/
private final ScoreLookup scoreLookup;
/**
* Looks up the {@code ctx} from the {@link #variables} if {@code ctx} is used, otherwise return {@code null}.
*/
private final Function<Map<String, Object>, Map<?, ?>> ctxLookup;
/**
* Current scorer being used
* @see #setScorer(Scorer)
*/
private Scorer scorer;
/**
* Current _value for aggregation
* @see #setNextAggregationValue(Object)
*/
private Object aggregationValue;
/**
* Creates a ScriptImpl for the a previously compiled Painless script.
* @param script The previously compiled Painless script.
* @param vars The initial variables to run the script with.
* @param lookup The lookup to allow search fields to be available if this is run as a search script.
*/
ScriptImpl(final GenericElasticsearchScript script, final Map<String, Object> vars, final LeafSearchLookup lookup) {
this.script = script;
this.lookup = lookup;
this.variables = new HashMap<>();
if (vars != null) {
variables.putAll(vars);
}
if (lookup != null) {
variables.putAll(lookup.asMap());
doc = lookup.doc();
} else {
doc = null;
}
scoreLookup = script.uses$_score() ? ScriptImpl::getScore : scorer -> 0.0;
ctxLookup = script.uses$ctx() ? variables -> (Map<?, ?>) variables.get("ctx") : variables -> null;
}
/**
* Set a variable for the script to be run against.
* @param name The variable name.
* @param value The variable value.
*/
@Override
public void setNextVar(final String name, final Object value) {
variables.put(name, value);
}
/**
* Set the next aggregation value.
* @param value Per-document value, typically a String, Long, or Double.
*/
@Override
public void setNextAggregationValue(Object value) {
this.aggregationValue = value;
}
/**
* Run the script.
* @return The script result.
*/
@Override
public Object run() {
return script.execute(variables, scoreLookup.apply(scorer), doc, aggregationValue, ctxLookup.apply(variables));
}
/**
* Run the script.
* @return The script result as a double.
*/
@Override
public double runAsDouble() {
return ((Number)run()).doubleValue();
}
/**
* Run the script.
* @return The script result as a long.
*/
@Override
public long runAsLong() {
return ((Number)run()).longValue();
}
/**
* Sets the scorer to be accessible within a script.
* @param scorer The scorer used for a search.
*/
@Override
public void setScorer(final Scorer scorer) {
this.scorer = scorer;
}
/**
* Sets the current document.
* @param doc The current document.
*/
@Override
public void setDocument(final int doc) {
if (lookup != null) {
lookup.setDocument(doc);
}
}
/**
* Sets the current source.
* @param source The current source.
*/
@Override
public void setSource(final Map<String, Object> source) {
if (lookup != null) {
lookup.source().setSource(source);
}
}
private static double getScore(Scorer scorer) {
try {
return scorer.score();
} catch (IOException e) {
throw new ElasticsearchException("couldn't lookup score", e);
}
}
interface ScoreLookup {
double apply(Scorer scorer);
}
}