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,106 +34,141 @@ 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) {
BoolQueryBuilder query = boolQuery();
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator(); QueryBuilder createQueryFromCriteria(Criteria criteria) {
while (chainIterator.hasNext()) { if(criteria == null)
Criteria chainedCriteria = chainIterator.next(); return null;
if (chainedCriteria.isOr()) {
query.should(createQueryFragmentForCriteria(chainedCriteria));
} else if (chainedCriteria.isNegating()) {
query.mustNot(createQueryFragmentForCriteria(chainedCriteria));
} else {
query.must(createQueryFragmentForCriteria(chainedCriteria));
}
}
return query;
}
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) { List<QueryBuilder> shouldQueryBuilderList = new LinkedList<QueryBuilder>();
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getCriteriaEntries().iterator(); List<QueryBuilder> mustNotQueryBuilderList = new LinkedList<QueryBuilder>();
boolean singeEntryCriteria = (chainedCriteria.getCriteriaEntries().size() == 1); List<QueryBuilder> mustQueryBuilderList = new LinkedList<QueryBuilder>();
String fieldName = chainedCriteria.getField().getName();
Assert.notNull(fieldName, "Unknown field");
QueryBuilder query = null;
if (singeEntryCriteria) { ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
Criteria.CriteriaEntry entry = it.next(); while (chainIterator.hasNext()) {
query = processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName); Criteria chainedCriteria = chainIterator.next();
} else { QueryBuilder queryFragmentForCriteria = createQueryFragmentForCriteria(chainedCriteria);
query = boolQuery();
while (it.hasNext()) {
Criteria.CriteriaEntry entry = it.next();
((BoolQueryBuilder) query).must(processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName));
}
}
addBoost(query, chainedCriteria.getBoost()); if(queryFragmentForCriteria!=null) {
return query; if(chainedCriteria.isOr()){
} shouldQueryBuilderList.add(queryFragmentForCriteria);
}else if(chainedCriteria.isNegating()){
mustNotQueryBuilderList.add(queryFragmentForCriteria);
}else{
mustQueryBuilderList.add(queryFragmentForCriteria);
}
}
}
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) { BoolQueryBuilder query = null;
if (value == null) {
return null;
}
QueryBuilder query = null;
switch (key) { if(!shouldQueryBuilderList.isEmpty() || !mustNotQueryBuilderList.isEmpty() || !mustQueryBuilderList.isEmpty()) {
case EQUALS:
query = fieldQuery(fieldName, value);
break;
case CONTAINS:
query = fieldQuery(fieldName, "*" + value + "*").analyzeWildcard(true);
break;
case STARTS_WITH:
query = fieldQuery(fieldName, value + "*").analyzeWildcard(true);
break;
case ENDS_WITH:
query = fieldQuery(fieldName, "*" + value).analyzeWildcard(true);
break;
case EXPRESSION:
query = queryString((String) value).field(fieldName);
break;
case BETWEEN:
Object[] ranges = (Object[]) value;
query = rangeQuery(fieldName).from(ranges[0]).to(ranges[1]);
break;
case FUZZY:
query = fuzzyQuery(fieldName, (String) value);
break;
case IN:
query = boolQuery();
Iterable<Object> collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).should(fieldQuery(fieldName, item));
}
break;
}
return query; query = boolQuery();
}
private QueryBuilder buildNegationQuery(String fieldName, Iterator<Criteria.CriteriaEntry> it) { for(QueryBuilder qb : shouldQueryBuilderList) {
BoolQueryBuilder notQuery = boolQuery(); query.should(qb);
while (it.hasNext()) { }
notQuery.mustNot(fieldQuery(fieldName, it.next().getValue())); for(QueryBuilder qb : mustNotQueryBuilderList) {
} query.mustNot(qb);
return notQuery; }
} for(QueryBuilder qb : mustQueryBuilderList) {
query.must(qb);
}
}
private void addBoost(QueryBuilder query, float boost) { return query;
if (Float.isNaN(boost)) { }
return;
}
if (query instanceof BoostableQueryBuilder) {
((BoostableQueryBuilder) query).boost(boost);
}
}
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
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");
QueryBuilder query = null;
if(singeEntryCriteria){
Criteria.CriteriaEntry entry = it.next();
query = processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName);
}else{
query = boolQuery();
while (it.hasNext()){
Criteria.CriteriaEntry entry = it.next();
((BoolQueryBuilder)query).must(processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName));
}
}
addBoost(query, chainedCriteria.getBoost());
return query;
}
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
if (value == null) {
return null;
}
QueryBuilder query = null;
switch (key) {
case EQUALS:
query = fieldQuery(fieldName, value);
break;
case CONTAINS:
query = fieldQuery(fieldName, "*" + value + "*").analyzeWildcard(true);
break;
case STARTS_WITH:
query = fieldQuery(fieldName, value + "*").analyzeWildcard(true);
break;
case ENDS_WITH:
query = fieldQuery(fieldName, "*" + value).analyzeWildcard(true);
break;
case EXPRESSION:
query = queryString((String) value).field(fieldName);
break;
case BETWEEN:
Object[] ranges = (Object[]) value;
query = rangeQuery(fieldName).from(ranges[0]).to(ranges[1]);
break;
case FUZZY:
query = fuzzyQuery(fieldName, (String) value);
break;
case IN:
query = boolQuery();
Iterable<Object> collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).should(fieldQuery(fieldName, item));
}
break;
}
return query;
}
private QueryBuilder buildNegationQuery(String fieldName, Iterator<Criteria.CriteriaEntry> it) {
BoolQueryBuilder notQuery = boolQuery();
while (it.hasNext()) {
notQuery.mustNot(fieldQuery(fieldName, it.next().getValue()));
}
return notQuery;
}
private void addBoost(QueryBuilder query, float boost) {
if (Float.isNaN(boost)) {
return;
}
if (query instanceof BoostableQueryBuilder) {
((BoostableQueryBuilder) query).boost(boost);
}
}
} }

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

