basic mechanism for term facets

This commit is contained in:
akonczak
2013-05-20 09:47:26 +01:00
parent b421cb5044
commit b2572d4950
29 changed files with 1633 additions and 512 deletions

View File

@@ -20,20 +20,26 @@ import java.lang.annotation.*;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Field {
String type() default "";
String type() default "";
String index() default "";
String index() default "";
boolean store() default false;
boolean store() default false;
String searchAnalyzer() default "";
String searchAnalyzer() default "";
String indexAnalyzer() default "";
String indexAnalyzer() default "";
boolean facetable() default false;
boolean sortable() default false;
}

View File

@@ -84,7 +84,7 @@ public interface ElasticsearchOperations {
* @param clazz
* @return
*/
<T> Page<T> queryForPage(SearchQuery query, Class<T> clazz);
<T> FacetedPage<T> queryForPage(SearchQuery query, Class<T> clazz);
/**
* Execute the query against elasticsearch and return result as {@link Page}
@@ -93,7 +93,7 @@ public interface ElasticsearchOperations {
* @param resultsMapper
* @return
*/
<T> Page<T> queryForPage(SearchQuery query, ResultsMapper<T> resultsMapper);
<T> FacetedPage<T> queryForPage(SearchQuery query, ResultsMapper<T> resultsMapper);
/**
* Execute the query against elasticsearch and return result as {@link Page}
@@ -111,7 +111,7 @@ public interface ElasticsearchOperations {
* @param clazz
* @return
*/
<T> Page<T> queryForPage(StringQuery query, Class<T> clazz);
<T> FacetedPage<T> queryForPage(StringQuery query, Class<T> clazz);
/**
* Execute the criteria query against elasticsearch and return result as {@link List}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import org.apache.commons.collections.CollectionUtils;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
@@ -33,19 +34,20 @@ 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.search.SearchHit;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.facet.Facet;
import org.elasticsearch.search.facet.FacetBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.facet.FacetMapper;
import org.springframework.data.elasticsearch.core.facet.FacetResult;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.*;
@@ -69,443 +71,460 @@ import static org.springframework.data.elasticsearch.core.MappingBuilder.buildMa
/**
* ElasticsearchTemplate
*
*
* @author Rizwan Idrees
* @author Mohsin Husen
*/
public class ElasticsearchTemplate implements ElasticsearchOperations {
private Client client;
private ElasticsearchConverter elasticsearchConverter;
private ObjectMapper objectMapper = new ObjectMapper();
private Client client;
private ElasticsearchConverter elasticsearchConverter;
private ObjectMapper objectMapper = new ObjectMapper();
{
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
{
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public ElasticsearchTemplate(Client client) {
this(client, null);
}
public ElasticsearchTemplate(Client client) {
this(client, null);
}
public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter) {
this.client = client;
this.elasticsearchConverter = (elasticsearchConverter == null) ? new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext()) : elasticsearchConverter;
}
public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter) {
this.client = client;
this.elasticsearchConverter = (elasticsearchConverter == null) ? new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext()) : elasticsearchConverter;
}
@Override
public <T> boolean createIndex(Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
return createIndexIfNotCreated(clazz);
}
@Override
public <T> boolean createIndex(Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
return createIndexIfNotCreated(clazz);
}
@Override
public <T> boolean putMapping(Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
PutMappingRequestBuilder requestBuilder = client.admin().indices()
.preparePutMapping(persistentEntity.getIndexName()).setType(persistentEntity.getIndexType());
@Override
public <T> boolean putMapping(Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
PutMappingRequestBuilder requestBuilder = client.admin().indices()
.preparePutMapping(persistentEntity.getIndexName()).setType(persistentEntity.getIndexType());
try {
XContentBuilder xContentBuilder = buildMapping(clazz, persistentEntity.getIndexType(), persistentEntity
.getIdProperty().getFieldName());
return requestBuilder.setSource(xContentBuilder).execute().actionGet().isAcknowledged();
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
}
try {
XContentBuilder xContentBuilder = buildMapping(clazz, persistentEntity.getIndexType(), persistentEntity
.getIdProperty().getFieldName());
return requestBuilder.setSource(xContentBuilder).execute().actionGet().isAcknowledged();
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
}
@Override
public ElasticsearchConverter getElasticsearchConverter() {
return elasticsearchConverter;
}
@Override
public ElasticsearchConverter getElasticsearchConverter() {
return elasticsearchConverter;
}
@Override
public <T> T queryForObject(GetQuery query, Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
GetResponse response = client
.prepareGet(persistentEntity.getIndexName(), persistentEntity.getIndexType(), query.getId()).execute()
.actionGet();
return mapResult(response.getSourceAsString(), clazz);
}
@Override
public <T> T queryForObject(GetQuery query, Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
GetResponse response = client
.prepareGet(persistentEntity.getIndexName(), persistentEntity.getIndexType(), query.getId()).execute()
.actionGet();
return mapResult(response.getSourceAsString(), clazz);
}
@Override
public <T> T queryForObject(CriteriaQuery query, Class<T> clazz) {
Page<T> page = queryForPage(query, clazz);
Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results");
return page.getTotalElements() > 0 ? page.getContent().get(0) : null;
}
@Override
public <T> T queryForObject(CriteriaQuery query, Class<T> clazz) {
Page<T> page = queryForPage(query, clazz);
Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results");
return page.getTotalElements() > 0 ? page.getContent().get(0) : null;
}
@Override
public <T> T queryForObject(StringQuery query, Class<T> clazz) {
Page<T> page = queryForPage(query, clazz);
Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results");
return page.getTotalElements() > 0 ? page.getContent().get(0) : null;
}
@Override
public <T> T queryForObject(StringQuery query, Class<T> clazz) {
Page<T> page = queryForPage(query, clazz);
Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results");
return page.getTotalElements() > 0 ? page.getContent().get(0) : null;
}
@Override
public <T> Page<T> queryForPage(SearchQuery query, Class<T> clazz) {
SearchResponse response = doSearch(prepareSearch(query, clazz), query.getQuery(), query.getFilter(),
query.getElasticsearchSort());
return mapResults(response, clazz, query.getPageable());
}
@Override
public <T> FacetedPage<T> queryForPage(SearchQuery query, Class<T> clazz) {
SearchResponse response = doSearch(prepareSearch(query, clazz), query);
return mapResults(response, clazz, query.getPageable());
}
@Override
public <T> Page<T> queryForPage(SearchQuery query, ResultsMapper<T> resultsMapper) {
SearchResponse response = doSearch(prepareSearch(query), query.getQuery(), query.getFilter(),
query.getElasticsearchSort());
return resultsMapper.mapResults(response);
}
@Override
public <T> FacetedPage<T> queryForPage(SearchQuery query, ResultsMapper<T> resultsMapper) {
SearchResponse response = doSearch(prepareSearch(query), query);
return resultsMapper.mapResults(response);
}
@Override
public <T> List<T> queryForList(CriteriaQuery query, Class<T> clazz) {
return queryForPage(query, clazz).getContent();
}
@Override
public <T> List<T> queryForList(CriteriaQuery query, Class<T> clazz) {
return queryForPage(query, clazz).getContent();
}
@Override
public <T> List<T> queryForList(StringQuery query, Class<T> clazz) {
return queryForPage(query, clazz).getContent();
}
@Override
public <T> List<T> queryForList(StringQuery query, Class<T> clazz) {
return queryForPage(query, clazz).getContent();
}
@Override
public <T> List<String> queryForIds(SearchQuery query) {
SearchRequestBuilder request = prepareSearch(query).setQuery(query.getQuery()).setNoFields();
if (query.getFilter() != null) {
request.setFilter(query.getFilter());
}
SearchResponse response = request.execute().actionGet();
return extractIds(response);
}
@Override
public <T> List<String> queryForIds(SearchQuery query) {
SearchRequestBuilder request = prepareSearch(query).setQuery(query.getQuery()).setNoFields();
if (query.getFilter() != null) {
request.setFilter(query.getFilter());
}
SearchResponse response = request.execute().actionGet();
return extractIds(response);
}
@Override
public <T> Page<T> queryForPage(CriteriaQuery criteriaQuery, Class<T> clazz) {
QueryBuilder query = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria());
SearchResponse response = prepareSearch(criteriaQuery, clazz).setQuery(query).execute().actionGet();
return mapResults(response, clazz, criteriaQuery.getPageable());
}
@Override
public <T> Page<T> queryForPage(CriteriaQuery criteriaQuery, Class<T> clazz) {
QueryBuilder query = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria());
SearchResponse response = prepareSearch(criteriaQuery, clazz).setQuery(query).execute().actionGet();
return mapResults(response, clazz, criteriaQuery.getPageable());
}
@Override
public <T> Page<T> queryForPage(StringQuery query, Class<T> clazz) {
SearchResponse response = prepareSearch(query, clazz).setQuery(query.getSource()).execute().actionGet();
return mapResults(response, clazz, query.getPageable());
}
@Override
public <T> FacetedPage<T> queryForPage(StringQuery query, Class<T> clazz) {
SearchResponse response = prepareSearch(query, clazz).setQuery(query.getSource()).execute().actionGet();
return mapResults(response, clazz, query.getPageable());
}
@Override
public <T> long count(SearchQuery query, Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
CountRequestBuilder countRequestBuilder = client.prepareCount(persistentEntity.getIndexName()).setTypes(
persistentEntity.getIndexType());
if (query.getQuery() != null) {
countRequestBuilder.setQuery(query.getQuery());
}
return countRequestBuilder.execute().actionGet().getCount();
}
@Override
public <T> long count(SearchQuery query, Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
CountRequestBuilder countRequestBuilder = client.prepareCount(persistentEntity.getIndexName()).setTypes(
persistentEntity.getIndexType());
if (query.getQuery() != null) {
countRequestBuilder.setQuery(query.getQuery());
}
return countRequestBuilder.execute().actionGet().getCount();
}
@Override
public String index(IndexQuery query) {
return prepareIndex(query).execute().actionGet().getId();
}
@Override
public String index(IndexQuery query) {
return prepareIndex(query).execute().actionGet().getId();
}
@Override
public void bulkIndex(List<IndexQuery> queries) {
BulkRequestBuilder bulkRequest = client.prepareBulk();
for (IndexQuery query : queries) {
bulkRequest.add(prepareIndex(query));
}
BulkResponse bulkResponse = bulkRequest.execute().actionGet();
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<String, String>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new ElasticsearchException(
"Bulk indexing has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + "]", failedDocuments);
}
}
@Override
public void bulkIndex(List<IndexQuery> queries) {
BulkRequestBuilder bulkRequest = client.prepareBulk();
for (IndexQuery query : queries) {
bulkRequest.add(prepareIndex(query));
}
BulkResponse bulkResponse = bulkRequest.execute().actionGet();
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<String, String>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new ElasticsearchException(
"Bulk indexing has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + "]", failedDocuments);
}
}
@Override
public <T> boolean indexExists(Class<T> clazz) {
return indexExists(getPersistentEntityFor(clazz).getIndexName());
}
@Override
public <T> boolean indexExists(Class<T> clazz) {
return indexExists(getPersistentEntityFor(clazz).getIndexName());
}
@Override
public <T> boolean deleteIndex(Class<T> clazz) {
String indexName = getPersistentEntityFor(clazz).getIndexName();
if (indexExists(indexName)) {
return client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet().isAcknowledged();
}
return false;
}
@Override
public <T> boolean deleteIndex(Class<T> clazz) {
String indexName = getPersistentEntityFor(clazz).getIndexName();
if (indexExists(indexName)) {
return client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet().isAcknowledged();
}
return false;
}
@Override
public String delete(String indexName, String type, String id) {
return client.prepareDelete(indexName, type, id).execute().actionGet().getId();
}
@Override
public String delete(String indexName, String type, String id) {
return client.prepareDelete(indexName, type, id).execute().actionGet().getId();
}
@Override
public <T> String delete(Class<T> clazz, String id) {
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
return delete(persistentEntity.getIndexName(), persistentEntity.getIndexType(), id);
}
@Override
public <T> String delete(Class<T> clazz, String id) {
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
return delete(persistentEntity.getIndexName(), persistentEntity.getIndexType(), id);
}
@Override
public <T> void delete(DeleteQuery deleteQuery, Class<T> clazz) {
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
client.prepareDeleteByQuery(persistentEntity.getIndexName()).setTypes(persistentEntity.getIndexType())
.setQuery(deleteQuery.getQuery()).execute().actionGet();
}
@Override
public <T> void delete(DeleteQuery deleteQuery, Class<T> clazz) {
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
client.prepareDeleteByQuery(persistentEntity.getIndexName()).setTypes(persistentEntity.getIndexType())
.setQuery(deleteQuery.getQuery()).execute().actionGet();
}
@Override
public String scan(SearchQuery searchQuery, long scrollTimeInMillis, boolean noFields) {
Assert.notNull(searchQuery.getIndices(), "No index defined for Query");
Assert.notNull(searchQuery.getTypes(), "No type define for Query");
Assert.notNull(searchQuery.getPageable(), "Query.pageable is required for scan & scroll");
@Override
public String scan(SearchQuery searchQuery, long scrollTimeInMillis, boolean noFields) {
Assert.notNull(searchQuery.getIndices(), "No index defined for Query");
Assert.notNull(searchQuery.getTypes(), "No type define for Query");
Assert.notNull(searchQuery.getPageable(), "Query.pageable is required for scan & scroll");
SearchRequestBuilder requestBuilder = client.prepareSearch(toArray(searchQuery.getIndices())).setSearchType(SCAN)
.setQuery(searchQuery.getQuery()).setTypes(toArray(searchQuery.getTypes()))
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).setFrom(0)
.setSize(searchQuery.getPageable().getPageSize());
SearchRequestBuilder requestBuilder = client.prepareSearch(toArray(searchQuery.getIndices())).setSearchType(SCAN)
.setQuery(searchQuery.getQuery()).setTypes(toArray(searchQuery.getTypes()))
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).setFrom(0)
.setSize(searchQuery.getPageable().getPageSize());
if (searchQuery.getFilter() != null) {
requestBuilder.setFilter(searchQuery.getFilter());
}
if (searchQuery.getFilter() != null) {
requestBuilder.setFilter(searchQuery.getFilter());
}
if (noFields) {
requestBuilder.setNoFields();
}
return requestBuilder.execute().actionGet().getScrollId();
}
if (noFields) {
requestBuilder.setNoFields();
}
return requestBuilder.execute().actionGet().getScrollId();
}
@Override
public <T> Page<T> scroll(String scrollId, long scrollTimeInMillis, ResultsMapper<T> resultsMapper) {
SearchResponse response = client.prepareSearchScroll(scrollId)
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).execute().actionGet();
return resultsMapper.mapResults(response);
}
@Override
public <T> Page<T> scroll(String scrollId, long scrollTimeInMillis, ResultsMapper<T> resultsMapper) {
SearchResponse response = client.prepareSearchScroll(scrollId)
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).execute().actionGet();
return resultsMapper.mapResults(response);
}
@Override
public <T> Page<T> moreLikeThis(MoreLikeThisQuery query, Class<T> clazz) {
int startRecord = 0;
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
String indexName = isNotBlank(query.getIndexName()) ? query.getIndexName() : persistentEntity.getIndexName();
String type = isNotBlank(query.getType()) ? query.getType() : persistentEntity.getIndexType();
@Override
public <T> Page<T> moreLikeThis(MoreLikeThisQuery query, Class<T> clazz) {
int startRecord = 0;
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
String indexName = isNotBlank(query.getIndexName()) ? query.getIndexName() : persistentEntity.getIndexName();
String type = isNotBlank(query.getType()) ? query.getType() : persistentEntity.getIndexType();
Assert.notNull(indexName, "No 'indexName' defined for MoreLikeThisQuery");
Assert.notNull(type, "No 'type' defined for MoreLikeThisQuery");
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
Assert.notNull(indexName, "No 'indexName' defined for MoreLikeThisQuery");
Assert.notNull(type, "No 'type' defined for MoreLikeThisQuery");
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
MoreLikeThisRequestBuilder requestBuilder = client.prepareMoreLikeThis(indexName, type, query.getId());
MoreLikeThisRequestBuilder requestBuilder = client.prepareMoreLikeThis(indexName, type, query.getId());
if (query.getPageable() != null) {
startRecord = query.getPageable().getPageNumber() * query.getPageable().getPageSize();
requestBuilder.setSearchSize(query.getPageable().getPageSize());
}
requestBuilder.setSearchFrom(startRecord);
if (query.getPageable() != null) {
startRecord = query.getPageable().getPageNumber() * query.getPageable().getPageSize();
requestBuilder.setSearchSize(query.getPageable().getPageSize());
}
requestBuilder.setSearchFrom(startRecord);
if (isNotEmpty(query.getSearchIndices())) {
requestBuilder.setSearchIndices(toArray(query.getSearchIndices()));
}
if (isNotEmpty(query.getSearchTypes())) {
requestBuilder.setSearchTypes(toArray(query.getSearchTypes()));
}
if (isNotEmpty(query.getFields())) {
requestBuilder.setField(toArray(query.getFields()));
}
if (isNotBlank(query.getRouting())) {
requestBuilder.setRouting(query.getRouting());
}
if (query.getPercentTermsToMatch() != null) {
requestBuilder.setPercentTermsToMatch(query.getPercentTermsToMatch());
}
if (query.getMinTermFreq() != null) {
requestBuilder.setMinTermFreq(query.getMinTermFreq());
}
if (query.getMaxQueryTerms() != null) {
requestBuilder.maxQueryTerms(query.getMaxQueryTerms());
}
if (isNotEmpty(query.getStopWords())) {
requestBuilder.setStopWords(toArray(query.getStopWords()));
}
if (query.getMinDocFreq() != null) {
requestBuilder.setMinDocFreq(query.getMinDocFreq());
}
if (query.getMaxDocFreq() != null) {
requestBuilder.setMaxDocFreq(query.getMaxDocFreq());
}
if (query.getMinWordLen() != null) {
requestBuilder.setMinWordLen(query.getMinWordLen());
}
if (query.getMaxWordLen() != null) {
requestBuilder.setMaxWordLen(query.getMaxWordLen());
}
if (query.getBoostTerms() != null) {
requestBuilder.setBoostTerms(query.getBoostTerms());
}
if (isNotEmpty(query.getSearchIndices())) {
requestBuilder.setSearchIndices(toArray(query.getSearchIndices()));
}
if (isNotEmpty(query.getSearchTypes())) {
requestBuilder.setSearchTypes(toArray(query.getSearchTypes()));
}
if (isNotEmpty(query.getFields())) {
requestBuilder.setField(toArray(query.getFields()));
}
if (isNotBlank(query.getRouting())) {
requestBuilder.setRouting(query.getRouting());
}
if (query.getPercentTermsToMatch() != null) {
requestBuilder.setPercentTermsToMatch(query.getPercentTermsToMatch());
}
if (query.getMinTermFreq() != null) {
requestBuilder.setMinTermFreq(query.getMinTermFreq());
}
if (query.getMaxQueryTerms() != null) {
requestBuilder.maxQueryTerms(query.getMaxQueryTerms());
}
if (isNotEmpty(query.getStopWords())) {
requestBuilder.setStopWords(toArray(query.getStopWords()));
}
if (query.getMinDocFreq() != null) {
requestBuilder.setMinDocFreq(query.getMinDocFreq());
}
if (query.getMaxDocFreq() != null) {
requestBuilder.setMaxDocFreq(query.getMaxDocFreq());
}
if (query.getMinWordLen() != null) {
requestBuilder.setMinWordLen(query.getMinWordLen());
}
if (query.getMaxWordLen() != null) {
requestBuilder.setMaxWordLen(query.getMaxWordLen());
}
if (query.getBoostTerms() != null) {
requestBuilder.setBoostTerms(query.getBoostTerms());
}
SearchResponse response = requestBuilder.execute().actionGet();
return mapResults(response, clazz, query.getPageable());
}
SearchResponse response = requestBuilder.execute().actionGet();
return mapResults(response, clazz, query.getPageable());
}
private SearchResponse doSearch(SearchRequestBuilder searchRequest, QueryBuilder query, FilterBuilder filter,
SortBuilder sortBuilder) {
if (filter != null) {
searchRequest.setFilter(filter);
}
private SearchResponse doSearch(SearchRequestBuilder searchRequest, SearchQuery searchQuery) {
if (searchQuery.getFilter() != null) {
searchRequest.setFilter(searchQuery.getFilter());
}
if (sortBuilder != null) {
searchRequest.addSort(sortBuilder);
}
if (searchQuery.getElasticsearchSort() != null) {
searchRequest.addSort(searchQuery.getElasticsearchSort());
}
return searchRequest.setQuery(query).execute().actionGet();
}
if (CollectionUtils.isNotEmpty(searchQuery.getFacets())) {
for (FacetRequest facetRequest : searchQuery.getFacets()) {
FacetBuilder facet = facetRequest.getFacet();
if (facetRequest.applyQueryFilter() && searchQuery.getFilter() != null) {
facet.facetFilter(searchQuery.getFilter());
}
searchRequest.addFacet(facet);
}
}
private <T> boolean createIndexIfNotCreated(Class<T> clazz) {
return indexExists(getPersistentEntityFor(clazz).getIndexName()) || createIndexWithSettings(clazz);
}
return searchRequest.setQuery(searchQuery.getQuery()).execute().actionGet();
}
private boolean indexExists(String indexName) {
return client.admin().indices().exists(indicesExistsRequest(indexName)).actionGet().isExists();
}
private <T> boolean createIndexIfNotCreated(Class<T> clazz) {
return indexExists(getPersistentEntityFor(clazz).getIndexName()) || createIndexWithSettings(clazz);
}
private <T> boolean createIndexWithSettings(Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
return client.admin().indices()
.create(Requests.createIndexRequest(persistentEntity.getIndexName()).settings(getSettings(persistentEntity)))
.actionGet().isAcknowledged();
}
private boolean indexExists(String indexName) {
return client.admin().indices().exists(indicesExistsRequest(indexName)).actionGet().isExists();
}
private <T> Map getSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
return new MapBuilder<String, String>().put("index.number_of_shards", String.valueOf(persistentEntity.getShards()))
.put("index.number_of_replicas", String.valueOf(persistentEntity.getReplicas()))
.put("index.refresh_interval", persistentEntity.getRefreshInterval())
.put("index.store.type", persistentEntity.getIndexStoreType()).map();
}
private <T> boolean createIndexWithSettings(Class<T> clazz) {
ElasticsearchPersistentEntity<T> persistentEntity = getPersistentEntityFor(clazz);
return client.admin().indices()
.create(Requests.createIndexRequest(persistentEntity.getIndexName()).settings(getSettings(persistentEntity)))
.actionGet().isAcknowledged();
}
private <T> SearchRequestBuilder prepareSearch(Query query, Class<T> clazz) {
if (query.getIndices().isEmpty()) {
query.addIndices(retrieveIndexNameFromPersistentEntity(clazz));
}
if (query.getTypes().isEmpty()) {
query.addTypes(retrieveTypeFromPersistentEntity(clazz));
}
return prepareSearch(query);
}
private <T> Map getSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
return new MapBuilder<String, String>().put("index.number_of_shards", String.valueOf(persistentEntity.getShards()))
.put("index.number_of_replicas", String.valueOf(persistentEntity.getReplicas()))
.put("index.refresh_interval", persistentEntity.getRefreshInterval())
.put("index.store.type", persistentEntity.getIndexStoreType()).map();
}
private SearchRequestBuilder prepareSearch(Query query) {
Assert.notNull(query.getIndices(), "No index defined for Query");
Assert.notNull(query.getTypes(), "No type defined for Query");
private <T> SearchRequestBuilder prepareSearch(Query query, Class<T> clazz) {
if (query.getIndices().isEmpty()) {
query.addIndices(retrieveIndexNameFromPersistentEntity(clazz));
}
if (query.getTypes().isEmpty()) {
query.addTypes(retrieveTypeFromPersistentEntity(clazz));
}
return prepareSearch(query);
}
int startRecord = 0;
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(toArray(query.getIndices()))
.setSearchType(DFS_QUERY_THEN_FETCH).setTypes(toArray(query.getTypes()));
private SearchRequestBuilder prepareSearch(Query query) {
Assert.notNull(query.getIndices(), "No index defined for Query");
Assert.notNull(query.getTypes(), "No type defined for Query");
if (query.getPageable() != null) {
startRecord = query.getPageable().getPageNumber() * query.getPageable().getPageSize();
searchRequestBuilder.setSize(query.getPageable().getPageSize());
}
searchRequestBuilder.setFrom(startRecord);
int startRecord = 0;
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(toArray(query.getIndices()))
.setSearchType(DFS_QUERY_THEN_FETCH).setTypes(toArray(query.getTypes()));
if (!query.getFields().isEmpty()) {
searchRequestBuilder.addFields(toArray(query.getFields()));
}
if (query.getPageable() != null) {
startRecord = query.getPageable().getPageNumber() * query.getPageable().getPageSize();
searchRequestBuilder.setSize(query.getPageable().getPageSize());
}
searchRequestBuilder.setFrom(startRecord);
if (query.getSort() != null) {
for (Sort.Order order : query.getSort()) {
searchRequestBuilder.addSort(order.getProperty(), order.getDirection() == Sort.Direction.DESC ? SortOrder.DESC
: SortOrder.ASC);
}
}
return searchRequestBuilder;
}
if (!query.getFields().isEmpty()) {
searchRequestBuilder.addFields(toArray(query.getFields()));
}
private IndexRequestBuilder prepareIndex(IndexQuery query) {
try {
String indexName = isBlank(query.getIndexName()) ? retrieveIndexNameFromPersistentEntity(query.getObject()
.getClass())[0] : query.getIndexName();
String type = isBlank(query.getType()) ? retrieveTypeFromPersistentEntity(query.getObject().getClass())[0]
: query.getType();
if (query.getSort() != null) {
for (Sort.Order order : query.getSort()) {
searchRequestBuilder.addSort(order.getProperty(), order.getDirection() == Sort.Direction.DESC ? SortOrder.DESC
: SortOrder.ASC);
}
}
return searchRequestBuilder;
}
IndexRequestBuilder indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(
objectMapper.writeValueAsString(query.getObject()));
private IndexRequestBuilder prepareIndex(IndexQuery query) {
try {
String indexName = isBlank(query.getIndexName()) ? retrieveIndexNameFromPersistentEntity(query.getObject()
.getClass())[0] : query.getIndexName();
String type = isBlank(query.getType()) ? retrieveTypeFromPersistentEntity(query.getObject().getClass())[0]
: query.getType();
if (query.getVersion() != null) {
indexRequestBuilder.setVersion(query.getVersion());
indexRequestBuilder.setVersionType(EXTERNAL);
}
return indexRequestBuilder;
} catch (IOException e) {
throw new ElasticsearchException("failed to index the document [id: " + query.getId() + "]", e);
}
}
IndexRequestBuilder indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(
objectMapper.writeValueAsString(query.getObject()));
public void refresh(String indexName, boolean waitForOperation) {
client.admin().indices().refresh(refreshRequest(indexName).waitForOperations(waitForOperation)).actionGet();
}
if (query.getVersion() != null) {
indexRequestBuilder.setVersion(query.getVersion());
indexRequestBuilder.setVersionType(EXTERNAL);
}
return indexRequestBuilder;
} catch (IOException e) {
throw new ElasticsearchException("failed to index the document [id: " + query.getId() + "]", e);
}
}
public <T> void refresh(Class<T> clazz, boolean waitForOperation) {
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
client.admin().indices()
.refresh(refreshRequest(persistentEntity.getIndexName()).waitForOperations(waitForOperation)).actionGet();
}
public void refresh(String indexName, boolean waitForOperation) {
client.admin().indices().refresh(refreshRequest(indexName).waitForOperations(waitForOperation)).actionGet();
}
private ElasticsearchPersistentEntity getPersistentEntityFor(Class clazz) {
Assert.isTrue(clazz.isAnnotationPresent(Document.class), "Unable to identify index name. " + clazz.getSimpleName()
+ " is not a Document. Make sure the document class is annotated with @Document(indexName=\"foo\")");
return elasticsearchConverter.getMappingContext().getPersistentEntity(clazz);
}
public <T> void refresh(Class<T> clazz, boolean waitForOperation) {
ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz);
client.admin().indices()
.refresh(refreshRequest(persistentEntity.getIndexName()).waitForOperations(waitForOperation)).actionGet();
}
private String[] retrieveIndexNameFromPersistentEntity(Class clazz) {
return new String[] { getPersistentEntityFor(clazz).getIndexName() };
}
private ElasticsearchPersistentEntity getPersistentEntityFor(Class clazz) {
Assert.isTrue(clazz.isAnnotationPresent(Document.class), "Unable to identify index name. " + clazz.getSimpleName()
+ " is not a Document. Make sure the document class is annotated with @Document(indexName=\"foo\")");
return elasticsearchConverter.getMappingContext().getPersistentEntity(clazz);
}
private String[] retrieveTypeFromPersistentEntity(Class clazz) {
return new String[] { getPersistentEntityFor(clazz).getIndexType() };
}
private String[] retrieveIndexNameFromPersistentEntity(Class clazz) {
return new String[]{getPersistentEntityFor(clazz).getIndexName()};
}
private <T> Page<T> mapResults(SearchResponse response, final Class<T> elementType, final Pageable pageable) {
ResultsMapper<T> resultsMapper = new ResultsMapper<T>() {
@Override
public Page<T> mapResults(SearchResponse response) {
long totalHits = response.getHits().totalHits();
List<T> results = new ArrayList<T>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
results.add(mapResult(hit.sourceAsString(), elementType));
}
}
return new PageImpl<T>(results, pageable, totalHits);
}
};
return resultsMapper.mapResults(response);
}
private String[] retrieveTypeFromPersistentEntity(Class clazz) {
return new String[]{getPersistentEntityFor(clazz).getIndexType()};
}
private List<String> extractIds(SearchResponse response) {
List<String> ids = new ArrayList<String>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
ids.add(hit.getId());
}
}
return ids;
}
private <T> FacetedPage<T> mapResults(SearchResponse response, final Class<T> elementType, final Pageable pageable) {
ResultsMapper<T> resultsMapper = new ResultsMapper<T>() {
@Override
public FacetedPage<T> mapResults(SearchResponse response) {
long totalHits = response.getHits().totalHits();
List<T> results = new ArrayList<T>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
results.add(mapResult(hit.sourceAsString(), elementType));
}
}
List<FacetResult> facets = new ArrayList<FacetResult>();
if (response.getFacets() != null) {
for (Facet facet : response.getFacets()) {
FacetResult facetResult = FacetMapper.parse(facet);
if (facetResult != null) {
facets.add(facetResult);
}
}
}
private <T> T mapResult(String source, Class<T> clazz) {
if (isBlank(source)) {
return null;
}
try {
return objectMapper.readValue(source, clazz);
} catch (IOException e) {
throw new ElasticsearchException("failed to map source [ " + source + "] to class " + clazz.getSimpleName(), e);
}
}
return new FacetedPageImpl<T>(results, pageable, totalHits, facets);
}
};
return resultsMapper.mapResults(response);
}
private static String[] toArray(List<String> values) {
String[] valuesAsArray = new String[values.size()];
return values.toArray(valuesAsArray);
private List<String> extractIds(SearchResponse response) {
List<String> ids = new ArrayList<String>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
ids.add(hit.getId());
}
}
return ids;
}
}
private <T> T mapResult(String source, Class<T> clazz) {
if (isBlank(source)) {
return null;
}
try {
return objectMapper.readValue(source, clazz);
} catch (IOException e) {
throw new ElasticsearchException("failed to map source [ " + source + "] to class " + clazz.getSimpleName(), e);
}
}
private static String[] toArray(List<String> values) {
String[] valuesAsArray = new String[values.size()];
return values.toArray(valuesAsArray);
}
}

