diff --git a/src/main/java/org/springframework/data/elasticsearch/core/facet/FacetMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/facet/FacetMapper.java index 8c26c3969..5a11afefd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/facet/FacetMapper.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/facet/FacetMapper.java @@ -29,6 +29,10 @@ public class FacetMapper { return parseStatistical((StatisticalFacet) facet); } + if (facet instanceof HistogramFacet) { + return parseHistogram((HistogramFacet) facet); + } + return null; } @@ -52,4 +56,12 @@ public class FacetMapper { return new StatisticalResult(facet.getName(), facet.getCount(), facet.getMax(), facet.getMin(), facet.getMean(), facet.getStdDeviation(), facet.getSumOfSquares(), facet.getTotal(), facet.getVariance()); } + private static FacetResult parseHistogram(HistogramFacet facet) { + List entries = new ArrayList(); + for (HistogramFacet.Entry entry : facet.getEntries()) { + entries.add(new IntervalUnit(entry.getKey(), entry.getCount(), entry.getTotalCount(), entry.getTotal(), entry.getMean(), entry.getMin(), entry.getMax())); + } + return new HistogramResult(facet.getName(), entries); + } + } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/facet/request/HistogramFacetRequest.java b/src/main/java/org/springframework/data/elasticsearch/core/facet/request/HistogramFacetRequest.java new file mode 100644 index 000000000..702ef6241 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/facet/request/HistogramFacetRequest.java @@ -0,0 +1,54 @@ +package org.springframework.data.elasticsearch.core.facet.request; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.elasticsearch.search.facet.FacetBuilder; +import org.elasticsearch.search.facet.FacetBuilders; +import org.elasticsearch.search.facet.histogram.HistogramFacetBuilder; +import org.springframework.data.elasticsearch.core.facet.AbstractFacetRequest; +import org.springframework.util.Assert; + +import java.util.concurrent.TimeUnit; + +/** + * @author Artur Konczak + */ +public class HistogramFacetRequest extends AbstractFacetRequest { + + private String field; + private long interval; + private TimeUnit timeUnit; + + public HistogramFacetRequest(String name) { + super(name); + } + + public void setField(String field) { + this.field = field; + } + + public void setInterval(long interval) { + this.interval = interval; + } + + public void setTimeUnit(TimeUnit timeUnit) { + this.timeUnit = timeUnit; + } + + public FacetBuilder getFacet() { + Assert.notNull(getName(), "Facet name can't be a null !!!"); + Assert.isTrue(StringUtils.isNotBlank(field), "Please select field on which to build the facet !!!"); + Assert.isTrue(interval > 0, "Please provide interval as positive value greater them zero !!!"); + + HistogramFacetBuilder builder = FacetBuilders.histogramFacet(getName()); + builder.field(field); + + if (timeUnit != null) { + builder.interval(interval, timeUnit); + } else { + builder.interval(interval); + } + + return builder; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/facet/request/HistogramFacetRequestBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/facet/request/HistogramFacetRequestBuilder.java new file mode 100644 index 000000000..a8ee85e39 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/facet/request/HistogramFacetRequestBuilder.java @@ -0,0 +1,41 @@ +package org.springframework.data.elasticsearch.core.facet.request; + +import org.springframework.data.elasticsearch.core.facet.FacetRequest; + +import java.util.concurrent.TimeUnit; + +/** + * @author Artur Konczak + */ +public class HistogramFacetRequestBuilder { + + HistogramFacetRequest result; + + public HistogramFacetRequestBuilder(String name) { + result = new HistogramFacetRequest(name); + } + + public HistogramFacetRequestBuilder field(String field) { + result.setField(field); + return this; + } + + public HistogramFacetRequestBuilder interval(long interval) { + result.setInterval(interval); + return this; + } + + public HistogramFacetRequestBuilder timeUnit(TimeUnit timeUnit) { + result.setTimeUnit(timeUnit); + return this; + } + + public FacetRequest build() { + return result; + } + + public HistogramFacetRequestBuilder applyQueryFilter() { + result.setApplyQueryFilter(true); + return this; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/facet/result/HistogramResult.java b/src/main/java/org/springframework/data/elasticsearch/core/facet/result/HistogramResult.java new file mode 100644 index 000000000..c360b74f0 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/facet/result/HistogramResult.java @@ -0,0 +1,24 @@ +package org.springframework.data.elasticsearch.core.facet.result; + +import org.springframework.data.elasticsearch.core.facet.AbstactFacetResult; +import org.springframework.data.elasticsearch.core.facet.FacetType; + +import java.util.List; + +/** + * @author Artur Konczak + */ +public class HistogramResult extends AbstactFacetResult { + + private List terms; + + public HistogramResult(String name, List terms) { + super(name, FacetType.term); + this.terms = terms; + } + + public List getIntervalUnit() { + return terms; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/facet/result/IntervalUnit.java b/src/main/java/org/springframework/data/elasticsearch/core/facet/result/IntervalUnit.java new file mode 100644 index 000000000..691c0d122 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/facet/result/IntervalUnit.java @@ -0,0 +1,76 @@ +package org.springframework.data.elasticsearch.core.facet.result; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Single term + * + * @author Rizwan Idrees + * @author Mohsin Husen + * @author Artur Konczak + * @author Jonathan Yan + */ +public class IntervalUnit { + + private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + long key; + long count; + long totalCount; + double total; + double mean; + double min; + double max; + + public IntervalUnit(long key, long count, long totalCount, double total, double mean, double min, double max) { + this.key = key; + this.count = count; + this.totalCount = totalCount; + this.total = total; + this.mean = mean; + this.min = min; + this.max = max; + } + + public long getKey() { + return key; + } + + public long getCount() { + return count; + } + + public long getTotalCount() { + return totalCount; + } + + public double getTotal() { + return total; + } + + public double getMean() { + return mean; + } + + public double getMin() { + return min; + } + + public double getMax() { + return max; + } + + @Override + public String toString() { + return "IntervalUnit{" + + "key=" + format.format(new Date(key)) + + ", count=" + count + + ", totalCount=" + totalCount + + ", total=" + total + + ", mean=" + mean + + ", min=" + min + + ", max=" + max + + '}'; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateFacetTests.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateFacetTests.java index 8d27db6f8..52a757992 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateFacetTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateFacetTests.java @@ -500,4 +500,32 @@ public class ElasticsearchTemplateFacetTests { assertThat(facet.getMax(), is(equalTo(2002.0))); assertThat(facet.getMin(), is(equalTo(2000.0))); } + + @Test + public void shouldReturnHistogramFacetForGivenQuery() { + // given + String facetName = "numberPublicationPerYear"; + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + .withFacet(new HistogramFacetRequestBuilder(facetName).field(PUBLISHED_YEARS).interval(1).build() + ).build(); + // when + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, ArticleEntity.class); + // then + assertThat(result.getNumberOfElements(), is(equalTo(4))); + + HistogramResult facet = (HistogramResult) result.getFacet(facetName); + assertThat(facet.getIntervalUnit().size(), is(equalTo(3))); + + IntervalUnit unit = facet.getIntervalUnit().get(0); + assertThat(unit.getKey(), is(Long.valueOf(YEAR_2000))); + assertThat(unit.getCount(), is(3L)); + + unit = facet.getIntervalUnit().get(1); + assertThat(unit.getKey(), is(Long.valueOf(YEAR_2001))); + assertThat(unit.getCount(), is(2L)); + + unit = facet.getIntervalUnit().get(2); + assertThat(unit.getKey(), is(Long.valueOf(YEAR_2002))); + assertThat(unit.getCount(), is(1L)); + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateHistogramFacetTests.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateHistogramFacetTests.java new file mode 100644 index 000000000..afd09a333 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/ElasticsearchTemplateHistogramFacetTests.java @@ -0,0 +1,139 @@ +/* + * 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.facet; + +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.FacetedPage; +import org.springframework.data.elasticsearch.core.facet.request.HistogramFacetRequestBuilder; +import org.springframework.data.elasticsearch.core.facet.result.*; +import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.SearchQuery; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.concurrent.TimeUnit; + +import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + +/** + * @author Rizwan Idrees + * @author Mohsin Husen + * @author Jonathan Yan + * @author Artur Konczak + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:elasticsearch-template-test.xml") +public class ElasticsearchTemplateHistogramFacetTests { + + public static final long SEQUECE_CODE_INSERT = 1; + public static final long SEQUECE_CODE_UPDATE = 2; + public static final long SEQUECE_CODE_DELETE = 3; + public static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + public static final String DATE_18 = "2013-10-18 18:01"; + public static final String DATE_17 = "2013-10-18 17:01"; + public static final String DATE_16 = "2013-10-18 16:01"; + + + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; + + @Before + public void before() throws ParseException { + elasticsearchTemplate.deleteIndex(LogEntity.class); + elasticsearchTemplate.createIndex(LogEntity.class); + elasticsearchTemplate.putMapping(LogEntity.class); + elasticsearchTemplate.refresh(LogEntity.class, true); + + IndexQuery entry1 = new LogEntityBuilder("1").action("update").date(dateFormatter.parse(DATE_18)).code(SEQUECE_CODE_UPDATE).buildIndex(); + IndexQuery entry2 = new LogEntityBuilder("2").action("insert").date(dateFormatter.parse(DATE_17)).code(SEQUECE_CODE_INSERT).buildIndex(); + IndexQuery entry3 = new LogEntityBuilder("3").action("update").date(dateFormatter.parse(DATE_17)).code(SEQUECE_CODE_UPDATE).buildIndex(); + IndexQuery entry4 = new LogEntityBuilder("4").action("delete").date(dateFormatter.parse(DATE_16)).code(SEQUECE_CODE_DELETE).buildIndex(); + + + elasticsearchTemplate.index(entry1); + elasticsearchTemplate.index(entry2); + elasticsearchTemplate.index(entry3); + elasticsearchTemplate.index(entry4); + + elasticsearchTemplate.refresh(LogEntity.class, true); + } + + + @Test + public void shouldReturnSimpleHistogramFacetForGivenQuery() { + // given + String facetName = "sequenceCodeFacet"; + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + .withFacet(new HistogramFacetRequestBuilder(facetName).field("sequenceCode").interval(1).build() + ).build(); + // when + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, LogEntity.class); + // then + assertThat(result.getNumberOfElements(), is(equalTo(4))); + + HistogramResult facet = (HistogramResult) result.getFacet(facetName); + assertThat(facet.getIntervalUnit().size(), is(equalTo(3))); + + IntervalUnit unit = facet.getIntervalUnit().get(0); + assertThat(unit.getKey(), is(SEQUECE_CODE_INSERT)); + assertThat(unit.getCount(), is(1L)); + + unit = facet.getIntervalUnit().get(1); + assertThat(unit.getKey(), is(SEQUECE_CODE_UPDATE)); + assertThat(unit.getCount(), is(2L)); + + unit = facet.getIntervalUnit().get(2); + assertThat(unit.getKey(), is(SEQUECE_CODE_DELETE)); + assertThat(unit.getCount(), is(1L)); + } + + @Test + public void shouldReturnDateHistogramFacetForGivenQuery() throws ParseException { + // given + String facetName = "sequenceCodeFacet"; + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + .withFacet(new HistogramFacetRequestBuilder(facetName).field("date").interval(1).timeUnit(TimeUnit.HOURS).build() + ).build(); + // when + FacetedPage result = elasticsearchTemplate.queryForPage(searchQuery, LogEntity.class); + // then + assertThat(result.getNumberOfElements(), is(equalTo(4))); + + HistogramResult facet = (HistogramResult) result.getFacet(facetName); + assertThat(facet.getIntervalUnit().size(), is(equalTo(3))); + + IntervalUnit unit = facet.getIntervalUnit().get(0); + assertThat(unit.getKey(),is(dateFormatter.parse("2013-10-18 16:00").getTime())); + assertThat(unit.getCount(), is(1L)); + + unit = facet.getIntervalUnit().get(1); + assertThat(unit.getKey(),is(dateFormatter.parse("2013-10-18 17:00").getTime())); + assertThat(unit.getCount(), is(2L)); + + unit = facet.getIntervalUnit().get(2); + assertThat(unit.getKey(),is(dateFormatter.parse("2013-10-18 18:00").getTime())); + assertThat(unit.getCount(), is(1L)); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/facet/LogEntity.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/LogEntity.java new file mode 100644 index 000000000..7be56b4a3 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/LogEntity.java @@ -0,0 +1,74 @@ +package org.springframework.data.elasticsearch.core.facet; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.DateFormat; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * User: Artur Konczak + * Date: 18/10/13 + * Time: 17:33 + */ +@Document(indexName = "logs", type = "log", shards = 1, replicas = 0, refreshInterval = "-1", indexStoreType = "memory") +public class LogEntity { + + private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + @Id + private String id; + + private String action; + + private long sequenceCode; + + @Field(type = FieldType.Date, format = DateFormat.basic_date_time) + private Date date; + + private LogEntity() { + } + + public LogEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAction() { + return action; + } + + public String toString(){ + return new StringBuffer().append("{id:").append(id).append(",action:").append(action).append(",code:").append(sequenceCode).append(",date:").append(format.format(date)).append("}").toString(); + } + + public void setAction(String action) { + this.action = action; + } + + public long getSequenceCode() { + return sequenceCode; + } + + public void setSequenceCode(long sequenceCode) { + this.sequenceCode = sequenceCode; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/facet/LogEntityBuilder.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/LogEntityBuilder.java new file mode 100644 index 000000000..d1200ee39 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/LogEntityBuilder.java @@ -0,0 +1,46 @@ +package org.springframework.data.elasticsearch.core.facet; + +import org.springframework.data.elasticsearch.core.query.IndexQuery; + +import java.util.Date; + +/** + * User: Artur Konczak + * Date: 18/10/13 + * Time: 17:33 + */ +public class LogEntityBuilder { + + private LogEntity result; + + public LogEntityBuilder(String id) { + result = new LogEntity(id); + } + + public LogEntityBuilder action(String action) { + result.setAction(action); + return this; + } + + public LogEntityBuilder code(long sequenceCode) { + result.setSequenceCode(sequenceCode); + return this; + } + + public LogEntityBuilder date(Date date) { + result.setDate(date); + return this; + } + + public LogEntity build() { + return result; + } + + public IndexQuery buildIndex() { + IndexQuery indexQuery = new IndexQuery(); + indexQuery.setId(result.getId()); + indexQuery.setObject(result); + return indexQuery; + } + +}