DATAES-12 Merge Geo Location from branch 'master' of https://github.com/fmarchand/spring-data-elasticsearch

This commit is contained in:
Artur Konczak 2013-06-13 17:31:14 +01:00
parent aa6a5e3753
commit 74a4035408
15 changed files with 796 additions and 253 deletions

View File

@ -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;
}
}

View File

@ -22,6 +22,8 @@ import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import static org.elasticsearch.index.query.QueryBuilders.*; import static org.elasticsearch.index.query.QueryBuilders.*;
@ -32,29 +34,63 @@ import static org.springframework.data.elasticsearch.core.query.Criteria.Operati
* *
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Franck Marchand
*/ */
class CriteriaQueryProcessor { class CriteriaQueryProcessor {
QueryBuilder createQueryFromCriteria(Criteria criteria) { 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(); ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
while (chainIterator.hasNext()) { while (chainIterator.hasNext()) {
Criteria chainedCriteria = chainIterator.next(); Criteria chainedCriteria = chainIterator.next();
QueryBuilder queryFragmentForCriteria = createQueryFragmentForCriteria(chainedCriteria);
if(queryFragmentForCriteria!=null) {
if(chainedCriteria.isOr()){ if(chainedCriteria.isOr()){
query.should(createQueryFragmentForCriteria(chainedCriteria)); shouldQueryBuilderList.add(queryFragmentForCriteria);
}else if(chainedCriteria.isNegating()){ }else if(chainedCriteria.isNegating()){
query.mustNot(createQueryFragmentForCriteria(chainedCriteria)); mustNotQueryBuilderList.add(queryFragmentForCriteria);
}else{ }else{
query.must(createQueryFragmentForCriteria(chainedCriteria)); 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; return query;
} }
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) { private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getCriteriaEntries().iterator(); if(chainedCriteria.getQueryCriteriaEntries().isEmpty())
boolean singeEntryCriteria = (chainedCriteria.getCriteriaEntries().size() == 1); return null;
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getQueryCriteriaEntries().iterator();
boolean singeEntryCriteria = (chainedCriteria.getQueryCriteriaEntries().size() == 1);
String fieldName = chainedCriteria.getField().getName(); String fieldName = chainedCriteria.getField().getName();
Assert.notNull(fieldName,"Unknown field"); Assert.notNull(fieldName,"Unknown field");
@ -75,6 +111,7 @@ class CriteriaQueryProcessor {
return query; return query;
} }
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) { private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
if (value == null) { if (value == null) {
return null; return null;

View File

@ -34,7 +34,9 @@ import org.elasticsearch.client.Requests;
import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.facet.Facet; import org.elasticsearch.search.facet.Facet;
import org.elasticsearch.search.facet.FacetBuilder; import org.elasticsearch.search.facet.FacetBuilder;
@ -180,8 +182,21 @@ public class ElasticsearchTemplate implements ElasticsearchOperations {
@Override @Override
public <T> Page<T> queryForPage(CriteriaQuery criteriaQuery, Class<T> clazz) { public <T> Page<T> queryForPage(CriteriaQuery criteriaQuery, Class<T> clazz) {
QueryBuilder query = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria()); QueryBuilder elasticsearchQuery = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria());
SearchResponse response = prepareSearch(criteriaQuery, clazz).setQuery(query).execute().actionGet(); 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()); return mapResults(response, clazz, criteriaQuery.getPageable());
} }

View File

@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.data.elasticsearch.annotations.*; import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.facet.FacetRequest; 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.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
@ -71,6 +72,11 @@ class MappingBuilder {
if (isEntity(field)) { if (isEntity(field)) {
mapEntity(xContentBuilder, field.getType(), false, EMPTY, field.getName()); mapEntity(xContentBuilder, field.getType(), false, EMPTY, field.getName());
} }
if(field.getType() == GeoLocation.class) {
applyGeoLocationFieldMapping(xContentBuilder, field);
}
Field singleField = field.getAnnotation(Field.class); Field singleField = field.getAnnotation(Field.class);
MultiField multiField = field.getAnnotation(MultiField.class); MultiField multiField = field.getAnnotation(MultiField.class);
if (isRootObject && singleField != null && isIdField(field, idFieldName)) { 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) private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field)
throws IOException { throws IOException {
xContentBuilder.startObject(field.getName()) xContentBuilder.startObject(field.getName())

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -25,6 +25,8 @@ import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException; 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; import org.springframework.util.Assert;
/** /**
@ -33,6 +35,7 @@ import org.springframework.util.Assert;
* *
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Franck Marchand
*/ */
public class Criteria { public class Criteria {
@ -48,7 +51,9 @@ public class Criteria {
private List<Criteria> criteriaChain = new ArrayList<Criteria>(1); 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() { public Criteria() {
} }
@ -171,7 +176,7 @@ public class Criteria {
Assert.notNull(criteria, "Cannot chain 'null' criteria."); Assert.notNull(criteria, "Cannot chain 'null' criteria.");
Criteria orConnectedCritiera = new OrCriteria(this.criteriaChain, criteria.getField()); Criteria orConnectedCritiera = new OrCriteria(this.criteriaChain, criteria.getField());
orConnectedCritiera.criteria.addAll(criteria.criteria); orConnectedCritiera.queryCriteria.addAll(criteria.queryCriteria);
return orConnectedCritiera; return orConnectedCritiera;
} }
@ -192,7 +197,7 @@ public class Criteria {
* @return * @return
*/ */
public Criteria is(Object o) { public Criteria is(Object o) {
criteria.add(new CriteriaEntry(OperationKey.EQUALS, o)); queryCriteria.add(new CriteriaEntry(OperationKey.EQUALS, o));
return this; return this;
} }
@ -205,7 +210,7 @@ public class Criteria {
*/ */
public Criteria contains(String s) { public Criteria contains(String s) {
assertNoBlankInWildcardedQuery(s, true, true); assertNoBlankInWildcardedQuery(s, true, true);
criteria.add(new CriteriaEntry(OperationKey.CONTAINS, s)); queryCriteria.add(new CriteriaEntry(OperationKey.CONTAINS, s));
return this; return this;
} }
@ -217,7 +222,7 @@ public class Criteria {
*/ */
public Criteria startsWith(String s) { public Criteria startsWith(String s) {
assertNoBlankInWildcardedQuery(s, true, false); assertNoBlankInWildcardedQuery(s, true, false);
criteria.add(new CriteriaEntry(OperationKey.STARTS_WITH, s)); queryCriteria.add(new CriteriaEntry(OperationKey.STARTS_WITH, s));
return this; return this;
} }
@ -230,7 +235,7 @@ public class Criteria {
*/ */
public Criteria endsWith(String s) { public Criteria endsWith(String s) {
assertNoBlankInWildcardedQuery(s, false, true); assertNoBlankInWildcardedQuery(s, false, true);
criteria.add(new CriteriaEntry(OperationKey.ENDS_WITH, s)); queryCriteria.add(new CriteriaEntry(OperationKey.ENDS_WITH, s));
return this; return this;
} }
@ -251,7 +256,7 @@ public class Criteria {
* @return * @return
*/ */
public Criteria fuzzy(String s) { public Criteria fuzzy(String s) {
criteria.add(new CriteriaEntry(OperationKey.FUZZY, s)); queryCriteria.add(new CriteriaEntry(OperationKey.FUZZY, s));
return this; return this;
} }
@ -262,7 +267,7 @@ public class Criteria {
* @return * @return
*/ */
public Criteria expression(String s) { public Criteria expression(String s) {
criteria.add(new CriteriaEntry(OperationKey.EXPRESSION, s)); queryCriteria.add(new CriteriaEntry(OperationKey.EXPRESSION, s));
return this; return this;
} }
@ -292,7 +297,7 @@ public class Criteria {
throw new InvalidDataAccessApiUsageException("Range [* TO *] is not allowed"); 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; return this;
} }
@ -341,7 +346,35 @@ public class Criteria {
*/ */
public Criteria in(Iterable<?> values) { public Criteria in(Iterable<?> values) {
Assert.notNull(values, "Collection of 'in' values must not be null"); 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; return this;
} }
@ -361,8 +394,16 @@ public class Criteria {
return this.field; return this.field;
} }
public Set<CriteriaEntry> getCriteriaEntries() { public Set<CriteriaEntry> getQueryCriteriaEntries() {
return Collections.unmodifiableSet(this.criteria); 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 { 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 { public static class CriteriaEntry {

View File

@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.apache.commons.collections.CollectionUtils.addAll; 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 * MoreLikeThisQuery

View File

@ -17,7 +17,6 @@ package org.springframework.data.elasticsearch.core.query;
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.facet.FacetBuilder;
import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortBuilder;
import org.springframework.data.elasticsearch.core.facet.FacetRequest; import org.springframework.data.elasticsearch.core.facet.FacetRequest;

View File

@ -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;
}
}

View File

@ -25,11 +25,13 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.GeoAuthor;
import org.springframework.data.elasticsearch.SampleEntity; import org.springframework.data.elasticsearch.SampleEntity;
import org.springframework.data.elasticsearch.SampleMappingEntity; 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.data.elasticsearch.core.query.*;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 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.FilterBuilders.termFilter;
import static org.elasticsearch.index.query.QueryBuilders.fieldQuery; import static org.elasticsearch.index.query.QueryBuilders.fieldQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Franck Marchand
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:elasticsearch-template-test.xml") @ContextConfiguration("classpath:elasticsearch-template-test.xml")
@ -61,6 +65,12 @@ public class ElasticsearchTemplateTests {
elasticsearchTemplate.deleteIndex(SampleEntity.class); elasticsearchTemplate.deleteIndex(SampleEntity.class);
elasticsearchTemplate.createIndex(SampleEntity.class); elasticsearchTemplate.createIndex(SampleEntity.class);
elasticsearchTemplate.refresh(SampleEntity.class, true); elasticsearchTemplate.refresh(SampleEntity.class, true);
elasticsearchTemplate.deleteIndex(GeoAuthor.class);
elasticsearchTemplate.createIndex(GeoAuthor.class);
elasticsearchTemplate.refresh(GeoAuthor.class, true);
elasticsearchTemplate.putMapping(GeoAuthor.class);
} }
@Test @Test
@ -716,4 +726,85 @@ public class ElasticsearchTemplateTests {
assertThat(elasticsearchTemplate.indexExists(clazz), is(false)); 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"))));
}
} }

View File

@ -5,7 +5,10 @@
xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd 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"> http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<elasticsearch:node-client id="client" local="true" <!--<elasticsearch:node-client id="client" local="true" cluster-name="testCluster" http-enabled="false" />-->
cluster-name="testCluster" http-enabled="false" />
<elasticsearch:transport-client id="client" cluster-name="elasticsearch" cluster-nodes="127.0.1.1:9300" />
</beans> </beans>