View File

@@ -0,0 +1,23 @@
package org.springframework.data.elasticsearch.core;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.facet.FacetResult;
import java.util.List;
/**
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
public interface FacetedPage<T> extends Page<T> {
boolean hasFacets();
List<FacetResult> getFacets();
FacetResult getFacet(String name);
}

View File

@@ -0,0 +1,55 @@
package org.springframework.data.elasticsearch.core;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.facet.FacetResult;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Container for query result and facet results
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
public class FacetedPageImpl<T> extends PageImpl<T> implements FacetedPage<T> {
private List<FacetResult> facets;
private Map<String, FacetResult> mapOfFacets = new HashMap<String, FacetResult>();
public FacetedPageImpl(List<T> content) {
super(content);
}
public FacetedPageImpl(List<T> content, Pageable pageable, long total) {
super(content, pageable, total);
}
public FacetedPageImpl(List<T> content, Pageable pageable, long total, List<FacetResult> facets) {
super(content, pageable, total);
this.facets = facets;
for (FacetResult facet : facets) {
mapOfFacets.put(facet.getName(), facet);
}
}
@Override
public boolean hasFacets() {
return CollectionUtils.isNotEmpty(facets);
}
@Override
public List<FacetResult> getFacets() {
return facets;
}
@Override
public FacetResult getFacet(String name) {
return mapOfFacets.get(name);
}
}

View File

@@ -0,0 +1,52 @@
package org.springframework.data.elasticsearch.core;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.facet.FacetResult;
import java.util.List;
import java.util.Map;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
public class FactedPageImpl<T> extends PageImpl<T> implements FacetedPage<T> {
private List<FacetResult> facets;
private Map<String, FacetResult> mapOfFacets;
public FactedPageImpl(List<T> content) {
super(content);
}
public FactedPageImpl(List<T> content, Pageable pageable, long total) {
super(content, pageable, total);
}
public FactedPageImpl(List<T> content, Pageable pageable, long total, List<FacetResult> facets) {
super(content, pageable, total);
this.facets = facets;
for (FacetResult facet : facets) {
mapOfFacets.put(facet.getName(), facet);
}
}
@Override
public boolean hasFacets() {
return CollectionUtils.isNotEmpty(facets);
}
@Override
public List<FacetResult> getFacets() {
return facets;
}
@Override
public FacetResult getFacet(String name) {
return mapOfFacets.get(name);
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.query.FacetRequest;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
@@ -31,90 +32,177 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
*/
class MappingBuilder {
private static SimpleTypeHolder SIMPLE_TYPE_HOLDER = new SimpleTypeHolder();
public static final String FIELD_STORE = "store";
public static final String FIELD_TYPE = "type";
public static final String FIELD_INDEX = "index";
public static final String FIELD_SEARCH_ANALYZER = "search_analyzer";
public static final String FIELD_INDEX_ANALYZER = "index_analyzer";
public static final String FIELD_PROPERTIES = "properties";
static XContentBuilder buildMapping(Class clazz, String indexType, String idFieldName) throws IOException {
XContentBuilder xContentBuilder = jsonBuilder().startObject().startObject(indexType).startObject("properties");
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";
mapEntity(xContentBuilder, clazz, true, idFieldName, EMPTY);
private static SimpleTypeHolder SIMPLE_TYPE_HOLDER = new SimpleTypeHolder();
return xContentBuilder.endObject().endObject().endObject();
}
static XContentBuilder buildMapping(Class clazz, String indexType, String idFieldName) throws IOException {
XContentBuilder xContentBuilder = jsonBuilder().startObject().startObject(indexType).startObject(FIELD_PROPERTIES);
private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, boolean isRootObject, String idFieldName,
String nestedObjectFieldName) throws IOException {
mapEntity(xContentBuilder, clazz, true, idFieldName, EMPTY);
java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
return xContentBuilder.endObject().endObject().endObject();
}
if (!isRootObject && isAnyPropertyAnnotatedAsField(fields)) {
xContentBuilder.startObject(nestedObjectFieldName).field("type", "object").startObject("properties");
}
private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, boolean isRootObject, String idFieldName,
String nestedObjectFieldName) throws IOException {
for (java.lang.reflect.Field field : fields) {
if (isEntity(field)) {
mapEntity(xContentBuilder, field.getType(), false, EMPTY, field.getName());
}
Field fieldAnnotation = field.getAnnotation(Field.class);
if (isRootObject && fieldAnnotation != null && isIdField(field, idFieldName)) {
applyDefaultIdFieldMapping(xContentBuilder, field);
} else if (fieldAnnotation != null) {
applyFieldAnnotationMapping(xContentBuilder, field, fieldAnnotation);
}
}
java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
if (!isRootObject && isAnyPropertyAnnotatedAsField(fields)) {
xContentBuilder.endObject().endObject();
}
if (!isRootObject && isAnyPropertyAnnotatedAsField(fields)) {
xContentBuilder.startObject(nestedObjectFieldName).field(FIELD_TYPE, TYPE_VALUE_OBJECT).startObject(FIELD_PROPERTIES);
}
}
for (java.lang.reflect.Field field : fields) {
if (isEntity(field)) {
mapEntity(xContentBuilder, field.getType(), false, EMPTY, field.getName());
}
Field fieldAnnotation = field.getAnnotation(Field.class);
if (isRootObject && fieldAnnotation != null && isIdField(field, idFieldName)) {
applyDefaultIdFieldMapping(xContentBuilder, field);
} else if (fieldAnnotation != null) {
if ((fieldAnnotation.sortable() || fieldAnnotation.facetable()) && TYPE_VALUE_STRING.equals(fieldAnnotation.type())) {
addMultiFieldMapping(xContentBuilder, field, fieldAnnotation);
} else {
addSimpleFieldMapping(xContentBuilder, field, fieldAnnotation);
}
}
}
private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field)
throws IOException {
xContentBuilder.startObject(field.getName()).field("type", "string").field("index", "not_analyzed").endObject();
}
if (!isRootObject && isAnyPropertyAnnotatedAsField(fields)) {
xContentBuilder.endObject().endObject();
}
private static void applyFieldAnnotationMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field,
Field fieldAnnotation) throws IOException {
xContentBuilder.startObject(field.getName());
xContentBuilder.field("store", fieldAnnotation.store());
if (isNotBlank(fieldAnnotation.type())) {
xContentBuilder.field("type", fieldAnnotation.type());
}
if (isNotBlank(fieldAnnotation.index())) {
xContentBuilder.field("index", fieldAnnotation.index());
}
if (isNotBlank(fieldAnnotation.searchAnalyzer())) {
xContentBuilder.field("search_analyzer", fieldAnnotation.searchAnalyzer());
}
if (isNotBlank(fieldAnnotation.indexAnalyzer())) {
xContentBuilder.field("index_analyzer", fieldAnnotation.indexAnalyzer());
}
xContentBuilder.endObject();
}
}
private static boolean isEntity(java.lang.reflect.Field field) {
TypeInformation typeInformation = ClassTypeInformation.from(field.getType());
TypeInformation<?> actualType = typeInformation.getActualType();
boolean isComplexType = actualType == null ? false : !SIMPLE_TYPE_HOLDER.isSimpleType(actualType.getType());
return isComplexType && !actualType.isCollectionLike() && !Map.class.isAssignableFrom(typeInformation.getType());
}
private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field)
throws IOException {
xContentBuilder.startObject(field.getName())
.field(FIELD_TYPE, TYPE_VALUE_STRING)
.field(FIELD_INDEX, INDEX_VALUE_NOT_ANALYZED);
xContentBuilder.endObject();
}
private static boolean isAnyPropertyAnnotatedAsField(java.lang.reflect.Field[] fields) {
if (fields != null) {
for (java.lang.reflect.Field field : fields) {
if (field.isAnnotationPresent(Field.class)) {
return true;
}
}
}
return false;
}
/**
* Apply mapping for a single @Field annotation
*
* @param xContentBuilder
* @param field
* @param fieldAnnotation
* @throws IOException
*/
private static void addSimpleFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field,
Field fieldAnnotation) throws IOException {
xContentBuilder.startObject(field.getName());
xContentBuilder.field(FIELD_STORE, fieldAnnotation.store());
if (isNotBlank(fieldAnnotation.type())) {
xContentBuilder.field(FIELD_TYPE, fieldAnnotation.type());
}
if (isNotBlank(fieldAnnotation.index())) {
xContentBuilder.field(FIELD_INDEX, fieldAnnotation.index());
}
if (isNotBlank(fieldAnnotation.searchAnalyzer())) {
xContentBuilder.field(FIELD_SEARCH_ANALYZER, fieldAnnotation.searchAnalyzer());
}
if (isNotBlank(fieldAnnotation.indexAnalyzer())) {
xContentBuilder.field(FIELD_INDEX_ANALYZER, fieldAnnotation.indexAnalyzer());
}
xContentBuilder.endObject();
}
private static boolean isIdField(java.lang.reflect.Field field, String idFieldName) {
return idFieldName.equals(field.getName());
}
/**
* Multi field mappings for string type fields, support for sorts and facets
*
* @param builder
* @param field
* @param annotation
* @throws IOException
*/
private static void addMultiFieldMapping(XContentBuilder builder, java.lang.reflect.Field field,
Field annotation) throws IOException {
builder.startObject(field.getName());
builder.field(FIELD_TYPE, "multi_field");
builder.startObject("fields");
//add standard field
addSimpleFieldMapping(builder, field, annotation);
//facet field - untouched, not analise, stored
if (annotation.facetable()) {
addFacetMapping(builder, field, annotation);
}
//sort field - lowercase, not analise, stored
if (annotation.sortable()) {
addSortMapping(builder, field, annotation);
}
builder.endObject();
builder.endObject();
}
/**
* Facet field for string type, for other types we don't need it(long, int, double, float)
*
* @param builder
* @param field
* @param annotation
* @throws IOException
*/
private static void addFacetMapping(XContentBuilder builder, java.lang.reflect.Field field, Field annotation) throws IOException {
builder.startObject(FacetRequest.FIELD_UNTOUCHED)
.field(FIELD_TYPE, TYPE_VALUE_STRING)
.field(FIELD_INDEX, INDEX_VALUE_NOT_ANALYZED)
.field(FIELD_STORE, true);
builder.endObject();
}
/**
* Sort field for string type, for other types we don't need it(long, int, double, float)
* value of the field should be converted to lowercase and not analise
*
* @param builder
* @param field
* @param annotation
* @throws IOException
*/
private static void addSortMapping(XContentBuilder builder, java.lang.reflect.Field field, Field annotation) throws IOException {
builder.startObject(FacetRequest.FIELD_SORT)
.field(FIELD_TYPE, TYPE_VALUE_STRING)
.field(FIELD_INDEX, "keyword")
.field(FIELD_STORE, true);
builder.endObject();
}
private static boolean isEntity(java.lang.reflect.Field field) {
TypeInformation typeInformation = ClassTypeInformation.from(field.getType());
TypeInformation<?> actualType = typeInformation.getActualType();
boolean isComplexType = actualType == null ? false : !SIMPLE_TYPE_HOLDER.isSimpleType(actualType.getType());
return isComplexType && !actualType.isCollectionLike() && !Map.class.isAssignableFrom(typeInformation.getType());
}
private static boolean isAnyPropertyAnnotatedAsField(java.lang.reflect.Field[] fields) {
if (fields != null) {
for (java.lang.reflect.Field field : fields) {
if (field.isAnnotationPresent(Field.class)) {
return true;
}
}
}
return false;
}
private static boolean isIdField(java.lang.reflect.Field field, String idFieldName) {
return idFieldName.equals(field.getName());
}
}

