diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/GeoPointField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/GeoPointField.java new file mode 100644 index 000000000..bdb8025bb --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/GeoPointField.java @@ -0,0 +1,28 @@ +/* + * 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.annotations; + +import java.lang.annotation.*; + +/** + * @author Artur Konczak + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface GeoPointField { + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/TransportClientFactoryBean.java b/src/main/java/org/springframework/data/elasticsearch/client/TransportClientFactoryBean.java index 9c78e45de..42b9158e3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/TransportClientFactoryBean.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/TransportClientFactoryBean.java @@ -46,6 +46,9 @@ public class TransportClientFactoryBean implements FactoryBean, private Boolean clientIgnoreClusterName; private String clientPingTimeout; private String clientNodesSamplerInterval; + private Boolean clientIgnoreClusterName; + private String clientPingTimeout; + private String clientNodesSamplerInterval; private TransportClient client; private Properties properties; static final String COLON = ":"; @@ -118,6 +121,8 @@ public class TransportClientFactoryBean implements FactoryBean, this.clusterName = clusterName; } + } + public void setClientTransportSniff(Boolean clientTransportSniff) { this.clientTransportSniff = clientTransportSniff; } @@ -146,6 +151,30 @@ public class TransportClientFactoryBean implements FactoryBean, this.clientIgnoreClusterName = clientIgnoreClusterName; } + public String getClientNodesSamplerInterval() { + return clientNodesSamplerInterval; + } + + public void setClientNodesSamplerInterval(String clientNodesSamplerInterval) { + this.clientNodesSamplerInterval = clientNodesSamplerInterval; + } + + public String getClientPingTimeout() { + return clientPingTimeout; + } + + public void setClientPingTimeout(String clientPingTimeout) { + this.clientPingTimeout = clientPingTimeout; + } + + public Boolean getClientIgnoreClusterName() { + return clientIgnoreClusterName; + } + + public void setClientIgnoreClusterName(Boolean clientIgnoreClusterName) { + this.clientIgnoreClusterName = clientIgnoreClusterName; + } + public void setProperties(Properties properties) { this.properties = properties; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java new file mode 100644 index 000000000..45f42d880 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaFilterProcessor.java @@ -0,0 +1,196 @@ +/* + * 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.GeoBox; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; +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 fbList = new LinkedList(); + FilterBuilder filter = null; + + ListIterator 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 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 createFilterFragmentForCriteria(Criteria chainedCriteria) { + Iterator it = chainedCriteria.getFilterCriteriaEntries().iterator(); + List filterList = new LinkedList(); + + 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 GeoPoint || valArray[0] instanceof String, "First element of a geo distance filter must be a GeoLocation or String"); + Assert.isTrue(valArray[1] instanceof String, "Second element of a geo distance filter must be a String"); + + String dist = (String) valArray[1]; + if (valArray[0] instanceof GeoPoint) { + GeoPoint loc = (GeoPoint) valArray[0]; + ((GeoDistanceFilterBuilder) filter).lat(loc.getLat()).lon(loc.getLon()).distance(dist); + } else { + String loc = (String) valArray[0]; + if (loc.contains(",")) { + String c[] = loc.split(","); + ((GeoDistanceFilterBuilder) filter).lat(Double.parseDouble(c[0])).lon(Double.parseDouble(c[1])).distance(dist); + } else { + ((GeoDistanceFilterBuilder) filter).geohash(loc).distance(dist); + } + + } + + break; + } + + case BBOX: { + filter = geoBoundingBoxFilter(fieldName); + + Assert.isTrue(value instanceof Object[], "Value of a bbox filter should be an array of one or two values."); + Object[] valArray = (Object[]) value; + Assert.noNullElements(valArray, "Geo bbox filter takes a not null element array as parameter."); + + if (valArray.length == 1) { + //GeoEnvelop + oneParameterBBox((GeoBoundingBoxFilterBuilder) filter, valArray[0]); + } else if (valArray.length == 2) { + //2x GeoPoint + //2x String + twoParameterBBox((GeoBoundingBoxFilterBuilder) filter, valArray); + } else { + //error + Assert.isTrue(false, "Geo distance filter takes a 1-elements array(GeoEnvelop) or 2-elements array(GeoPoints or Strings(geohash))."); + } + break; + } + + } + + return filter; + } + + private void oneParameterBBox(GeoBoundingBoxFilterBuilder filter, Object value) { + Assert.isTrue(value instanceof GeoBox, "single-element of a geo bbox filter must be type of GeoEnvelop"); + GeoBox geoBBox = (GeoBox) value; + filter.topLeft(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon()); + filter.bottomRight(geoBBox.getBottomRight().getLat(), geoBBox.getBottomRight().getLon()); + } + + private static boolean isType(Object[] array, Class clazz) { + for (Object o : array) { + if (!clazz.isInstance(o)) { + return false; + } + } + return true; + } + + private void twoParameterBBox(GeoBoundingBoxFilterBuilder filter, Object[] values) { + Assert.isTrue(isType(values, GeoPoint.class) || isType(values, String.class), " both elements of geo bbox filter must be type of GeoPoint or String(geohash)"); + if (values[0] instanceof GeoPoint) { + GeoPoint topLeft = (GeoPoint) values[0]; + GeoPoint bottomRight = (GeoPoint) values[1]; + filter.topLeft(topLeft.getLat(), topLeft.getLon()); + filter.bottomRight(bottomRight.getLat(), bottomRight.getLon()); + } else { + String topLeft = (String) values[0]; + String bottomRight = (String) values[1]; + filter.topLeft(topLeft); + filter.bottomRight(bottomRight); + } + } + + private List buildNegationFilter(String fieldName, Iterator it) { + List notFilterList = new LinkedList(); + + while (it.hasNext()) { + Criteria.CriteriaEntry criteriaEntry = it.next(); + FilterBuilder notFilter = notFilter(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName)); + notFilterList.add(notFilter); + } + + return notFilterList; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java index 0da61b4d0..d33259183 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java @@ -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,106 +34,141 @@ 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(); - ListIterator 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)); - } - } - return query; - } + QueryBuilder createQueryFromCriteria(Criteria criteria) { + if(criteria == null) + return null; - private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) { - Iterator it = chainedCriteria.getCriteriaEntries().iterator(); - boolean singeEntryCriteria = (chainedCriteria.getCriteriaEntries().size() == 1); + List shouldQueryBuilderList = new LinkedList(); + List mustNotQueryBuilderList = new LinkedList(); + List mustQueryBuilderList = new LinkedList(); - 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)); - } - } + ListIterator chainIterator = criteria.getCriteriaChain().listIterator(); + while (chainIterator.hasNext()) { + Criteria chainedCriteria = chainIterator.next(); + QueryBuilder queryFragmentForCriteria = createQueryFragmentForCriteria(chainedCriteria); - addBoost(query, chainedCriteria.getBoost()); - return query; - } + if(queryFragmentForCriteria!=null) { + 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) { - if (value == null) { - return null; - } - QueryBuilder query = null; + BoolQueryBuilder 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 collection = (Iterable) value; - for (Object item : collection) { - ((BoolQueryBuilder) query).should(fieldQuery(fieldName, item)); - } - break; - } + if(!shouldQueryBuilderList.isEmpty() || !mustNotQueryBuilderList.isEmpty() || !mustQueryBuilderList.isEmpty()) { - return query; - } + query = boolQuery(); - private QueryBuilder buildNegationQuery(String fieldName, Iterator it) { - BoolQueryBuilder notQuery = boolQuery(); - while (it.hasNext()) { - notQuery.mustNot(fieldQuery(fieldName, it.next().getValue())); - } - return notQuery; - } + for(QueryBuilder qb : shouldQueryBuilderList) { + query.should(qb); + } + for(QueryBuilder qb : mustNotQueryBuilderList) { + query.mustNot(qb); + } + for(QueryBuilder qb : mustQueryBuilderList) { + query.must(qb); + } + } - private void addBoost(QueryBuilder query, float boost) { - if (Float.isNaN(boost)) { - return; - } - if (query instanceof BoostableQueryBuilder) { - ((BoostableQueryBuilder) query).boost(boost); - } + return query; + } - } + + private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) { + if(chainedCriteria.getQueryCriteriaEntries().isEmpty()) + return null; + + Iterator 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 collection = (Iterable) value; + for (Object item : collection) { + ((BoolQueryBuilder) query).should(fieldQuery(fieldName, item)); + } + break; + } + + return query; + } + + private QueryBuilder buildNegationQuery(String fieldName, Iterator 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); + } + + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java index 8ef4700db..bae6e6848 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java @@ -134,6 +134,16 @@ public interface ElasticsearchOperations { */ List queryForList(StringQuery query, Class clazz); + /** + * Execute the search query against elasticsearch and return result as {@link List} + * + * @param query + * @param clazz + * @param + * @return + */ + List queryForList(SearchQuery query, Class clazz); + /** * Execute the query against elasticsearch and return ids * diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 544c92c5e..87976af0b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -37,7 +37,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; @@ -171,6 +173,11 @@ public class ElasticsearchTemplate implements ElasticsearchOperations { return queryForPage(query, clazz).getContent(); } + @Override + public List queryForList(SearchQuery query, Class clazz) { + return queryForPage(query, clazz).getContent(); + } + @Override public List queryForIds(SearchQuery query) { SearchRequestBuilder request = prepareSearch(query).setQuery(query.getQuery()).setNoFields(); @@ -183,8 +190,21 @@ public class ElasticsearchTemplate implements ElasticsearchOperations { @Override public Page queryForPage(CriteriaQuery criteriaQuery, Class 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()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java index ee43c9b99..cc6de328f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java @@ -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.GeoPoint; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -47,6 +48,7 @@ class MappingBuilder { public static final String INDEX_VALUE_NOT_ANALYZED = "not_analyzed"; public static final String TYPE_VALUE_STRING = "string"; public static final String TYPE_VALUE_OBJECT = "object"; + public static final String TYPE_VALUE_GEO_POINT = "geo_point"; private static SimpleTypeHolder SIMPLE_TYPE_HOLDER = new SimpleTypeHolder(); @@ -71,8 +73,15 @@ class MappingBuilder { if (isEntity(field)) { mapEntity(xContentBuilder, field.getType(), false, EMPTY, field.getName()); } + Field singleField = field.getAnnotation(Field.class); MultiField multiField = field.getAnnotation(MultiField.class); + GeoPointField geoPoint = field.getAnnotation(GeoPointField.class); + + if (field.getType() == GeoPoint.class || geoPoint != null) { + applyGeoPointFieldMapping(xContentBuilder, field); + } + if (isRootObject && singleField != null && isIdField(field, idFieldName)) { applyDefaultIdFieldMapping(xContentBuilder, field); } else if (multiField != null) { @@ -88,6 +97,12 @@ class MappingBuilder { } + private static void applyGeoPointFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field) throws IOException { + xContentBuilder.startObject(field.getName()); + xContentBuilder.field(FIELD_TYPE, TYPE_VALUE_GEO_POINT) + .endObject(); + } + private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field) throws IOException { xContentBuilder.startObject(field.getName()) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoBox.java b/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoBox.java new file mode 100644 index 000000000..c8317b094 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoBox.java @@ -0,0 +1,41 @@ +/* + * 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 bbox used for #{@link org.springframework.data.elasticsearch.core.query.Criteria}. + * + * @author Franck Marchand + */ +public class GeoBox { + + private GeoPoint topLeft; + private GeoPoint bottomRight; + + public GeoBox(GeoPoint topLeft, GeoPoint bottomRight) { + this.topLeft = topLeft; + this.bottomRight = bottomRight; + } + + public GeoPoint getTopLeft() { + return topLeft; + } + + public GeoPoint getBottomRight() { + return bottomRight; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java b/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java new file mode 100644 index 000000000..75eae5ce1 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/geo/GeoPoint.java @@ -0,0 +1,45 @@ +/* + * 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 GeoPoint { + + private double lat; + private double lon; + + private GeoPoint() { + //required by mapper to instantiate object + } + + public GeoPoint(double latitude, double longitude) { + this.lat = latitude; + this.lon = longitude; + } + + public double getLat() { + return lat; + } + + public double getLon() { + return lon; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index f0abaa8f2..2bb79698f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -1,102 +1,102 @@ -/* - * 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.query; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.util.Assert; - -import java.util.ArrayList; -import java.util.List; - -import static org.apache.commons.collections.CollectionUtils.addAll; - -/** - * AbstractQuery - * - * @author Rizwan Idrees - * @author Mohsin Husen - */ -abstract class AbstractQuery implements Query { - - protected Pageable pageable = DEFAULT_PAGE; - protected Sort sort; - protected List indices = new ArrayList(); - protected List types = new ArrayList(); - protected List fields = new ArrayList(); - - @Override - public Sort getSort() { - return this.sort; - } - - @Override - public Pageable getPageable() { - return this.pageable; - } - - @Override - public final T setPageable(Pageable pageable) { - Assert.notNull(pageable); - this.pageable = pageable; - return (T) this.addSort(pageable.getSort()); - } - - @Override - public void addFields(String... fields) { - addAll(this.fields, fields); - } - - @Override - public List getFields() { - return fields; - } - - @Override - public List getIndices() { - return indices; - } - - @Override - public void addIndices(String... indices) { - addAll(this.indices, indices); - } - - @Override - public void addTypes(String... types) { - addAll(this.types, types); - } - - @Override - public List getTypes() { - return types; - } - - @SuppressWarnings("unchecked") - public final T addSort(Sort sort) { - if (sort == null) { - return (T) this; - } - - if (this.sort == null) { - this.sort = sort; - } else { - this.sort = this.sort.and(sort); - } - - return (T) this; - } -} +/* + * 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.query; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.List; + +import static org.apache.commons.collections.CollectionUtils.addAll; + +/** + * AbstractQuery + * + * @author Rizwan Idrees + * @author Mohsin Husen + */ +abstract class AbstractQuery implements Query { + + protected Pageable pageable = DEFAULT_PAGE; + protected Sort sort; + protected List indices = new ArrayList(); + protected List types = new ArrayList(); + protected List fields = new ArrayList(); + + @Override + public Sort getSort() { + return this.sort; + } + + @Override + public Pageable getPageable() { + return this.pageable; + } + + @Override + public final T setPageable(Pageable pageable) { + Assert.notNull(pageable); + this.pageable = pageable; + return (T) this.addSort(pageable.getSort()); + } + + @Override + public void addFields(String... fields) { + addAll(this.fields, fields); + } + + @Override + public List getFields() { + return fields; + } + + @Override + public List getIndices() { + return indices; + } + + @Override + public void addIndices(String... indices) { + addAll(this.indices, indices); + } + + @Override + public void addTypes(String... types) { + addAll(this.types, types); + } + + @Override + public List getTypes() { + return types; + } + + @SuppressWarnings("unchecked") + public final T addSort(Sort sort) { + if (sort == null) { + return (T) this; + } + + if (this.sort == null) { + this.sort = sort; + } else { + this.sort = this.sort.and(sort); + } + + return (T) this; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java index f375eb2d8..c46cb8e84 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java @@ -25,426 +25,514 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.elasticsearch.core.geo.GeoBox; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.util.Assert; /** * Criteria is the central class when constructing queries. It follows more or less a fluent API style, which allows to * easily chain together multiple criteria. - * + * * @author Rizwan Idrees * @author Mohsin Husen + * @author Franck Marchand */ public class Criteria { - public static final String WILDCARD = "*"; - public static final String CRITERIA_VALUE_SEPERATOR = " "; + public static final String WILDCARD = "*"; + public static final String CRITERIA_VALUE_SEPERATOR = " "; - private static final String OR_OPERATOR = " OR "; - private static final String AND_OPERATOR = " AND "; + private static final String OR_OPERATOR = " OR "; + private static final String AND_OPERATOR = " AND "; - private Field field; - private float boost = Float.NaN; - private boolean negating = false; + private Field field; + private float boost = Float.NaN; + private boolean negating = false; - private List criteriaChain = new ArrayList(1); + private List criteriaChain = new ArrayList(1); - private Set criteria = new LinkedHashSet(); + private Set queryCriteria = new LinkedHashSet(); - public Criteria() { - } + private Set filterCriteria = new LinkedHashSet(); - /** - * Creates a new CriterSimpleFieldia for the Filed with provided name - * - * @param fieldname - */ - public Criteria(String fieldname) { - this(new SimpleField(fieldname)); - } + public Criteria() { + } - /** - * Creates a new Criteria for the given field - * - * @param field - */ - public Criteria(Field field) { - Assert.notNull(field, "Field for criteria must not be null"); - Assert.hasText(field.getName(), "Field.name for criteria must not be null/empty"); + /** + * Creates a new CriterSimpleFieldia for the Filed with provided name + * + * @param fieldname + */ + public Criteria(String fieldname) { + this(new SimpleField(fieldname)); + } - this.criteriaChain.add(this); - this.field = field; - } + /** + * Creates a new Criteria for the given field + * + * @param field + */ + public Criteria(Field field) { + Assert.notNull(field, "Field for criteria must not be null"); + Assert.hasText(field.getName(), "Field.name for criteria must not be null/empty"); - protected Criteria(List criteriaChain, String fieldname) { - this(criteriaChain, new SimpleField(fieldname)); - } + this.criteriaChain.add(this); + this.field = field; + } - protected Criteria(List criteriaChain, Field field) { - Assert.notNull(criteriaChain, "CriteriaChain must not be null"); - Assert.notNull(field, "Field for criteria must not be null"); - Assert.hasText(field.getName(), "Field.name for criteria must not be null/empty"); + protected Criteria(List criteriaChain, String fieldname) { + this(criteriaChain, new SimpleField(fieldname)); + } - this.criteriaChain.addAll(criteriaChain); - this.criteriaChain.add(this); - this.field = field; - } + protected Criteria(List criteriaChain, Field field) { + Assert.notNull(criteriaChain, "CriteriaChain must not be null"); + Assert.notNull(field, "Field for criteria must not be null"); + Assert.hasText(field.getName(), "Field.name for criteria must not be null/empty"); - /** - * Static factory method to create a new Criteria for field with given name - * - * @param field - * @return - */ - public static Criteria where(String field) { - return where(new SimpleField(field)); - } + this.criteriaChain.addAll(criteriaChain); + this.criteriaChain.add(this); + this.field = field; + } - /** - * Static factory method to create a new Criteria for provided field - * - * @param field - * @return - */ - public static Criteria where(Field field) { - return new Criteria(field); - } + /** + * Static factory method to create a new Criteria for field with given name + * + * @param field + * @return + */ + public static Criteria where(String field) { + return where(new SimpleField(field)); + } - /** - * Chain using {@code AND} - * - * @param field - * @return - */ - public Criteria and(Field field) { - return new Criteria(this.criteriaChain, field); - } + /** + * Static factory method to create a new Criteria for provided field + * + * @param field + * @return + */ + public static Criteria where(Field field) { + return new Criteria(field); + } - /** - * Chain using {@code AND} - * - * @param fieldName - * @return - */ - public Criteria and(String fieldName) { - return new Criteria(this.criteriaChain, fieldName); - } + /** + * Chain using {@code AND} + * + * @param field + * @return + */ + public Criteria and(Field field) { + return new Criteria(this.criteriaChain, field); + } - /** - * Chain using {@code AND} - * - * @param criteria - * @return - */ - public Criteria and(Criteria criteria) { - this.criteriaChain.add(criteria); - return this; - } + /** + * Chain using {@code AND} + * + * @param fieldName + * @return + */ + public Criteria and(String fieldName) { + return new Criteria(this.criteriaChain, fieldName); + } - /** - * Chain using {@code AND} - * - * @param criterias - * @return - */ - public Criteria and(Criteria... criterias) { - this.criteriaChain.addAll(Arrays.asList(criterias)); - return this; - } + /** + * Chain using {@code AND} + * + * @param criteria + * @return + */ + public Criteria and(Criteria criteria) { + this.criteriaChain.add(criteria); + return this; + } - /** - * Chain using {@code OR} - * - * @param field - * @return - */ - public Criteria or(Field field) { - return new OrCriteria(this.criteriaChain, field); - } + /** + * Chain using {@code AND} + * + * @param criterias + * @return + */ + public Criteria and(Criteria... criterias) { + this.criteriaChain.addAll(Arrays.asList(criterias)); + return this; + } - /** - * Chain using {@code OR} - * - * @param criteria - * @return - */ - public Criteria or(Criteria criteria) { - Assert.notNull(criteria, "Cannot chain 'null' criteria."); + /** + * Chain using {@code OR} + * + * @param field + * @return + */ + public Criteria or(Field field) { + return new OrCriteria(this.criteriaChain, field); + } - Criteria orConnectedCritiera = new OrCriteria(this.criteriaChain, criteria.getField()); - orConnectedCritiera.criteria.addAll(criteria.criteria); - return orConnectedCritiera; - } + /** + * Chain using {@code OR} + * + * @param criteria + * @return + */ + public Criteria or(Criteria criteria) { + Assert.notNull(criteria, "Cannot chain 'null' criteria."); - /** - * Chain using {@code OR} - * - * @param fieldName - * @return - */ - public Criteria or(String fieldName) { - return or(new SimpleField(fieldName)); - } + Criteria orConnectedCritiera = new OrCriteria(this.criteriaChain, criteria.getField()); + orConnectedCritiera.queryCriteria.addAll(criteria.queryCriteria); + return orConnectedCritiera; + } - /** - * Crates new CriteriaEntry without any wildcards - * - * @param o - * @return - */ - public Criteria is(Object o) { - criteria.add(new CriteriaEntry(OperationKey.EQUALS, o)); - return this; - } + /** + * Chain using {@code OR} + * + * @param fieldName + * @return + */ + public Criteria or(String fieldName) { + return or(new SimpleField(fieldName)); + } - /** - * Crates new CriteriaEntry with leading and trailing wildcards
- * NOTE: mind your schema as leading wildcards may not be supported and/or execution might be slow. - * - * @param s - * @return - */ - public Criteria contains(String s) { - assertNoBlankInWildcardedQuery(s, true, true); - criteria.add(new CriteriaEntry(OperationKey.CONTAINS, s)); - return this; - } + /** + * Crates new CriteriaEntry without any wildcards + * + * @param o + * @return + */ + public Criteria is(Object o) { + queryCriteria.add(new CriteriaEntry(OperationKey.EQUALS, o)); + return this; + } - /** - * Crates new CriteriaEntry with trailing wildcard - * - * @param s - * @return - */ - public Criteria startsWith(String s) { - assertNoBlankInWildcardedQuery(s, true, false); - criteria.add(new CriteriaEntry(OperationKey.STARTS_WITH, s)); - return this; - } + /** + * Crates new CriteriaEntry with leading and trailing wildcards
+ * NOTE: mind your schema as leading wildcards may not be supported and/or execution might be slow. + * + * @param s + * @return + */ + public Criteria contains(String s) { + assertNoBlankInWildcardedQuery(s, true, true); + queryCriteria.add(new CriteriaEntry(OperationKey.CONTAINS, s)); + return this; + } - /** - * Crates new CriteriaEntry with leading wildcard
- * NOTE: mind your schema and execution times as leading wildcards may not be supported. - * - * @param s - * @return - */ - public Criteria endsWith(String s) { - assertNoBlankInWildcardedQuery(s, false, true); - criteria.add(new CriteriaEntry(OperationKey.ENDS_WITH, s)); - return this; - } + /** + * Crates new CriteriaEntry with trailing wildcard + * + * @param s + * @return + */ + public Criteria startsWith(String s) { + assertNoBlankInWildcardedQuery(s, true, false); + queryCriteria.add(new CriteriaEntry(OperationKey.STARTS_WITH, s)); + return this; + } - /** - * Crates new CriteriaEntry with trailing - - * - * @return - */ - public Criteria not() { - this.negating = true; - return this; - } + /** + * Crates new CriteriaEntry with leading wildcard
+ * NOTE: mind your schema and execution times as leading wildcards may not be supported. + * + * @param s + * @return + */ + public Criteria endsWith(String s) { + assertNoBlankInWildcardedQuery(s, false, true); + queryCriteria.add(new CriteriaEntry(OperationKey.ENDS_WITH, s)); + return this; + } - /** - * Crates new CriteriaEntry with trailing ~ - * - * @param s - * @return - */ - public Criteria fuzzy(String s) { - criteria.add(new CriteriaEntry(OperationKey.FUZZY, s)); - return this; - } + /** + * Crates new CriteriaEntry with trailing - + * + * @return + */ + public Criteria not() { + this.negating = true; + return this; + } - /** - * Crates new CriteriaEntry allowing native elasticsearch expressions - * - * @param s - * @return - */ - public Criteria expression(String s) { - criteria.add(new CriteriaEntry(OperationKey.EXPRESSION, s)); - return this; - } + /** + * Crates new CriteriaEntry with trailing ~ + * + * @param s + * @return + */ + public Criteria fuzzy(String s) { + queryCriteria.add(new CriteriaEntry(OperationKey.FUZZY, s)); + return this; + } - /** - * Boost positive hit with given factor. eg. ^2.3 - * - * @param boost - * @return - */ - public Criteria boost(float boost) { - if (boost < 0) { - throw new InvalidDataAccessApiUsageException("Boost must not be negative."); - } - this.boost = boost; - return this; - } + /** + * Crates new CriteriaEntry allowing native elasticsearch expressions + * + * @param s + * @return + */ + public Criteria expression(String s) { + queryCriteria.add(new CriteriaEntry(OperationKey.EXPRESSION, s)); + return this; + } - /** - * Crates new CriteriaEntry for {@code RANGE [lowerBound TO upperBound]} - * - * @param lowerBound - * @param upperBound - * @return - */ - public Criteria between(Object lowerBound, Object upperBound) { - if (lowerBound == null && upperBound == null) { - throw new InvalidDataAccessApiUsageException("Range [* TO *] is not allowed"); - } + /** + * Boost positive hit with given factor. eg. ^2.3 + * + * @param boost + * @return + */ + public Criteria boost(float boost) { + if (boost < 0) { + throw new InvalidDataAccessApiUsageException("Boost must not be negative."); + } + this.boost = boost; + return this; + } - criteria.add(new CriteriaEntry(OperationKey.BETWEEN, new Object[] { lowerBound, upperBound })); - return this; - } + /** + * Crates new CriteriaEntry for {@code RANGE [lowerBound TO upperBound]} + * + * @param lowerBound + * @param upperBound + * @return + */ + public Criteria between(Object lowerBound, Object upperBound) { + if (lowerBound == null && upperBound == null) { + throw new InvalidDataAccessApiUsageException("Range [* TO *] is not allowed"); + } - /** - * Crates new CriteriaEntry for {@code RANGE [* TO upperBound]} - * - * @param upperBound - * @return - */ - public Criteria lessThanEqual(Object upperBound) { - between(null, upperBound); - return this; - } + queryCriteria.add(new CriteriaEntry(OperationKey.BETWEEN, new Object[]{lowerBound, upperBound})); + return this; + } - /** - * Crates new CriteriaEntry for {@code RANGE [lowerBound TO *]} - * - * @param lowerBound - * @return - */ - public Criteria greaterThanEqual(Object lowerBound) { - between(lowerBound, null); - return this; - } + /** + * Crates new CriteriaEntry for {@code RANGE [* TO upperBound]} + * + * @param upperBound + * @return + */ + public Criteria lessThanEqual(Object upperBound) { + between(null, upperBound); + return this; + } - /** - * Crates new CriteriaEntry for multiple values {@code (arg0 arg1 arg2 ...)} - * - * @param values - * @return - */ - public Criteria in(Object... values) { - if (values.length == 0 || (values.length > 1 && values[1] instanceof Collection)) { - throw new InvalidDataAccessApiUsageException("At least one element " - + (values.length > 0 ? ("of argument of type " + values[1].getClass().getName()) : "") - + " has to be present."); - } - return in(Arrays.asList(values)); - } + /** + * Crates new CriteriaEntry for {@code RANGE [lowerBound TO *]} + * + * @param lowerBound + * @return + */ + public Criteria greaterThanEqual(Object lowerBound) { + between(lowerBound, null); + return this; + } - /** - * Crates new CriteriaEntry for multiple values {@code (arg0 arg1 arg2 ...)} - * - * @param values the collection containing the values to match against - * @return - */ - public Criteria in(Iterable values) { - Assert.notNull(values, "Collection of 'in' values must not be null"); - criteria.add(new CriteriaEntry(OperationKey.IN, values)); - return this; - } + /** + * Crates new CriteriaEntry for multiple values {@code (arg0 arg1 arg2 ...)} + * + * @param values + * @return + */ + public Criteria in(Object... values) { + if (values.length == 0 || (values.length > 1 && values[1] instanceof Collection)) { + throw new InvalidDataAccessApiUsageException("At least one element " + + (values.length > 0 ? ("of argument of type " + values[1].getClass().getName()) : "") + + " has to be present."); + } + return in(Arrays.asList(values)); + } - private void assertNoBlankInWildcardedQuery(String searchString, boolean leadingWildcard, boolean trailingWildcard) { - if (StringUtils.contains(searchString, CRITERIA_VALUE_SEPERATOR)) { - throw new InvalidDataAccessApiUsageException("Cannot constructQuery '" + (leadingWildcard ? "*" : "") + "\"" - + searchString + "\"" + (trailingWildcard ? "*" : "") + "'. Use epxression or mulitple clauses instead."); - } - } + /** + * Crates new CriteriaEntry for multiple values {@code (arg0 arg1 arg2 ...)} + * + * @param values the collection containing the values to match against + * @return + */ + public Criteria in(Iterable values) { + Assert.notNull(values, "Collection of 'in' values must not be null"); + queryCriteria.add(new CriteriaEntry(OperationKey.IN, values)); + return this; + } - /** - * Field targeted by this Criteria - * - * @return - */ - public Field getField() { - return this.field; - } + /** + * Creates new CriteriaEntry for {@code location WITHIN distance} + * + * @param location {@link org.springframework.data.elasticsearch.core.geo.GeoPoint} 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(GeoPoint 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; + } - public Set getCriteriaEntries() { - return Collections.unmodifiableSet(this.criteria); - } + /** + * Creates new CriteriaEntry for {@code geoLocation WITHIN distance} + * + * @param geoLocation {@link String} center point + * supported formats: + * lat on = > "41.2,45.1", + * geohash = > "asd9as0d" + * @param distance {@link String} radius as a string (e.g. : '100km'). + * Distance unit : + * either mi/miles or km can be set + * @return + */ + public Criteria within(String geoLocation, String distance) { + Assert.isTrue(StringUtils.isNotBlank(geoLocation), "geoLocation value must not be null"); + filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[]{geoLocation, distance})); + return this; + } - /** - * Conjunction to be used with this criteria (AND | OR) - * - * @return - */ - public String getConjunctionOperator() { - return AND_OPERATOR; - } + /** + * Creates new CriteriaEntry for {@code location BBOX bounding box} + * + * @param bbox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner + right bottom corner) + * @return Criteria the chaind criteria with the new 'bbox' criteria included. + */ + public Criteria bbox(GeoBox bbox) { + Assert.notNull(bbox, "bbox value for bbox criteria must not be null"); + filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{bbox})); + return this; + } - public List getCriteriaChain() { - return Collections.unmodifiableList(this.criteriaChain); - } - public boolean isNegating() { - return this.negating; - } + /** + * Creates new CriteriaEntry for bounding box created from points + * + * @param topLeft left top corner of bounding box + * @param bottomRight right bottom corner of bounding box + * @return Criteria the chaind criteria with the new 'bbox' criteria included. + */ + public Criteria bbox(String topLeft, String bottomRight) { + Assert.isTrue(StringUtils.isNotBlank(topLeft), "topLeft point must not be empty"); + Assert.isTrue(StringUtils.isNotBlank(bottomRight), "bottomRight point must not be empty"); + filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{topLeft, bottomRight})); + return this; + } - public boolean isAnd() { - return AND_OPERATOR == getConjunctionOperator(); - } + /** + * Creates new CriteriaEntry for bounding box created from points + * + * @param topLeft left top corner of bounding box + * @param bottomRight right bottom corner of bounding box + * @return Criteria the chaind criteria with the new 'bbox' criteria included. + */ + public Criteria bbox(GeoPoint topLeft, GeoPoint bottomRight) { + Assert.notNull(topLeft, "topLeft point must not be null"); + Assert.notNull(bottomRight, "bottomRight point must not be null"); + filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{topLeft, bottomRight})); + return this; + } - public boolean isOr() { - return OR_OPERATOR == getConjunctionOperator(); - } + private void assertNoBlankInWildcardedQuery(String searchString, boolean leadingWildcard, boolean trailingWildcard) { + if (StringUtils.contains(searchString, CRITERIA_VALUE_SEPERATOR)) { + throw new InvalidDataAccessApiUsageException("Cannot constructQuery '" + (leadingWildcard ? "*" : "") + "\"" + + searchString + "\"" + (trailingWildcard ? "*" : "") + "'. Use epxression or mulitple clauses instead."); + } + } - public float getBoost() { - return this.boost; - } + /** + * Field targeted by this Criteria + * + * @return + */ + public Field getField() { + return this.field; + } - static class OrCriteria extends Criteria { + public Set getQueryCriteriaEntries() { + return Collections.unmodifiableSet(this.queryCriteria); + } - public OrCriteria() { - super(); - } + public Set getFilterCriteriaEntries() { + return Collections.unmodifiableSet(this.filterCriteria); + } - public OrCriteria(Field field) { - super(field); - } + public Set getFilterCriteria() { + return filterCriteria; + } - public OrCriteria(List criteriaChain, Field field) { - super(criteriaChain, field); - } + /** + * Conjunction to be used with this criteria (AND | OR) + * + * @return + */ + public String getConjunctionOperator() { + return AND_OPERATOR; + } - public OrCriteria(List criteriaChain, String fieldname) { - super(criteriaChain, fieldname); - } + public List getCriteriaChain() { + return Collections.unmodifiableList(this.criteriaChain); + } - public OrCriteria(String fieldname) { - super(fieldname); - } + public boolean isNegating() { + return this.negating; + } - @Override - public String getConjunctionOperator() { - return OR_OPERATOR; - } + public boolean isAnd() { + return AND_OPERATOR == getConjunctionOperator(); + } - } + public boolean isOr() { + return OR_OPERATOR == getConjunctionOperator(); + } - public enum OperationKey { - EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH, EXPRESSION, BETWEEN, FUZZY, IN; - } + public float getBoost() { + return this.boost; + } - public static class CriteriaEntry { + static class OrCriteria extends Criteria { - private OperationKey key; - private Object value; + public OrCriteria() { + super(); + } - CriteriaEntry(OperationKey key, Object value) { - this.key = key; - this.value = value; - } + public OrCriteria(Field field) { + super(field); + } - public OperationKey getKey() { - return key; - } + public OrCriteria(List criteriaChain, Field field) { + super(criteriaChain, field); + } - public Object getValue() { - return value; - } + public OrCriteria(List criteriaChain, String fieldname) { + super(criteriaChain, fieldname); + } - } + public OrCriteria(String fieldname) { + super(fieldname); + } + + @Override + public String getConjunctionOperator() { + return OR_OPERATOR; + } + + } + + public enum OperationKey { + EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH, EXPRESSION, BETWEEN, FUZZY, IN, WITHIN, BBOX, NEAR; + } + + public static class CriteriaEntry { + + private OperationKey key; + private Object value; + + CriteriaEntry(OperationKey key, Object value) { + this.key = key; + this.value = value; + } + + public OperationKey getKey() { + return key; + } + + public Object getValue() { + return value; + } + + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java index 734e710d2..8dfa99de8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/CriteriaQuery.java @@ -34,48 +34,48 @@ public class CriteriaQuery extends AbstractQuery { public CriteriaQuery(Criteria criteria) { 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) { - this.criteria = criteria; - this.pageable = pageable; - if (pageable != null) { - this.addSort(pageable.getSort()); - } - } + public static final Query fromQuery(CriteriaQuery source) { + return fromQuery(source, new CriteriaQuery()); + } - public static final Query fromQuery(CriteriaQuery source) { - return fromQuery(source, new CriteriaQuery()); - } + public static T fromQuery(CriteriaQuery source, T destination) { + if (source == null || destination == null) { + return null; + } - public static T fromQuery(CriteriaQuery source, T destination) { - if (source == null || destination == null) { - return null; - } + if (source.getCriteria() != null) { + destination.addCriteria(source.getCriteria()); + } - if (source.getCriteria() != null) { - destination.addCriteria(source.getCriteria()); - } + if (source.getSort() != null) { + destination.addSort(source.getSort()); + } - if (source.getSort() != null) { - destination.addSort(source.getSort()); - } + return destination; + } - return destination; - } + @SuppressWarnings("unchecked") + public final 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 final 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; - } - - public Criteria getCriteria() { - return this.criteria; - } + public Criteria getCriteria() { + return this.criteria; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java index f95099c13..c125e5e12 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java @@ -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 diff --git a/src/test/java/org/springframework/data/elasticsearch/ArticleBuilder.java b/src/test/java/org/springframework/data/elasticsearch/ArticleBuilder.java deleted file mode 100644 index bba9a5844..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/ArticleBuilder.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.springframework.data.elasticsearch; - -import org.springframework.data.elasticsearch.core.query.IndexQuery; - -/** - * Simple type to test facets - */ -public class ArticleBuilder { - - private Article resutl; - - public ArticleBuilder(String id) { - resutl = new Article(id); - } - - public ArticleBuilder title(String title) { - resutl.setTitle(title); - return this; - } - - public ArticleBuilder addAuthor(String author) { - resutl.getAuthors().add(author); - return this; - } - - public ArticleBuilder addPublishedYear(Integer year) { - resutl.getPublishedYears().add(year); - return this; - } - - public ArticleBuilder score(int score) { - resutl.setScore(score); - return this; - } - - public Article build() { - return resutl; - } - - public IndexQuery buildIndex() { - IndexQuery indexQuery = new IndexQuery(); - indexQuery.setId(resutl.getId()); - indexQuery.setObject(resutl); - return indexQuery; - } - -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 8ade5078d..6cdc46204 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -49,6 +49,7 @@ import static org.junit.Assert.*; /** * @author Rizwan Idrees * @author Mohsin Husen + * @author Franck Marchand */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:elasticsearch-template-test.xml") @@ -57,12 +58,12 @@ public class ElasticsearchTemplateTests { @Autowired private ElasticsearchTemplate elasticsearchTemplate; - @Before - public void before() { + @Before + public void before(){ elasticsearchTemplate.deleteIndex(SampleEntity.class); - elasticsearchTemplate.createIndex(SampleEntity.class); - elasticsearchTemplate.refresh(SampleEntity.class, true); - } + elasticsearchTemplate.createIndex(SampleEntity.class); + elasticsearchTemplate.refresh(SampleEntity.class, true); + } @Test public void shouldReturnCountForGivenSearchQuery() { diff --git a/src/test/java/org/springframework/data/elasticsearch/Article.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntity.java similarity index 93% rename from src/test/java/org/springframework/data/elasticsearch/Article.java rename to src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntity.java index 2e5b120cd..6d6c1df1d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/Article.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntity.java @@ -1,4 +1,4 @@ -package org.springframework.data.elasticsearch; +package org.springframework.data.elasticsearch.core.facet; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.*; @@ -15,7 +15,7 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.Strin * Simple type to test facets */ @Document(indexName = "articles", type = "article", shards = 1, replicas = 0, refreshInterval = "-1", indexStoreType = "memory") -public class Article { +public class ArticleEntity { @Id private String id; @@ -36,11 +36,11 @@ public class Article { private int score; - public Article() { + private ArticleEntity(){ } - public Article(String id) { + public ArticleEntity(String id) { this.id = id; } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntityBuilder.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntityBuilder.java new file mode 100644 index 000000000..4b55b7328 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntityBuilder.java @@ -0,0 +1,47 @@ +package org.springframework.data.elasticsearch.core.facet; + +import org.springframework.data.elasticsearch.core.query.IndexQuery; + +/** + * Simple type to test facets + */ +public class ArticleEntityBuilder { + + private ArticleEntity result; + + public ArticleEntityBuilder(String id) { + result = new ArticleEntity(id); + } + + public ArticleEntityBuilder title(String title) { + result.setTitle(title); + return this; + } + + public ArticleEntityBuilder addAuthor(String author) { + result.getAuthors().add(author); + return this; + } + + public ArticleEntityBuilder addPublishedYear(Integer year) { + result.getPublishedYears().add(year); + return this; + } + + public ArticleEntityBuilder score(int score) { + result.setScore(score); + return this; + } + + public ArticleEntity build() { + return result; + } + + public IndexQuery buildIndex() { + IndexQuery indexQuery = new IndexQuery(); + indexQuery.setId(result.getId()); + indexQuery.setObject(result); + return indexQuery; + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateFacetTests.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateFacetTests.java similarity index 86% rename from src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateFacetTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateFacetTests.java index 47c29c16d..dda6f7f80 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateFacetTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateFacetTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch.core; +package org.springframework.data.elasticsearch.core.facet; import org.elasticsearch.index.query.FilterBuilders; import org.elasticsearch.search.facet.FacetBuilders; @@ -21,8 +21,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.elasticsearch.Article; -import org.springframework.data.elasticsearch.ArticleBuilder; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.FacetedPage; import org.springframework.data.elasticsearch.core.facet.request.NativeFacetRequest; import org.springframework.data.elasticsearch.core.facet.request.RangeFacetRequestBuilder; import org.springframework.data.elasticsearch.core.facet.request.TermFacetRequestBuilder; @@ -62,21 +62,21 @@ public class ElasticsearchTemplateFacetTests { @Before public void before() { - elasticsearchTemplate.deleteIndex(Article.class); - elasticsearchTemplate.createIndex(Article.class); - elasticsearchTemplate.putMapping(Article.class); - elasticsearchTemplate.refresh(Article.class, true); + elasticsearchTemplate.deleteIndex(ArticleEntity.class); + elasticsearchTemplate.createIndex(ArticleEntity.class); + elasticsearchTemplate.putMapping(ArticleEntity.class); + elasticsearchTemplate.refresh(ArticleEntity.class, true); - IndexQuery article1 = new ArticleBuilder("1").title("article four").addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addAuthor(MOHSIN_HUSEN).addAuthor(JONATHAN_YAN).score(10).buildIndex(); - IndexQuery article2 = new ArticleBuilder("2").title("article three").addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addAuthor(MOHSIN_HUSEN).addPublishedYear(YEAR_2000).score(20).buildIndex(); - IndexQuery article3 = new ArticleBuilder("3").title("article two").addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addPublishedYear(YEAR_2001).addPublishedYear(YEAR_2000).score(30).buildIndex(); - IndexQuery article4 = new ArticleBuilder("4").title("article one").addAuthor(RIZWAN_IDREES).addPublishedYear(YEAR_2002).addPublishedYear(YEAR_2001).addPublishedYear(YEAR_2000).score(40).buildIndex(); + IndexQuery article1 = new ArticleEntityBuilder("1").title("article four").addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addAuthor(MOHSIN_HUSEN).addAuthor(JONATHAN_YAN).score(10).buildIndex(); + IndexQuery article2 = new ArticleEntityBuilder("2").title("article three").addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addAuthor(MOHSIN_HUSEN).addPublishedYear(YEAR_2000).score(20).buildIndex(); + IndexQuery article3 = new ArticleEntityBuilder("3").title("article two").addAuthor(RIZWAN_IDREES).addAuthor(ARTUR_KONCZAK).addPublishedYear(YEAR_2001).addPublishedYear(YEAR_2000).score(30).buildIndex(); + IndexQuery article4 = new ArticleEntityBuilder("4").title("article one").addAuthor(RIZWAN_IDREES).addPublishedYear(YEAR_2002).addPublishedYear(YEAR_2001).addPublishedYear(YEAR_2000).score(40).buildIndex(); elasticsearchTemplate.index(article1); elasticsearchTemplate.index(article2); elasticsearchTemplate.index(article3); elasticsearchTemplate.index(article4); - elasticsearchTemplate.refresh(Article.class, true); + elasticsearchTemplate.refresh(ArticleEntity.class, true); } @Test @@ -86,7 +86,7 @@ public class ElasticsearchTemplateFacetTests { String facetName = "fauthors"; SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withFacet(new TermFacetRequestBuilder(facetName).fields("authors.untouched").build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -117,7 +117,7 @@ public class ElasticsearchTemplateFacetTests { .withFilter(FilterBuilders.notFilter(FilterBuilders.termFilter("title", "four"))) .withFacet(new TermFacetRequestBuilder(facetName).applyQueryFilter().fields("authors.untouched").build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(3))); @@ -143,7 +143,7 @@ public class ElasticsearchTemplateFacetTests { .withFilter(FilterBuilders.notFilter(FilterBuilders.termFilter("title", "four"))) .withFacet(new TermFacetRequestBuilder(facetName).applyQueryFilter().fields("authors.untouched").excludeTerms(RIZWAN_IDREES, ARTUR_KONCZAK).build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(3))); @@ -165,7 +165,7 @@ public class ElasticsearchTemplateFacetTests { SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withFacet(new TermFacetRequestBuilder(facetName).fields("authors.untouched").ascTerm().build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -196,7 +196,7 @@ public class ElasticsearchTemplateFacetTests { SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withFacet(new TermFacetRequestBuilder(facetName).fields("authors.untouched").ascCount().build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -226,7 +226,7 @@ public class ElasticsearchTemplateFacetTests { SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withFacet(new TermFacetRequestBuilder(facetName).fields("publishedYears").descCount().build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -256,7 +256,7 @@ public class ElasticsearchTemplateFacetTests { SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withFacet(new TermFacetRequestBuilder(facetName).fields("publishedYears", "authors.untouched").ascTerm().build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -305,7 +305,7 @@ public class ElasticsearchTemplateFacetTests { .withFacet(new TermFacetRequestBuilder(stringFacetName).fields("authors.untouched").ascTerm().build()) .build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -352,7 +352,7 @@ public class ElasticsearchTemplateFacetTests { SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withFacet(new NativeFacetRequest(FacetBuilders.termsFacet(facetName).field("publishedYears"))).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -381,7 +381,7 @@ public class ElasticsearchTemplateFacetTests { .withFilter(FilterBuilders.notFilter(FilterBuilders.termFilter("title", "four"))) .withFacet(new TermFacetRequestBuilder(facetName).applyQueryFilter().fields("authors.untouched").regex("Art.*").build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(3))); @@ -403,7 +403,7 @@ public class ElasticsearchTemplateFacetTests { .withFilter(FilterBuilders.notFilter(FilterBuilders.termFilter("title", "four"))) .withFacet(new TermFacetRequestBuilder(facetName).applyQueryFilter().fields("authors.untouched").allTerms().build()).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(3))); @@ -423,7 +423,7 @@ public class ElasticsearchTemplateFacetTests { .to(YEAR_2000).range(YEAR_2000, YEAR_2002).from(YEAR_2002).build() ).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); @@ -460,7 +460,7 @@ public class ElasticsearchTemplateFacetTests { .to(YEAR_2000).range(YEAR_2000, YEAR_2002).from(YEAR_2002).build() ).build(); // when - FacetedPage
result = elasticsearchTemplate.queryForPage(searchQuery, Article.class); + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); // then assertThat(result.getNumberOfElements(), is(equalTo(4))); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerAnnotatedEntity.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerAnnotatedEntity.java new file mode 100644 index 000000000..fad86fbca --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerAnnotatedEntity.java @@ -0,0 +1,77 @@ +/* + * 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 org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.GeoPointField; + +/** + * @author Franck Marchand + */ +@Document(indexName = "test-geo-index", type = "geo-annotation-point-type", indexStoreType = "memory", shards = 1, replicas = 0, refreshInterval = "-1") +public class AuthorMarkerAnnotatedEntity { + + @Id + private String id; + private String name; + + @GeoPointField + private String location; + + @GeoPointField + private double[] additionalLocation; + + private AuthorMarkerAnnotatedEntity() { + + } + + public AuthorMarkerAnnotatedEntity(String id) { + this.id = id; + } + + 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 String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public double[] getAdditionalLocation() { + return additionalLocation; + } + + public void setAdditionalLocation(double... additionalLocation) { + this.additionalLocation = additionalLocation; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerAnnotatedEntityBuilder.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerAnnotatedEntityBuilder.java new file mode 100644 index 000000000..2912d88f5 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerAnnotatedEntityBuilder.java @@ -0,0 +1,53 @@ +/* + * 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 org.springframework.data.elasticsearch.core.query.IndexQuery; + +public class AuthorMarkerAnnotatedEntityBuilder { + + private AuthorMarkerAnnotatedEntity result; + + public AuthorMarkerAnnotatedEntityBuilder(String id) { + result = new AuthorMarkerAnnotatedEntity(id); + } + + public AuthorMarkerAnnotatedEntityBuilder name(String name) { + result.setName(name); + return this; + } + + public AuthorMarkerAnnotatedEntityBuilder location(String location) { + result.setLocation(location); + return this; + } + + public AuthorMarkerAnnotatedEntityBuilder additionalLocation(double... location) { + result.setAdditionalLocation(location); + return this; + } + + public AuthorMarkerAnnotatedEntity build() { + return result; + } + + public IndexQuery buildIndex() { + IndexQuery indexQuery = new IndexQuery(); + indexQuery.setId(result.getId()); + indexQuery.setObject(result); + return indexQuery; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerEntity.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerEntity.java new file mode 100644 index 000000000..007e04955 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerEntity.java @@ -0,0 +1,64 @@ +/* + * 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 org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; + +/** + * @author Franck Marchand + */ +@Document(indexName = "test-geo-index", type = "geo-class-point-type", indexStoreType = "memory", shards = 1, replicas = 0, refreshInterval = "-1") +public class AuthorMarkerEntity { + + @Id + private String id; + private String name; + + private GeoPoint location; + + private AuthorMarkerEntity(){ + } + + public AuthorMarkerEntity(String id){ + this.id = id; + } + + 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 GeoPoint getLocation() { + return location; + } + + public void setLocation(GeoPoint location) { + this.location = location; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerEntityBuilder.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerEntityBuilder.java new file mode 100644 index 000000000..294b6fd8c --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/AuthorMarkerEntityBuilder.java @@ -0,0 +1,48 @@ +/* + * 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 org.springframework.data.elasticsearch.core.query.IndexQuery; + +public class AuthorMarkerEntityBuilder { + + private AuthorMarkerEntity result; + + public AuthorMarkerEntityBuilder(String id) { + result = new AuthorMarkerEntity(id); + } + + public AuthorMarkerEntityBuilder name(String name) { + result.setName(name); + return this; + } + + public AuthorMarkerEntityBuilder location(double latitude, double longitude) { + result.setLocation(new GeoPoint(latitude, longitude)); + return this; + } + + public AuthorMarkerEntity build() { + return result; + } + + public IndexQuery buildIndex() { + IndexQuery indexQuery = new IndexQuery(); + indexQuery.setId(result.getId()); + indexQuery.setObject(result); + return indexQuery; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java new file mode 100644 index 000000000..133f2ba6f --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java @@ -0,0 +1,234 @@ +/* + * 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 org.elasticsearch.index.query.FilterBuilders; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.query.Criteria; +import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertEquals; + +/** + * @author Rizwan Idrees + * @author Mohsin Husen + * @author Franck Marchand + * @author Artur Konczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:elasticsearch-template-test.xml") +public class ElasticsearchTemplateGeoTests { + + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; + + @Before + public void before() { + + } + + private void loadClassBaseEntities() { + elasticsearchTemplate.deleteIndex(AuthorMarkerEntity.class); + elasticsearchTemplate.createIndex(AuthorMarkerEntity.class); + elasticsearchTemplate.refresh(AuthorMarkerEntity.class, true); + elasticsearchTemplate.putMapping(AuthorMarkerEntity.class); + + List indexQueries = new ArrayList(); + indexQueries.add(new AuthorMarkerEntityBuilder("1").name("Franck Marchand").location(45.7806d, 3.0875d).buildIndex()); + indexQueries.add(new AuthorMarkerEntityBuilder("2").name("Mohsin Husen").location(51.5171d, 0.1062d).buildIndex()); + indexQueries.add(new AuthorMarkerEntityBuilder("3").name("Rizwan Idrees").location(51.5171d, 0.1062d).buildIndex()); + elasticsearchTemplate.bulkIndex(indexQueries); + elasticsearchTemplate.refresh(AuthorMarkerEntity.class, true); + } + + private void loadAnnotationBaseEntities() { + elasticsearchTemplate.deleteIndex(AuthorMarkerAnnotatedEntity.class); + elasticsearchTemplate.createIndex(AuthorMarkerAnnotatedEntity.class); + elasticsearchTemplate.refresh(AuthorMarkerAnnotatedEntity.class, true); + elasticsearchTemplate.putMapping(AuthorMarkerAnnotatedEntity.class); + + List indexQueries = new ArrayList(); + double[] latLonArray = {0.100000, 51.000000}; + String lonLatString = "51.000000, 0.100000"; + String geohash = "u1044k2bd6u"; + indexQueries.add(new AuthorMarkerAnnotatedEntityBuilder("2").name("Mohsin Husen").location(geohash.substring(0,5)).additionalLocation(latLonArray).buildIndex()); + indexQueries.add(new AuthorMarkerAnnotatedEntityBuilder("1").name("Artur Konczak").location(lonLatString).additionalLocation(latLonArray).buildIndex()); + indexQueries.add(new AuthorMarkerAnnotatedEntityBuilder("3").name("Rizwan Idrees").location(geohash).additionalLocation(latLonArray).buildIndex()); + + elasticsearchTemplate.bulkIndex(indexQueries); + elasticsearchTemplate.refresh(AuthorMarkerEntity.class, true); + } + + @Test + public void shouldPutMappingForGivenEntityWithGeoLocation() throws Exception { + //given + Class entity = AuthorMarkerEntity.class; + elasticsearchTemplate.createIndex(entity); + //when + assertThat(elasticsearchTemplate.putMapping(entity), is(true)); + } + + @Test + public void shouldFindAuthorMarkersInRangeForGivenCriteriaQuery() { + //given + loadClassBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery = new CriteriaQuery( + new Criteria("location").within(new GeoPoint(45.7806d, 3.0875d), "20km")); + //when + List geoAuthorsForGeoCriteria = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery, AuthorMarkerEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria.size(), is(1)); + assertEquals("Franck Marchand", geoAuthorsForGeoCriteria.get(0).getName()); + } + + @Test + public void shouldFindSelectedAuthorMarkerInRangeForGivenCriteriaQuery() { + //given + loadClassBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery2 = new CriteriaQuery( + new Criteria("name").is("Mohsin Husen").and("location").within(new GeoPoint(51.5171d, 0.1062d), "20km")); + //when + List geoAuthorsForGeoCriteria2 = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery2, AuthorMarkerEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria2.size(), is(1)); + assertEquals("Mohsin Husen", geoAuthorsForGeoCriteria2.get(0).getName()); + } + + @Test + public void shouldFindStringAnnotatedGeoMarkersInRangeForGivenCriteriaQuery() { + //given + loadAnnotationBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery = new CriteriaQuery( + new Criteria("location").within(new GeoPoint(51.000000, 0.100000), "1km")); + //when + List geoAuthorsForGeoCriteria = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery, AuthorMarkerAnnotatedEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria.size(), is(3)); + } + + @Test + public void shouldFindDoubleAnnotatedGeoMarkersInRangeForGivenCriteriaQuery() { + //given + loadAnnotationBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery = new CriteriaQuery( + new Criteria("additionalLocation").within(new GeoPoint(51.001000, 0.10100), "1km")); + //when + List geoAuthorsForGeoCriteria = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery, AuthorMarkerAnnotatedEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria.size(), is(3)); + } + + @Test + public void shouldFindAnnotatedGeoMarkersInRangeForGivenCriteriaQuery() { + //given + loadAnnotationBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery = new CriteriaQuery( + new Criteria("additionalLocation").within("51.001000, 0.10100", "1km")); + //when + List geoAuthorsForGeoCriteria = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery, AuthorMarkerAnnotatedEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria.size(), is(3)); + } + + @Test + public void shouldFindAnnotatedGeoMarkersInRangeForGivenCriteriaQueryUsingGeohashLocation() { + //given + loadAnnotationBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery = new CriteriaQuery( + new Criteria("additionalLocation").within("u1044", "1km")); + //when + List geoAuthorsForGeoCriteria = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery, AuthorMarkerAnnotatedEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria.size(), is(3)); + } + + @Test + public void shouldFindAllMarkersForNativeSearchQuery() { + //Given + loadAnnotationBaseEntities(); + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withFilter(FilterBuilders.geoBoundingBoxFilter("additionalLocation").topLeft("52, -1").bottomRight("50,1")); + //When + List geoAuthorsForGeoCriteria = elasticsearchTemplate.queryForList(queryBuilder.build(), AuthorMarkerAnnotatedEntity.class); + //Then + assertThat(geoAuthorsForGeoCriteria.size(), is(3)); + } + + @Test + public void shouldFindAuthorMarkersInBoxForGivenCriteriaQueryUsingGeoEnvelop() { + //given + loadClassBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery3 = new CriteriaQuery( + new Criteria("location").bbox( + new GeoBox(new GeoPoint(53.5171d, 0), + new GeoPoint(49.5171d, 0.2062d)))); + //when + List geoAuthorsForGeoCriteria3 = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery3, AuthorMarkerEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria3.size(), is(2)); + assertThat(geoAuthorsForGeoCriteria3, containsInAnyOrder(hasProperty("name", equalTo("Mohsin Husen")), hasProperty("name", equalTo("Rizwan Idrees")))); + } + + @Test + public void shouldFindAuthorMarkersInBoxForGivenCriteriaQueryUsingString() { + //given + loadClassBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery3 = new CriteriaQuery( + new Criteria("location").bbox("53.5171d, 0", "49.5171d, 0.2062d")); + //when + List geoAuthorsForGeoCriteria3 = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery3, AuthorMarkerEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria3.size(), is(2)); + assertThat(geoAuthorsForGeoCriteria3, containsInAnyOrder(hasProperty("name", equalTo("Mohsin Husen")), hasProperty("name", equalTo("Rizwan Idrees")))); + } + + @Test + public void shouldFindAuthorMarkersInBoxForGivenCriteriaQueryUsingGeoPoints() { + //given + loadClassBaseEntities(); + CriteriaQuery geoLocationCriteriaQuery3 = new CriteriaQuery( + new Criteria("location").bbox( + new GeoPoint(53.5171d, 0), + new GeoPoint(49.5171d, 0.2062d))); + //when + List geoAuthorsForGeoCriteria3 = elasticsearchTemplate.queryForList(geoLocationCriteriaQuery3, AuthorMarkerEntity.class); + + //then + assertThat(geoAuthorsForGeoCriteria3.size(), is(2)); + assertThat(geoAuthorsForGeoCriteria3, containsInAnyOrder(hasProperty("name", equalTo("Mohsin Husen")), hasProperty("name", equalTo("Rizwan Idrees")))); + } + +} diff --git a/src/test/resources/infrastructure.xml b/src/test/resources/infrastructure.xml index 747841218..7c17a094d 100644 --- a/src/test/resources/infrastructure.xml +++ b/src/test/resources/infrastructure.xml @@ -5,7 +5,8 @@ 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"> - + + + \ No newline at end of file