mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-07-08 11:32:45 +00:00
DATAES-12 Merge Geo Location from branch 'master' of https://github.com/fmarchand/spring-data-elasticsearch
This commit is contained in:
parent
aa6a5e3753
commit
74a4035408
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.elasticsearch.index.query.*;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoBBox;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoLocation;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import static org.elasticsearch.index.query.FilterBuilders.*;
|
||||
import static org.springframework.data.elasticsearch.core.query.Criteria.OperationKey;
|
||||
|
||||
/**
|
||||
* CriteriaFilterProcessor
|
||||
*
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
class CriteriaFilterProcessor {
|
||||
|
||||
|
||||
FilterBuilder createFilterFromCriteria(Criteria criteria) {
|
||||
List<FilterBuilder> fbList = new LinkedList<FilterBuilder>();
|
||||
FilterBuilder filter = null;
|
||||
|
||||
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
|
||||
|
||||
while (chainIterator.hasNext()) {
|
||||
FilterBuilder fb = null;
|
||||
Criteria chainedCriteria = chainIterator.next();
|
||||
if(chainedCriteria.isOr()){
|
||||
fb = orFilter(createFilterFragmentForCriteria(chainedCriteria).toArray(new FilterBuilder[]{ }));
|
||||
fbList.add(fb);
|
||||
}else if(chainedCriteria.isNegating()){
|
||||
List<FilterBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(), criteria.getFilterCriteriaEntries().iterator());
|
||||
|
||||
if(!negationFilters.isEmpty()) {
|
||||
fbList.addAll(negationFilters);
|
||||
}
|
||||
}else {
|
||||
fbList.addAll(createFilterFragmentForCriteria(chainedCriteria));
|
||||
}
|
||||
}
|
||||
|
||||
if(!fbList.isEmpty()) {
|
||||
if(fbList.size() == 1) {
|
||||
filter =fbList.get(0);
|
||||
} else {
|
||||
filter = andFilter(fbList.toArray(new FilterBuilder[]{ }));
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
||||
private List<FilterBuilder> createFilterFragmentForCriteria(Criteria chainedCriteria) {
|
||||
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getFilterCriteriaEntries().iterator();
|
||||
List<FilterBuilder> filterList = new LinkedList<FilterBuilder>();
|
||||
|
||||
String fieldName = chainedCriteria.getField().getName();
|
||||
Assert.notNull(fieldName,"Unknown field");
|
||||
FilterBuilder filter = null;
|
||||
|
||||
while (it.hasNext()){
|
||||
Criteria.CriteriaEntry entry = it.next();
|
||||
filter = processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName);
|
||||
filterList.add(filter);
|
||||
}
|
||||
|
||||
return filterList;
|
||||
}
|
||||
|
||||
|
||||
private FilterBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
FilterBuilder filter = null;
|
||||
|
||||
switch (key){
|
||||
case WITHIN: {
|
||||
filter = geoDistanceFilter(fieldName);
|
||||
|
||||
Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values.");
|
||||
Object[] valArray = (Object[]) value;
|
||||
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
|
||||
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
|
||||
Assert.isTrue(valArray[0] instanceof GeoLocation, "First element of a geo distance filter must be a GeoLocation");
|
||||
Assert.isTrue(valArray[1] instanceof String, "Second element of a geo distance filter must be a String");
|
||||
|
||||
GeoLocation loc = (GeoLocation)valArray[0];
|
||||
String dist = (String)valArray[1];
|
||||
|
||||
((GeoDistanceFilterBuilder)filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist);
|
||||
break;
|
||||
}
|
||||
|
||||
case BBOX: {
|
||||
filter = geoBoundingBoxFilter(fieldName);
|
||||
|
||||
Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values.");
|
||||
Object[] valArray = (Object[]) value;
|
||||
Assert.noNullElements(valArray, "Geo bbox filter takes a not null element array as parameter.");
|
||||
Assert.isTrue(valArray.length == 1, "Geo distance filter takes a 1-elements array as parameter.");
|
||||
Assert.isTrue(valArray[0] instanceof GeoBBox, "single-element of a geo bbox filter must be a GeoBBox");
|
||||
|
||||
GeoBBox geoBBox = (GeoBBox)valArray[0];
|
||||
((GeoBoundingBoxFilterBuilder)filter).topLeft(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon());
|
||||
((GeoBoundingBoxFilterBuilder)filter).bottomRight(geoBBox.getBottomRight().getLat(), geoBBox.getBottomRight().getLon());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private List<FilterBuilder> buildNegationFilter(String fieldName, Iterator<Criteria.CriteriaEntry> it){
|
||||
List<FilterBuilder> notFilterList = new LinkedList<FilterBuilder>();
|
||||
|
||||
while (it.hasNext()){
|
||||
Criteria.CriteriaEntry criteriaEntry = it.next();
|
||||
FilterBuilder notFilter = notFilter(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
|
||||
notFilterList.add(notFilter);
|
||||
}
|
||||
|
||||
return notFilterList;
|
||||
}
|
||||
}
|
@ -22,6 +22,8 @@ import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
@ -32,42 +34,76 @@ import static org.springframework.data.elasticsearch.core.query.Criteria.Operati
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
class CriteriaQueryProcessor {
|
||||
|
||||
|
||||
QueryBuilder createQueryFromCriteria(Criteria criteria) {
|
||||
BoolQueryBuilder query = boolQuery();
|
||||
if(criteria == null)
|
||||
return null;
|
||||
|
||||
List<QueryBuilder> shouldQueryBuilderList = new LinkedList<QueryBuilder>();
|
||||
List<QueryBuilder> mustNotQueryBuilderList = new LinkedList<QueryBuilder>();
|
||||
List<QueryBuilder> mustQueryBuilderList = new LinkedList<QueryBuilder>();
|
||||
|
||||
|
||||
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
|
||||
while (chainIterator.hasNext()) {
|
||||
Criteria chainedCriteria = chainIterator.next();
|
||||
if (chainedCriteria.isOr()) {
|
||||
query.should(createQueryFragmentForCriteria(chainedCriteria));
|
||||
} else if (chainedCriteria.isNegating()) {
|
||||
query.mustNot(createQueryFragmentForCriteria(chainedCriteria));
|
||||
} else {
|
||||
query.must(createQueryFragmentForCriteria(chainedCriteria));
|
||||
QueryBuilder queryFragmentForCriteria = createQueryFragmentForCriteria(chainedCriteria);
|
||||
|
||||
if(queryFragmentForCriteria!=null) {
|
||||
if(chainedCriteria.isOr()){
|
||||
shouldQueryBuilderList.add(queryFragmentForCriteria);
|
||||
}else if(chainedCriteria.isNegating()){
|
||||
mustNotQueryBuilderList.add(queryFragmentForCriteria);
|
||||
}else{
|
||||
mustQueryBuilderList.add(queryFragmentForCriteria);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BoolQueryBuilder query = null;
|
||||
|
||||
if(!shouldQueryBuilderList.isEmpty() || !mustNotQueryBuilderList.isEmpty() || !mustQueryBuilderList.isEmpty()) {
|
||||
|
||||
query = boolQuery();
|
||||
|
||||
for(QueryBuilder qb : shouldQueryBuilderList) {
|
||||
query.should(qb);
|
||||
}
|
||||
for(QueryBuilder qb : mustNotQueryBuilderList) {
|
||||
query.mustNot(qb);
|
||||
}
|
||||
for(QueryBuilder qb : mustQueryBuilderList) {
|
||||
query.must(qb);
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
|
||||
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getCriteriaEntries().iterator();
|
||||
boolean singeEntryCriteria = (chainedCriteria.getCriteriaEntries().size() == 1);
|
||||
if(chainedCriteria.getQueryCriteriaEntries().isEmpty())
|
||||
return null;
|
||||
|
||||
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getQueryCriteriaEntries().iterator();
|
||||
boolean singeEntryCriteria = (chainedCriteria.getQueryCriteriaEntries().size() == 1);
|
||||
|
||||
String fieldName = chainedCriteria.getField().getName();
|
||||
Assert.notNull(fieldName, "Unknown field");
|
||||
Assert.notNull(fieldName,"Unknown field");
|
||||
QueryBuilder query = null;
|
||||
|
||||
if (singeEntryCriteria) {
|
||||
if(singeEntryCriteria){
|
||||
Criteria.CriteriaEntry entry = it.next();
|
||||
query = processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName);
|
||||
} else {
|
||||
}else{
|
||||
query = boolQuery();
|
||||
while (it.hasNext()) {
|
||||
while (it.hasNext()){
|
||||
Criteria.CriteriaEntry entry = it.next();
|
||||
((BoolQueryBuilder) query).must(processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName));
|
||||
((BoolQueryBuilder)query).must(processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +111,7 @@ class CriteriaQueryProcessor {
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
|
@ -34,7 +34,9 @@ import org.elasticsearch.client.Requests;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.query.FilterBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.facet.Facet;
|
||||
import org.elasticsearch.search.facet.FacetBuilder;
|
||||
@ -180,8 +182,21 @@ public class ElasticsearchTemplate implements ElasticsearchOperations {
|
||||
|
||||
@Override
|
||||
public <T> Page<T> queryForPage(CriteriaQuery criteriaQuery, Class<T> clazz) {
|
||||
QueryBuilder query = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria());
|
||||
SearchResponse response = prepareSearch(criteriaQuery, clazz).setQuery(query).execute().actionGet();
|
||||
QueryBuilder elasticsearchQuery = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria());
|
||||
FilterBuilder elasticsearchFilter = new CriteriaFilterProcessor().createFilterFromCriteria(criteriaQuery.getCriteria());
|
||||
SearchRequestBuilder searchRequestBuilder = prepareSearch(criteriaQuery, clazz);
|
||||
|
||||
if (elasticsearchQuery != null) {
|
||||
searchRequestBuilder.setQuery(elasticsearchQuery);
|
||||
} else {
|
||||
searchRequestBuilder.setQuery(QueryBuilders.matchAllQuery());
|
||||
}
|
||||
|
||||
if (elasticsearchFilter != null)
|
||||
searchRequestBuilder.setFilter(elasticsearchFilter);
|
||||
|
||||
SearchResponse response = searchRequestBuilder
|
||||
.execute().actionGet();
|
||||
return mapResults(response, clazz, criteriaQuery.getPageable());
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.springframework.data.elasticsearch.annotations.*;
|
||||
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoLocation;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
@ -71,6 +72,11 @@ class MappingBuilder {
|
||||
if (isEntity(field)) {
|
||||
mapEntity(xContentBuilder, field.getType(), false, EMPTY, field.getName());
|
||||
}
|
||||
|
||||
if(field.getType() == GeoLocation.class) {
|
||||
applyGeoLocationFieldMapping(xContentBuilder, field);
|
||||
}
|
||||
|
||||
Field singleField = field.getAnnotation(Field.class);
|
||||
MultiField multiField = field.getAnnotation(MultiField.class);
|
||||
if (isRootObject && singleField != null && isIdField(field, idFieldName)) {
|
||||
@ -88,6 +94,12 @@ class MappingBuilder {
|
||||
|
||||
}
|
||||
|
||||
private static void applyGeoLocationFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field) throws IOException {
|
||||
xContentBuilder.startObject(field.getName());
|
||||
xContentBuilder.field("type", "geo_point")
|
||||
.endObject();
|
||||
}
|
||||
|
||||
private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field)
|
||||
throws IOException {
|
||||
xContentBuilder.startObject(field.getName())
|
||||
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.core.geo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Geo bbox used for #{@link org.springframework.data.elasticsearch.core.query.Criteria}.
|
||||
*
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
public class GeoBBox {
|
||||
private GeoLocation topLeft;
|
||||
private GeoLocation bottomRight;
|
||||
|
||||
public GeoBBox(GeoLocation topLeft, GeoLocation bottomRight) {
|
||||
this.topLeft = topLeft;
|
||||
this.bottomRight = bottomRight;
|
||||
}
|
||||
|
||||
public GeoLocation getTopLeft() {
|
||||
return topLeft;
|
||||
}
|
||||
|
||||
public void setTopLeft(GeoLocation topLeft) {
|
||||
this.topLeft = topLeft;
|
||||
}
|
||||
|
||||
public GeoLocation getBottomRight() {
|
||||
return bottomRight;
|
||||
}
|
||||
|
||||
public void setBottomRight(GeoLocation bottomRight) {
|
||||
this.bottomRight = bottomRight;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.core.geo;
|
||||
|
||||
/**
|
||||
* geo-location used for #{@link org.springframework.data.elasticsearch.core.query.Criteria}.
|
||||
*
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
public class GeoLocation {
|
||||
private double lat;
|
||||
private double lon;
|
||||
|
||||
public GeoLocation lat(double lat) {
|
||||
setLat(lat);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GeoLocation lon(double lon) {
|
||||
setLon(lon);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GeoLocation() {
|
||||
}
|
||||
|
||||
public GeoLocation(double latitude, double longitude) {
|
||||
this.lat = latitude;
|
||||
this.lon = longitude;
|
||||
}
|
||||
|
||||
public double getLat() {
|
||||
return lat;
|
||||
}
|
||||
|
||||
public void setLat(double lat) {
|
||||
this.lat = lat;
|
||||
}
|
||||
|
||||
public double getLon() {
|
||||
return lon;
|
||||
}
|
||||
|
||||
public void setLon(double lon) {
|
||||
this.lon = lon;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.core.geo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Geo polygon used for #{@link org.springframework.data.elasticsearch.core.query.Criteria}.
|
||||
*
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
public class GeoPolygon {
|
||||
private List<GeoLocation> points;
|
||||
|
||||
public GeoPolygon(List<GeoLocation> points) {
|
||||
this.points = points;
|
||||
}
|
||||
}
|
@ -25,6 +25,8 @@ import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoBBox;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoLocation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -33,6 +35,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
public class Criteria {
|
||||
|
||||
@ -48,7 +51,9 @@ public class Criteria {
|
||||
|
||||
private List<Criteria> criteriaChain = new ArrayList<Criteria>(1);
|
||||
|
||||
private Set<CriteriaEntry> criteria = new LinkedHashSet<CriteriaEntry>();
|
||||
private Set<CriteriaEntry> queryCriteria = new LinkedHashSet<CriteriaEntry>();
|
||||
|
||||
private Set<CriteriaEntry> filterCriteria = new LinkedHashSet<CriteriaEntry>();
|
||||
|
||||
public Criteria() {
|
||||
}
|
||||
@ -171,7 +176,7 @@ public class Criteria {
|
||||
Assert.notNull(criteria, "Cannot chain 'null' criteria.");
|
||||
|
||||
Criteria orConnectedCritiera = new OrCriteria(this.criteriaChain, criteria.getField());
|
||||
orConnectedCritiera.criteria.addAll(criteria.criteria);
|
||||
orConnectedCritiera.queryCriteria.addAll(criteria.queryCriteria);
|
||||
return orConnectedCritiera;
|
||||
}
|
||||
|
||||
@ -192,7 +197,7 @@ public class Criteria {
|
||||
* @return
|
||||
*/
|
||||
public Criteria is(Object o) {
|
||||
criteria.add(new CriteriaEntry(OperationKey.EQUALS, o));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.EQUALS, o));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -205,7 +210,7 @@ public class Criteria {
|
||||
*/
|
||||
public Criteria contains(String s) {
|
||||
assertNoBlankInWildcardedQuery(s, true, true);
|
||||
criteria.add(new CriteriaEntry(OperationKey.CONTAINS, s));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.CONTAINS, s));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -217,7 +222,7 @@ public class Criteria {
|
||||
*/
|
||||
public Criteria startsWith(String s) {
|
||||
assertNoBlankInWildcardedQuery(s, true, false);
|
||||
criteria.add(new CriteriaEntry(OperationKey.STARTS_WITH, s));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.STARTS_WITH, s));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -230,7 +235,7 @@ public class Criteria {
|
||||
*/
|
||||
public Criteria endsWith(String s) {
|
||||
assertNoBlankInWildcardedQuery(s, false, true);
|
||||
criteria.add(new CriteriaEntry(OperationKey.ENDS_WITH, s));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.ENDS_WITH, s));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -251,7 +256,7 @@ public class Criteria {
|
||||
* @return
|
||||
*/
|
||||
public Criteria fuzzy(String s) {
|
||||
criteria.add(new CriteriaEntry(OperationKey.FUZZY, s));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.FUZZY, s));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -262,7 +267,7 @@ public class Criteria {
|
||||
* @return
|
||||
*/
|
||||
public Criteria expression(String s) {
|
||||
criteria.add(new CriteriaEntry(OperationKey.EXPRESSION, s));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.EXPRESSION, s));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -292,7 +297,7 @@ public class Criteria {
|
||||
throw new InvalidDataAccessApiUsageException("Range [* TO *] is not allowed");
|
||||
}
|
||||
|
||||
criteria.add(new CriteriaEntry(OperationKey.BETWEEN, new Object[] { lowerBound, upperBound }));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.BETWEEN, new Object[]{lowerBound, upperBound}));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -341,7 +346,35 @@ public class Criteria {
|
||||
*/
|
||||
public Criteria in(Iterable<?> values) {
|
||||
Assert.notNull(values, "Collection of 'in' values must not be null");
|
||||
criteria.add(new CriteriaEntry(OperationKey.IN, values));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.IN, values));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new CriteriaEntry for {@code location WITHIN distance}
|
||||
* @param location {@link GeoLocation} center coordinates
|
||||
* @param distance {@link String} radius as a string (e.g. : '100km').
|
||||
* Distance unit :
|
||||
* either mi/miles or km can be set
|
||||
*
|
||||
* @return Criteria the chaind criteria with the new 'within' criteria included.
|
||||
*/
|
||||
public Criteria within(GeoLocation location, String distance) {
|
||||
Assert.notNull(location, "Location value for near criteria must not be null");
|
||||
Assert.notNull(location, "Distance value for near criteria must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[]{location, distance}));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new CriteriaEntry for {@code location BBOX bounding box}
|
||||
* @param bbox {@link org.springframework.data.elasticsearch.core.geo.GeoBBox} center coordinates
|
||||
*
|
||||
* @return Criteria the chaind criteria with the new 'bbox' criteria included.
|
||||
*/
|
||||
public Criteria bbox(GeoBBox bbox) {
|
||||
Assert.notNull(bbox, "bbox value for bbox criteria must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{bbox}));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -361,8 +394,16 @@ public class Criteria {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
public Set<CriteriaEntry> getCriteriaEntries() {
|
||||
return Collections.unmodifiableSet(this.criteria);
|
||||
public Set<CriteriaEntry> getQueryCriteriaEntries() {
|
||||
return Collections.unmodifiableSet(this.queryCriteria);
|
||||
}
|
||||
|
||||
public Set<CriteriaEntry> getFilterCriteriaEntries() {
|
||||
return Collections.unmodifiableSet(this.filterCriteria);
|
||||
}
|
||||
|
||||
public Set<CriteriaEntry> getFilterCriteria() {
|
||||
return filterCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -424,7 +465,7 @@ public class Criteria {
|
||||
}
|
||||
|
||||
public enum OperationKey {
|
||||
EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH, EXPRESSION, BETWEEN, FUZZY, IN;
|
||||
EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH, EXPRESSION, BETWEEN, FUZZY, IN, WITHIN, BBOX, NEAR;
|
||||
}
|
||||
|
||||
public static class CriteriaEntry {
|
||||
|
@ -21,7 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.collections.CollectionUtils.addAll;
|
||||
import static org.springframework.data.elasticsearch.core.query.Query.DEFAULT_PAGE;
|
||||
import static org.springframework.data.elasticsearch.core.query.AbstractQuery.DEFAULT_PAGE;
|
||||
|
||||
/**
|
||||
* MoreLikeThisQuery
|
||||
|
@ -17,7 +17,6 @@ package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import org.elasticsearch.index.query.FilterBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.facet.FacetBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoLocation;
|
||||
|
||||
/**
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
@Document(indexName = "test-geo-index", type = "test-geo-type")
|
||||
public class GeoAuthor {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
private GeoLocation location;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public GeoLocation getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(GeoLocation location) {
|
||||
this.location = location;
|
||||
}
|
||||
}
|
@ -25,11 +25,13 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.GeoAuthor;
|
||||
import org.springframework.data.elasticsearch.SampleEntity;
|
||||
import org.springframework.data.elasticsearch.SampleMappingEntity;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoBBox;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoLocation;
|
||||
import org.springframework.data.elasticsearch.core.query.*;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@ -42,12 +44,14 @@ import static org.elasticsearch.index.query.FilterBuilders.boolFilter;
|
||||
import static org.elasticsearch.index.query.FilterBuilders.termFilter;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.fieldQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Franck Marchand
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:elasticsearch-template-test.xml")
|
||||
@ -57,10 +61,16 @@ public class ElasticsearchTemplateTests {
|
||||
private ElasticsearchTemplate elasticsearchTemplate;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
public void before(){
|
||||
elasticsearchTemplate.deleteIndex(SampleEntity.class);
|
||||
elasticsearchTemplate.createIndex(SampleEntity.class);
|
||||
elasticsearchTemplate.refresh(SampleEntity.class, true);
|
||||
|
||||
elasticsearchTemplate.deleteIndex(GeoAuthor.class);
|
||||
elasticsearchTemplate.createIndex(GeoAuthor.class);
|
||||
elasticsearchTemplate.refresh(GeoAuthor.class, true);
|
||||
|
||||
elasticsearchTemplate.putMapping(GeoAuthor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -716,4 +726,85 @@ public class ElasticsearchTemplateTests {
|
||||
assertThat(elasticsearchTemplate.indexExists(clazz), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPutMappingForGivenEntityWithGeoLocation()throws Exception{
|
||||
//given
|
||||
Class entity = GeoAuthor.class;
|
||||
elasticsearchTemplate.createIndex(entity);
|
||||
//when
|
||||
assertThat(elasticsearchTemplate.putMapping(entity) , is(true)) ;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnListForGivenCriteriaWithGeoLocation(){
|
||||
//given
|
||||
List<IndexQuery> indexQueries = new ArrayList<IndexQuery>();
|
||||
//first document
|
||||
String documentId = randomNumeric(5);
|
||||
GeoAuthor geoAuthor1 = new GeoAuthor();
|
||||
geoAuthor1.setId(documentId);
|
||||
geoAuthor1.setName("Franck Marchand");
|
||||
geoAuthor1.setLocation(new GeoLocation(45.7806d, 3.0875d)); // Clermont-Ferrand
|
||||
|
||||
IndexQuery indexQuery1 = new IndexQuery();
|
||||
indexQuery1.setId(documentId);
|
||||
indexQuery1.setObject(geoAuthor1);
|
||||
indexQueries.add(indexQuery1);
|
||||
|
||||
//second document
|
||||
String documentId2 = randomNumeric(5);
|
||||
GeoAuthor geoAuthor2 = new GeoAuthor();
|
||||
geoAuthor2.setId(documentId2);
|
||||
geoAuthor2.setName("Mohsin Husen");
|
||||
geoAuthor2.setLocation(new GeoLocation(51.5171d, 0.1062d)); // London
|
||||
|
||||
IndexQuery indexQuery2 = new IndexQuery();
|
||||
indexQuery2.setId(documentId2);
|
||||
indexQuery2.setObject(geoAuthor2);
|
||||
|
||||
indexQueries.add(indexQuery2);
|
||||
|
||||
//third document
|
||||
String documentId3 = randomNumeric(5);
|
||||
GeoAuthor geoAuthor3 = new GeoAuthor();
|
||||
geoAuthor3.setId(documentId3);
|
||||
geoAuthor3.setName("Rizwan Idrees");
|
||||
geoAuthor3.setLocation(new GeoLocation(51.5171d, 0.1062d)); // London
|
||||
|
||||
IndexQuery indexQuery3 = new IndexQuery();
|
||||
indexQuery3.setId(documentId3);
|
||||
indexQuery3.setObject(geoAuthor3);
|
||||
|
||||
indexQueries.add(indexQuery3);
|
||||
//when
|
||||
elasticsearchTemplate.bulkIndex(indexQueries);
|
||||
elasticsearchTemplate.refresh(GeoAuthor.class, true);
|
||||
//when
|
||||
CriteriaQuery geoLocationCriteriaQuery = new CriteriaQuery(
|
||||
new Criteria("location").within(new GeoLocation(45.7806d, 3.0875d), "20km"));
|
||||
|
||||
|
||||
List<GeoAuthor> geoAuthorsForGeoCriteria = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery,GeoAuthor.class);
|
||||
//then
|
||||
assertThat(geoAuthorsForGeoCriteria.size(),is(1));
|
||||
assertEquals("Franck Marchand", geoAuthorsForGeoCriteria.get(0).getName());
|
||||
|
||||
// query/filter geo distance mixed query
|
||||
CriteriaQuery geoLocationCriteriaQuery2 = new CriteriaQuery(
|
||||
new Criteria("name").is("Mohsin Husen").and("location").within(new GeoLocation(51.5171d, 0.1062d), "20km"));
|
||||
List<GeoAuthor> geoAuthorsForGeoCriteria2 = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery2,GeoAuthor.class);
|
||||
|
||||
assertThat(geoAuthorsForGeoCriteria2.size(),is(1));
|
||||
assertEquals("Mohsin Husen", geoAuthorsForGeoCriteria2.get(0).getName());
|
||||
|
||||
// bbox query
|
||||
CriteriaQuery geoLocationCriteriaQuery3 = new CriteriaQuery(
|
||||
new Criteria("location").bbox(
|
||||
new GeoBBox(new GeoLocation(53.5171d, 0),
|
||||
new GeoLocation(49.5171d, 0.2062d))));
|
||||
List<GeoAuthor> geoAuthorsForGeoCriteria3 = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery3,GeoAuthor.class);
|
||||
|
||||
assertThat(geoAuthorsForGeoCriteria3.size(),is(2));
|
||||
assertThat(geoAuthorsForGeoCriteria3, containsInAnyOrder(hasProperty("name", equalTo("Mohsin Husen")), hasProperty("name",equalTo("Rizwan Idrees"))));
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,10 @@
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<elasticsearch:node-client id="client" local="true"
|
||||
cluster-name="testCluster" http-enabled="false" />
|
||||
<!--<elasticsearch:node-client id="client" local="true" cluster-name="testCluster" http-enabled="false" />-->
|
||||
|
||||
<elasticsearch:transport-client id="client" cluster-name="elasticsearch" cluster-nodes="127.0.1.1:9300" />
|
||||
|
||||
|
||||
|
||||
</beans>
|
Loading…
x
Reference in New Issue
Block a user