View File

@@ -25,11 +25,12 @@ import org.springframework.data.domain.Page;
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
*
*/
public interface ResultsMapper<T> {
Page<T> mapResults(SearchResponse response);
FacetedPage<T> mapResults(SearchResponse response);
}

View File

@@ -0,0 +1,31 @@
package org.springframework.data.elasticsearch.core.facet;
import org.springframework.util.Assert;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
public class AbstactFacetResult implements FacetResult {
private final String name;
private final FacetType type;
protected AbstactFacetResult(String name, FacetType type) {
Assert.hasText(name, "Facet name can't be null and should have a value");
this.name = name;
this.type = type;
}
@Override
public String getName() {
return name;
}
@Override
public FacetType getType() {
return type;
}
}

View File

@@ -0,0 +1,29 @@
package org.springframework.data.elasticsearch.core.facet;
import org.elasticsearch.search.facet.Facet;
import org.elasticsearch.search.facet.terms.TermsFacet;
import java.util.ArrayList;
import java.util.List;
/**
* @author Artur Konczak
*/
public class FacetMapper {
public static FacetResult parse(Facet facet){
if(facet instanceof TermsFacet){
return parseTerm((TermsFacet) facet);
}
return null;
}
private static FacetResult parseTerm(TermsFacet facet) {
List<Term> terms = new ArrayList<Term>();
for(TermsFacet.Entry entry:facet.getEntries()){
terms.add(new Term(entry.getTerm().toString(),entry.getCount()));
}
return new TermResult(facet.getName(),terms);
}
}

