mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-28 14:52:20 +00:00
DATAES-50 - Added scripted field functionality
This commit is contained in:
parent
727cb5eb09
commit
d6603f8edf
@ -0,0 +1,19 @@
|
|||||||
|
package org.springframework.data.elasticsearch.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Ryan Murfitt
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Documented
|
||||||
|
public @interface ScriptedField {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Optional) The name of the scripted field. Defaults to
|
||||||
|
* the field name.
|
||||||
|
*/
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
}
|
@ -37,7 +37,9 @@ import org.elasticsearch.search.SearchHit;
|
|||||||
import org.elasticsearch.search.SearchHitField;
|
import org.elasticsearch.search.SearchHitField;
|
||||||
import org.elasticsearch.search.facet.Facet;
|
import org.elasticsearch.search.facet.Facet;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||||
import org.springframework.data.elasticsearch.annotations.Document;
|
import org.springframework.data.elasticsearch.annotations.Document;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||||
import org.springframework.data.elasticsearch.core.facet.DefaultFacetMapper;
|
import org.springframework.data.elasticsearch.core.facet.DefaultFacetMapper;
|
||||||
import org.springframework.data.elasticsearch.core.facet.FacetResult;
|
import org.springframework.data.elasticsearch.core.facet.FacetResult;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||||
@ -85,6 +87,7 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
|||||||
result = mapEntity(hit.getFields().values(), clazz);
|
result = mapEntity(hit.getFields().values(), clazz);
|
||||||
}
|
}
|
||||||
setPersistentEntityId(result, hit.getId(), clazz);
|
setPersistentEntityId(result, hit.getId(), clazz);
|
||||||
|
populateScriptFields(result, hit);
|
||||||
results.add(result);
|
results.add(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,7 +104,31 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
|||||||
return new FacetedPageImpl<T>(results, pageable, totalHits, facets);
|
return new FacetedPageImpl<T>(results, pageable, totalHits, facets);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T mapEntity(Collection<SearchHitField> values, Class<T> clazz) {
|
private <T> void populateScriptFields(T result, SearchHit hit) {
|
||||||
|
if (hit.getFields() != null && !hit.getFields().isEmpty() && result != null) {
|
||||||
|
for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) {
|
||||||
|
ScriptedField scriptedField = field.getAnnotation(ScriptedField.class);
|
||||||
|
if (scriptedField != null) {
|
||||||
|
String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name();
|
||||||
|
SearchHitField searchHitField = hit.getFields().get(name);
|
||||||
|
if (searchHitField != null) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
field.set(result, searchHitField.getValue());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new ElasticsearchException("failed to set scripted field: " + name + " with value: "
|
||||||
|
+ searchHitField.getValue(), e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new ElasticsearchException("failed to access scripted field: " + name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private <T> T mapEntity(Collection<SearchHitField> values, Class<T> clazz) {
|
||||||
return mapEntity(buildJSONFromFields(values), clazz);
|
return mapEntity(buildJSONFromFields(values), clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ import org.springframework.data.elasticsearch.ElasticsearchException;
|
|||||||
import org.springframework.data.elasticsearch.annotations.Document;
|
import org.springframework.data.elasticsearch.annotations.Document;
|
||||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||||
import org.springframework.data.elasticsearch.annotations.Setting;
|
import org.springframework.data.elasticsearch.annotations.Setting;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||||
@ -841,7 +842,15 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, Applicati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CollectionUtils.isNotEmpty(searchQuery.getFacets())) {
|
if (!searchQuery.getScriptFields().isEmpty()) {
|
||||||
|
searchRequest.addField("_source");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ScriptField scriptedField : searchQuery.getScriptFields()) {
|
||||||
|
searchRequest.addScriptField(scriptedField.fieldName(), scriptedField.script(), scriptedField.params());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollectionUtils.isNotEmpty(searchQuery.getFacets())) {
|
||||||
for (FacetRequest facetRequest : searchQuery.getFacets()) {
|
for (FacetRequest facetRequest : searchQuery.getFacets()) {
|
||||||
FacetBuilder facet = facetRequest.getFacet();
|
FacetBuilder facet = facetRequest.getFacet();
|
||||||
if (facetRequest.applyQueryFilter() && searchQuery.getFilter() != null) {
|
if (facetRequest.applyQueryFilter() && searchQuery.getFilter() != null) {
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core.query;
|
package org.springframework.data.elasticsearch.core.query;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.elasticsearch.index.query.FilterBuilder;
|
import org.elasticsearch.index.query.FilterBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilder;
|
import org.elasticsearch.index.query.QueryBuilder;
|
||||||
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
||||||
@ -23,6 +26,7 @@ import org.elasticsearch.search.sort.SortBuilder;
|
|||||||
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +41,7 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
|
|||||||
private QueryBuilder query;
|
private QueryBuilder query;
|
||||||
private FilterBuilder filter;
|
private FilterBuilder filter;
|
||||||
private List<SortBuilder> sorts;
|
private List<SortBuilder> sorts;
|
||||||
|
private final List<ScriptField> scriptFields = new ArrayList<ScriptField>();
|
||||||
private List<FacetRequest> facets;
|
private List<FacetRequest> facets;
|
||||||
private List<AbstractAggregationBuilder> aggregations;
|
private List<AbstractAggregationBuilder> aggregations;
|
||||||
private HighlightBuilder.Field[] highlightFields;
|
private HighlightBuilder.Field[] highlightFields;
|
||||||
@ -81,6 +86,17 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
|
|||||||
return highlightFields;
|
return highlightFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ScriptField> getScriptFields() { return scriptFields; }
|
||||||
|
|
||||||
|
public void setScriptFields(List<ScriptField> scriptFields) {
|
||||||
|
this.scriptFields.addAll(scriptFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addScriptField(ScriptField... scriptField) {
|
||||||
|
scriptFields.addAll(Arrays.asList(scriptField));
|
||||||
|
}
|
||||||
|
|
||||||
public void addFacet(FacetRequest facetRequest) {
|
public void addFacet(FacetRequest facetRequest) {
|
||||||
if (facets == null) {
|
if (facets == null) {
|
||||||
facets = new ArrayList<FacetRequest>();
|
facets = new ArrayList<FacetRequest>();
|
||||||
|
@ -41,6 +41,7 @@ public class NativeSearchQueryBuilder {
|
|||||||
|
|
||||||
private QueryBuilder queryBuilder;
|
private QueryBuilder queryBuilder;
|
||||||
private FilterBuilder filterBuilder;
|
private FilterBuilder filterBuilder;
|
||||||
|
private List<ScriptField> scriptFields = new ArrayList<ScriptField>();
|
||||||
private List<SortBuilder> sortBuilders = new ArrayList<SortBuilder>();
|
private List<SortBuilder> sortBuilders = new ArrayList<SortBuilder>();
|
||||||
private List<FacetRequest> facetRequests = new ArrayList<FacetRequest>();
|
private List<FacetRequest> facetRequests = new ArrayList<FacetRequest>();
|
||||||
private List<AbstractAggregationBuilder> aggregationBuilders = new ArrayList<AbstractAggregationBuilder>();
|
private List<AbstractAggregationBuilder> aggregationBuilders = new ArrayList<AbstractAggregationBuilder>();
|
||||||
@ -69,6 +70,11 @@ public class NativeSearchQueryBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NativeSearchQueryBuilder withScriptField(ScriptField scriptField) {
|
||||||
|
this.scriptFields.add(scriptField);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder aggregationBuilder) {
|
public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder aggregationBuilder) {
|
||||||
this.aggregationBuilders.add(aggregationBuilder);
|
this.aggregationBuilders.add(aggregationBuilder);
|
||||||
return this;
|
return this;
|
||||||
@ -141,6 +147,9 @@ public class NativeSearchQueryBuilder {
|
|||||||
if (fields != null) {
|
if (fields != null) {
|
||||||
nativeSearchQuery.addFields(fields);
|
nativeSearchQuery.addFields(fields);
|
||||||
}
|
}
|
||||||
|
if (CollectionUtils.isNotEmpty(scriptFields)) {
|
||||||
|
nativeSearchQuery.setScriptFields(scriptFields);
|
||||||
|
}
|
||||||
|
|
||||||
if (CollectionUtils.isNotEmpty(facetRequests)) {
|
if (CollectionUtils.isNotEmpty(facetRequests)) {
|
||||||
nativeSearchQuery.setFacets(facetRequests);
|
nativeSearchQuery.setFacets(facetRequests);
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.springframework.data.elasticsearch.core.query;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Ryan Murfitt
|
||||||
|
*/
|
||||||
|
public class ScriptField {
|
||||||
|
private final String fieldName;
|
||||||
|
private final String script;
|
||||||
|
private final Map<String, Object> params;
|
||||||
|
|
||||||
|
public ScriptField(String fieldName, String script, Map<String, Object> params) {
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
this.script = script;
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fieldName() {
|
||||||
|
return fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String script() {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> params() {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,4 +44,7 @@ public interface SearchQuery extends Query {
|
|||||||
List<AbstractAggregationBuilder> getAggregations();
|
List<AbstractAggregationBuilder> getAggregations();
|
||||||
|
|
||||||
HighlightBuilder.Field[] getHighlightFields();
|
HighlightBuilder.Field[] getHighlightFields();
|
||||||
|
|
||||||
|
List<ScriptField> getScriptFields();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed 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.springframework.data.elasticsearch;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||||
|
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.annotation.Version;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.Document;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Rizwan Idrees
|
||||||
|
* @author Mohsin Husen
|
||||||
|
*/
|
||||||
|
@Document(indexName = "test-index", type = "test-type", indexStoreType = "memory", shards = 1, replicas = 0, refreshInterval = "-1")
|
||||||
|
public class SampleEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
private String type;
|
||||||
|
private String message;
|
||||||
|
private int rate;
|
||||||
|
@ScriptedField
|
||||||
|
private Long scriptedRate;
|
||||||
|
private boolean available;
|
||||||
|
private String highlightedMessage;
|
||||||
|
@Version
|
||||||
|
private Long version;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRate() {
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRate(int rate) {
|
||||||
|
this.rate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getScriptedRate() {
|
||||||
|
return scriptedRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScriptedRate(Long scriptedRate) {
|
||||||
|
this.scriptedRate = scriptedRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAvailable(boolean available) {
|
||||||
|
this.available = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHighlightedMessage() {
|
||||||
|
return highlightedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHighlightedMessage(String highlightedMessage) {
|
||||||
|
this.highlightedMessage = highlightedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(Long version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof SampleEntity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
SampleEntity rhs = (SampleEntity) obj;
|
||||||
|
return new EqualsBuilder().append(this.id, rhs.id).append(this.type, rhs.type).append(this.message, rhs.message)
|
||||||
|
.append(this.rate, rhs.rate).append(this.available, rhs.available).append(this.version, rhs.version).isEquals();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return new HashCodeBuilder().append(id).append(type).append(message).append(rate).append(available).append(version)
|
||||||
|
.toHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SampleEntity{" +
|
||||||
|
"id='" + id + '\'' +
|
||||||
|
", type='" + type + '\'' +
|
||||||
|
", message='" + message + '\'' +
|
||||||
|
", rate=" + rate +
|
||||||
|
", available=" + available +
|
||||||
|
", highlightedMessage='" + highlightedMessage + '\'' +
|
||||||
|
", version=" + version +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -466,6 +466,36 @@ public class ElasticsearchTemplateTests {
|
|||||||
assertThat(sampleEntities.getTotalElements(), equalTo(1L));
|
assertThat(sampleEntities.getTotalElements(), equalTo(1L));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldUseScriptedFields() {
|
||||||
|
// given
|
||||||
|
String documentId = randomNumeric(5);
|
||||||
|
SampleEntity sampleEntity = new SampleEntity();
|
||||||
|
sampleEntity.setId(documentId);
|
||||||
|
sampleEntity.setRate(2);
|
||||||
|
sampleEntity.setMessage("some message");
|
||||||
|
sampleEntity.setVersion(System.currentTimeMillis());
|
||||||
|
|
||||||
|
IndexQuery indexQuery = new IndexQuery();
|
||||||
|
indexQuery.setId(documentId);
|
||||||
|
indexQuery.setObject(sampleEntity);
|
||||||
|
|
||||||
|
elasticsearchTemplate.index(indexQuery);
|
||||||
|
elasticsearchTemplate.refresh(SampleEntity.class, true);
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<String, Object>();
|
||||||
|
params.put("factor", 2);
|
||||||
|
// when
|
||||||
|
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||||
|
.withQuery(matchAllQuery())
|
||||||
|
.withScriptField(new ScriptField("scriptedRate", "doc['rate'].value * factor", params))
|
||||||
|
.build();
|
||||||
|
Page<SampleEntity> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class);
|
||||||
|
// then
|
||||||
|
assertThat(sampleEntities.getTotalElements(), equalTo(1L));
|
||||||
|
assertThat(sampleEntities.getContent().get(0).getScriptedRate(), equalTo(4L));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnPageableResultsGivenStringQuery() {
|
public void shouldReturnPageableResultsGivenStringQuery() {
|
||||||
// given
|
// given
|
||||||
|
Loading…
x
Reference in New Issue
Block a user