@ -1,102 +1,102 @@
/* /*
* Copyright 2013 the original author or authors. * Copyright 2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.elasticsearch.core.query; package org.springframework.data.elasticsearch.core.query;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.util.ArrayList; 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;
/** /**
* AbstractQuery * AbstractQuery
* *
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
*/ */
abstract class AbstractQuery implements Query { abstract class AbstractQuery implements Query {
protected Pageable pageable = DEFAULT_PAGE; protected Pageable pageable = DEFAULT_PAGE;
protected Sort sort; protected Sort sort;
protected List<String> indices = new ArrayList<String>(); protected List<String> indices = new ArrayList<String>();
protected List<String> types = new ArrayList<String>(); protected List<String> types = new ArrayList<String>();
protected List<String> fields = new ArrayList<String>(); protected List<String> fields = new ArrayList<String>();
@Override @Override
public Sort getSort() { public Sort getSort() {
return this.sort; return this.sort;
} }
@Override @Override
public Pageable getPageable() { public Pageable getPageable() {
return this.pageable; return this.pageable;
} }
@Override @Override
public final <T extends Query> T setPageable(Pageable pageable) { public final <T extends Query> T setPageable(Pageable pageable) {
Assert.notNull(pageable); Assert.notNull(pageable);
this.pageable = pageable; this.pageable = pageable;
return (T) this.addSort(pageable.getSort()); return (T) this.addSort(pageable.getSort());
} }
@Override @Override
public void addFields(String... fields) { public void addFields(String... fields) {
addAll(this.fields, fields); addAll(this.fields, fields);
} }
@Override @Override
public List<String> getFields() { public List<String> getFields() {
return fields; return fields;
} }
@Override @Override
public List<String> getIndices() { public List<String> getIndices() {
return indices; return indices;
} }
@Override @Override
public void addIndices(String... indices) { public void addIndices(String... indices) {
addAll(this.indices, indices); addAll(this.indices, indices);
} }
@Override @Override
public void addTypes(String... types) { public void addTypes(String... types) {
addAll(this.types, types); addAll(this.types, types);
} }
@Override @Override
public List<String> getTypes() { public List<String> getTypes() {
return types; return types;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final <T extends Query> T addSort(Sort sort) { public final <T extends Query> T addSort(Sort sort) {
if (sort == null) { if (sort == null) {
return (T) this; return (T) this;
} }
if (this.sort == null) { if (this.sort == null) {
this.sort = sort; this.sort = sort;
} else { } else {
this.sort = this.sort.and(sort); this.sort = this.sort.and(sort);
} }
return (T) this; return (T) this;
} }
} }

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,8 +256,8 @@ 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,9 +346,37 @@ 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; 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;
}
private void assertNoBlankInWildcardedQuery(String searchString, boolean leadingWildcard, boolean trailingWildcard) { private void assertNoBlankInWildcardedQuery(String searchString, boolean leadingWildcard, boolean trailingWildcard) {
if (StringUtils.contains(searchString, CRITERIA_VALUE_SEPERATOR)) { if (StringUtils.contains(searchString, CRITERIA_VALUE_SEPERATOR)) {
@ -361,10 +394,18 @@ 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;
}
/** /**
* Conjunction to be used with this criteria (AND | OR) * Conjunction to be used with this criteria (AND | OR)
* *
@ -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

@ -34,48 +34,48 @@ public class CriteriaQuery extends AbstractQuery {
public CriteriaQuery(Criteria criteria) { public CriteriaQuery(Criteria criteria) {
this(criteria, null); this(criteria, null);
} }
public CriteriaQuery(Criteria criteria, Pageable pageable) {
this.criteria = criteria;
this.pageable = pageable;
if (pageable != null) {
this.addSort(pageable.getSort());
}
}
public CriteriaQuery(Criteria criteria, Pageable pageable) { public static final Query fromQuery(CriteriaQuery source) {
this.criteria = criteria; return fromQuery(source, new CriteriaQuery());
this.pageable = pageable; }
if (pageable != null) {
this.addSort(pageable.getSort());
}
}
public static final Query fromQuery(CriteriaQuery source) { public static <T extends CriteriaQuery> T fromQuery(CriteriaQuery source, T destination) {
return fromQuery(source, new CriteriaQuery()); if (source == null || destination == null) {
} return null;
}
public static <T extends CriteriaQuery> T fromQuery(CriteriaQuery source, T destination) { if (source.getCriteria() != null) {
if (source == null || destination == null) { destination.addCriteria(source.getCriteria());
return null; }
}
if (source.getCriteria() != null) { if (source.getSort() != null) {
destination.addCriteria(source.getCriteria()); destination.addSort(source.getSort());
} }
if (source.getSort() != null) { return destination;
destination.addSort(source.getSort()); }
}
return destination; @SuppressWarnings("unchecked")
} public final <T extends CriteriaQuery> T addCriteria(Criteria criteria) {
Assert.notNull(criteria, "Cannot add null criteria.");
if (this.criteria == null) {
this.criteria = criteria;
} else {
this.criteria.and(criteria);
}
return (T) this;
}
@SuppressWarnings("unchecked") public Criteria getCriteria() {
public final <T extends CriteriaQuery> T addCriteria(Criteria criteria) { return this.criteria;
Assert.notNull(criteria, "Cannot add null criteria."); }
if (this.criteria == null) {
this.criteria = criteria;
} else {
this.criteria.and(criteria);
}
return (T) this;
}
public Criteria getCriteria() {
return this.criteria;
}
} }

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")
@ -56,12 +60,18 @@ public class ElasticsearchTemplateTests {
@Autowired @Autowired
private ElasticsearchTemplate elasticsearchTemplate; private ElasticsearchTemplate elasticsearchTemplate;
@Before @Before
public void before() { public void before(){
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
public void shouldReturnCountForGivenSearchQuery() { public void shouldReturnCountForGivenSearchQuery() {
@ -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>