View File

@@ -0,0 +1,18 @@
package org.springframework.data.elasticsearch.core.facet;
/**
* Generic interface for all facets
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*
*/
public interface FacetResult {
String getName();
FacetType getType();
}

View File

@@ -0,0 +1,7 @@
package org.springframework.data.elasticsearch.core.facet;
public enum FacetType {
term, renage, histogram
}

View File

@@ -0,0 +1,28 @@
package org.springframework.data.elasticsearch.core.facet;
/**
* Single term
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
public class Term {
private String term;
private int count;
public Term(String term, int count) {
this.term = term;
this.count = count;
}
public String getTerm() {
return term;
}
public int getCount() {
return count;
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.data.elasticsearch.core.facet;
import org.springframework.util.Assert;
import java.util.List;
/**
* Basic term facet result
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
public class TermResult extends AbstactFacetResult {
private List<Term> terms;
public TermResult(String name, List<Term> terms) {
super(name, FacetType.term);
this.terms = terms;
}
public List<Term> getTerms() {
return terms;
}
}

View File

@@ -0,0 +1,30 @@
package org.springframework.data.elasticsearch.core.query;
import org.springframework.util.Assert;
/**
* @author Artur Konczak
*/
public abstract class AbstractFacetRequest implements FacetRequest {
private String name;
private boolean applyQueryFilter;
public AbstractFacetRequest(String name) {
Assert.hasText(name, "Facet can't be null or empty !!!");
this.name = name;
}
protected String getName(){
return name;
}
public void setApplyQueryFilter(boolean applyQueryFilter) {
this.applyQueryFilter = applyQueryFilter;
}
@Override
public boolean applyQueryFilter() {
return applyQueryFilter;
}
}

View File

@@ -0,0 +1,17 @@
package org.springframework.data.elasticsearch.core.query;
import org.elasticsearch.search.facet.FacetBuilder;
/**
* @author Artur Koczak
*/
public interface FacetRequest {
public static final String FIELD_UNTOUCHED = "untouched";
public static final String FIELD_SORT = "sort";
FacetBuilder getFacet();
boolean applyQueryFilter();
}

View File

@@ -0,0 +1,31 @@
package org.springframework.data.elasticsearch.core.query;
import org.elasticsearch.search.facet.FacetBuilder;
/**
* @author Artur Konczak
*/
public class NativeFacetRequest implements FacetRequest {
private FacetBuilder facet;
private boolean applyQueryFilter;
public NativeFacetRequest(FacetBuilder facet) {
this(facet, false);
}
public NativeFacetRequest(FacetBuilder facet, boolean applyQueryFilter) {
this.facet = facet;
this.applyQueryFilter = applyQueryFilter;
}
@Override
public FacetBuilder getFacet() {
return facet;
}
@Override
public boolean applyQueryFilter() {
return applyQueryFilter;
}
}

View File

@@ -17,8 +17,12 @@ package org.springframework.data.elasticsearch.core.query;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.facet.FacetBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import java.util.ArrayList;
import java.util.List;
/**
* NativeSearchQuery
*
@@ -31,6 +35,17 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
private QueryBuilder query;
private FilterBuilder filter;
private SortBuilder sort;
private List<FacetRequest> facets;
public NativeSearchQuery(QueryBuilder query) {
this.query = query;
}
public NativeSearchQuery(QueryBuilder query, FilterBuilder filter) {
this.query = query;
this.filter = filter;
}
public NativeSearchQuery(QueryBuilder query, FilterBuilder filter, SortBuilder sort) {
this.query = query;
@@ -49,4 +64,20 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
public SortBuilder getElasticsearchSort() {
return sort;
}
public void addFacet(FacetRequest facetRequest){
if(facets==null){
facets = new ArrayList<FacetRequest>();
}
facets.add(facetRequest);
}
public void setFacets(List<FacetRequest> facets){
this.facets = facets;
}
@Override
public List<FacetRequest> getFacets() {
return facets;
}
}

View File

@@ -15,14 +15,18 @@
*/
package org.springframework.data.elasticsearch.core.query;
import org.apache.commons.collections.CollectionUtils;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.springframework.data.domain.Pageable;
import java.util.ArrayList;
import java.util.List;
/**
* NativeSearchQuery
*
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
@@ -30,63 +34,72 @@ import org.springframework.data.domain.Pageable;
public class NativeSearchQueryBuilder {
private QueryBuilder queryBuilder;
private FilterBuilder filterBuilder;
private SortBuilder sortBuilder;
private Pageable pageable;
private String[] indices;
private String[] types;
private String[] fields;
private QueryBuilder queryBuilder;
private FilterBuilder filterBuilder;
private SortBuilder sortBuilder;
private List<FacetRequest> facetRequests = new ArrayList<FacetRequest>();
private Pageable pageable;
private String[] indices;
private String[] types;
private String[] fields;
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {
this.queryBuilder = queryBuilder;
return this;
}
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {
this.queryBuilder = queryBuilder;
return this;
}
public NativeSearchQueryBuilder withFilter(FilterBuilder filterBuilder) {
this.filterBuilder = filterBuilder;
return this;
}
public NativeSearchQueryBuilder withFilter(FilterBuilder filterBuilder) {
this.filterBuilder = filterBuilder;
return this;
}
public NativeSearchQueryBuilder withSort(SortBuilder sortBuilder) {
this.sortBuilder = sortBuilder;
return this;
}
public NativeSearchQueryBuilder withSort(SortBuilder sortBuilder) {
this.sortBuilder = sortBuilder;
return this;
}
public NativeSearchQueryBuilder withPageable(Pageable pageable) {
this.pageable = pageable;
return this;
}
public NativeSearchQueryBuilder withFacet(FacetRequest facetRequest) {
facetRequests.add(facetRequest);
return this;
}
public NativeSearchQueryBuilder withIndices(String... indices) {
this.indices = indices;
return this;
}
public NativeSearchQueryBuilder withPageable(Pageable pageable) {
this.pageable = pageable;
return this;
}
public NativeSearchQueryBuilder withTypes(String... types) {
this.types = types;
return this;
}
public NativeSearchQueryBuilder withIndices(String... indices) {
this.indices = indices;
return this;
}
public NativeSearchQueryBuilder withFields(String... fields) {
this.fields = fields;
return this;
}
public NativeSearchQueryBuilder withTypes(String... types) {
this.types = types;
return this;
}
public NativeSearchQuery build() {
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryBuilder, filterBuilder, sortBuilder);
if (pageable != null) {
nativeSearchQuery.setPageable(pageable);
}
if (indices != null) {
nativeSearchQuery.addIndices(indices);
}
if (types != null) {
nativeSearchQuery.addTypes(types);
}
if (fields != null) {
nativeSearchQuery.addFields(fields);
}
return nativeSearchQuery;
}
public NativeSearchQueryBuilder withFields(String... fields) {
this.fields = fields;
return this;
}
public NativeSearchQuery build() {
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryBuilder, filterBuilder, sortBuilder);
if (pageable != null) {
nativeSearchQuery.setPageable(pageable);
}
if (indices != null) {
nativeSearchQuery.addIndices(indices);
}
if (types != null) {
nativeSearchQuery.addTypes(types);
}
if (fields != null) {
nativeSearchQuery.addFields(fields);
}
if (CollectionUtils.isNotEmpty(facetRequests)) {
nativeSearchQuery.setFacets(facetRequests);
}
return nativeSearchQuery;
}
}

View File

@@ -17,8 +17,11 @@ package org.springframework.data.elasticsearch.core.query;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.facet.FacetBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import java.util.List;
/**
* NativeSearchQuery
*
@@ -32,4 +35,6 @@ public interface SearchQuery extends Query {
FilterBuilder getFilter();
SortBuilder getElasticsearchSort();
List<FacetRequest> getFacets();
}

View File

@@ -0,0 +1,83 @@
package org.springframework.data.elasticsearch.core.query;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.elasticsearch.search.facet.FacetBuilder;
import org.elasticsearch.search.facet.FacetBuilders;
import org.elasticsearch.search.facet.terms.TermsFacet;
import org.elasticsearch.search.facet.terms.TermsFacetBuilder;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Basic term facet
*
* @author Artur Konczak
*/
public class TermFacetRequest extends AbstractFacetRequest {
private String[] stringFields;
private String[] numberFields;
private int size = 10;
private TermsFacet.ComparatorType order;
public TermFacetRequest(String name) {
super(name);
}
public void setStringFields(String... fields) {
this.stringFields = fields;
}
public void setNumberFields(String... fields) {
this.numberFields = fields;
}
public void setSize(int size) {
Assert.isTrue(size <= 0, "Size should be bigger then zero !!!");
this.size = size;
}
public void ascTerm() {
order = TermsFacet.ComparatorType.TERM;
}
public void descTerm() {
order = TermsFacet.ComparatorType.REVERSE_TERM;
}
public void ascCount() {
order = TermsFacet.ComparatorType.REVERSE_COUNT;
}
public void descCount() {
order = TermsFacet.ComparatorType.COUNT;
}
private List<String> convertStringFieldsToFullNames() {
List<String> result = new ArrayList<String>();
if (ArrayUtils.isNotEmpty(stringFields)) {
for (String stringField : stringFields) {
result.add(stringField + "." + FIELD_UNTOUCHED);
}
}
if (ArrayUtils.isNotEmpty(numberFields)) {
Collections.addAll(result, numberFields);
}
return result;
}
@Override
public FacetBuilder getFacet() {
List<String> fields = convertStringFieldsToFullNames();
Assert.notEmpty(fields, "Please select at last one field !!!");
TermsFacetBuilder builder = FacetBuilders.termsFacet(getName()).fields(fields.toArray(new String[fields.size()])).size(size);
if (order != null) {
builder.order(order);
}
return builder;
}
}

View File

@@ -0,0 +1,59 @@
package org.springframework.data.elasticsearch.core.query;
/**
* Basic term facet
*
* @author Artur Konczak
*/
public class TermFacetRequestBuilder {
private TermFacetRequest result;
public TermFacetRequestBuilder(String name) {
result = new TermFacetRequest(name);
}
public TermFacetRequestBuilder withStringFields(String... fields) {
result.setStringFields(fields);
return this;
}
public TermFacetRequestBuilder withNumberFields(String... fields) {
result.setNumberFields(fields);
return this;
}
public TermFacetRequestBuilder withSize(int size) {
result.setSize(size);
return this;
}
public TermFacetRequestBuilder ascTerm() {
result.ascTerm();
return this;
}
public TermFacetRequestBuilder descTerm() {
result.descTerm();
return this;
}
public TermFacetRequestBuilder ascCount() {
result.ascCount();
return this;
}
public TermFacetRequestBuilder descCount() {
result.descCount();
return this;
}
public TermFacetRequestBuilder applyQueryFilter() {
result.setApplyQueryFilter(true);
return this;
}
public TermFacetRequest build() {
return result;
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.repository;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.FacetedPage;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.repository.NoRepositoryBean;
@@ -37,9 +38,9 @@ public interface ElasticsearchRepository<T, ID extends Serializable> extends Ela
Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
FacetedPage<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
FacetedPage<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, SearchQuery searchQuery);
Page<T> searchSimilar(T entity, SearchQuery searchQuery);
}

View File

@@ -19,6 +19,7 @@ import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.*;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.FacetedPage;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.util.Assert;
@@ -180,13 +181,13 @@ public abstract class AbstractElasticsearchRepository<T, ID extends Serializable
}
@Override
public Page<T> search(QueryBuilder query, Pageable pageable) {
public FacetedPage<T> search(QueryBuilder query, Pageable pageable) {
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(query).withPageable(pageable).build();
return elasticsearchOperations.queryForPage(searchQuery, getEntityClass());
}
@Override
public Page<T> search(SearchQuery query) {
public FacetedPage<T> search(SearchQuery query) {
return elasticsearchOperations.queryForPage(query, getEntityClass());
}