001/* 002 * (C) Copyright 2014-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bdelbosc 018 */ 019package org.nuxeo.elasticsearch.aggregate; 020 021import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_EXTENDED_BOUND_MAX_PROP; 022import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_EXTENDED_BOUND_MIN_PROP; 023import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_INTERVAL_PROP; 024import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_MIN_DOC_COUNT_PROP; 025import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_COUNT_ASC; 026import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_COUNT_DESC; 027import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_KEY_ASC; 028import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_KEY_DESC; 029import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_PROP; 030 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.List; 034import java.util.Map; 035 036import org.codehaus.jackson.annotate.JsonIgnore; 037import org.elasticsearch.index.query.BoolQueryBuilder; 038import org.elasticsearch.index.query.QueryBuilder; 039import org.elasticsearch.index.query.QueryBuilders; 040import org.elasticsearch.index.query.RangeQueryBuilder; 041import org.elasticsearch.search.aggregations.AggregationBuilders; 042import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; 043import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; 044import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder; 045import org.nuxeo.ecm.core.api.DocumentModel; 046import org.nuxeo.ecm.platform.query.api.AggregateDefinition; 047import org.nuxeo.ecm.platform.query.core.BucketRange; 048 049/** 050 * @since 6.0 051 */ 052public class HistogramAggregate extends AggregateEsBase<BucketRange> { 053 054 private Integer interval; 055 056 public HistogramAggregate(AggregateDefinition definition, DocumentModel searchDocument) { 057 super(definition, searchDocument); 058 } 059 060 @JsonIgnore 061 @Override 062 public HistogramAggregationBuilder getEsAggregate() { 063 HistogramAggregationBuilder ret = AggregationBuilders.histogram(getId()).field(getField()); 064 Map<String, String> props = getProperties(); 065 ret.interval(getInterval()); 066 if (props.containsKey(AGG_MIN_DOC_COUNT_PROP)) { 067 ret.minDocCount(Long.parseLong(props.get(AGG_MIN_DOC_COUNT_PROP))); 068 } 069 if (props.containsKey(AGG_ORDER_PROP)) { 070 switch (props.get(AGG_ORDER_PROP).toLowerCase()) { 071 case AGG_ORDER_COUNT_DESC: 072 ret.order(Histogram.Order.COUNT_DESC); 073 break; 074 case AGG_ORDER_COUNT_ASC: 075 ret.order(Histogram.Order.COUNT_ASC); 076 break; 077 case AGG_ORDER_KEY_DESC: 078 ret.order(Histogram.Order.KEY_DESC); 079 break; 080 case AGG_ORDER_KEY_ASC: 081 ret.order(Histogram.Order.KEY_ASC); 082 break; 083 default: 084 throw new IllegalArgumentException("Invalid order: " + props.get(AGG_ORDER_PROP)); 085 } 086 } 087 if (props.containsKey(AGG_EXTENDED_BOUND_MAX_PROP) && props.containsKey(AGG_EXTENDED_BOUND_MIN_PROP)) { 088 ret.extendedBounds(Long.parseLong(props.get(AGG_EXTENDED_BOUND_MIN_PROP)), 089 Long.parseLong(props.get(AGG_EXTENDED_BOUND_MAX_PROP))); 090 } 091 return ret; 092 } 093 094 @JsonIgnore 095 @Override 096 public QueryBuilder getEsFilter() { 097 if (getSelection().isEmpty()) { 098 return null; 099 } 100 BoolQueryBuilder ret = QueryBuilders.boolQuery(); 101 for (String sel : getSelection()) { 102 RangeQueryBuilder rangeFilter = QueryBuilders.rangeQuery(getField()); 103 long from = Long.parseLong(sel); 104 long to = from + getInterval(); 105 rangeFilter.gte(from).lt(to); 106 ret.should(rangeFilter); 107 } 108 return ret; 109 } 110 111 @JsonIgnore 112 @Override 113 public void parseEsBuckets(Collection<? extends MultiBucketsAggregation.Bucket> buckets) { 114 List<BucketRange> nxBuckets = new ArrayList<>(buckets.size()); 115 for (MultiBucketsAggregation.Bucket bucket : buckets) { 116 Histogram.Bucket histoBucket = (Histogram.Bucket) bucket; 117 int from = parseInt(histoBucket.getKeyAsString()); 118 nxBuckets.add( 119 new BucketRange(bucket.getKeyAsString(), from, from + getInterval(), histoBucket.getDocCount())); 120 } 121 this.buckets = nxBuckets; 122 } 123 124 public int getInterval() { 125 if (interval == null) { 126 Map<String, String> props = getProperties(); 127 if (props.containsKey(AGG_INTERVAL_PROP)) { 128 interval = Integer.parseInt(props.get(AGG_INTERVAL_PROP)); 129 } else { 130 throw new IllegalArgumentException("interval property must be defined for " + toString()); 131 } 132 } 133 return interval; 134 } 135 136 protected int parseInt(String key) { 137 if ("-Infinity".equals(key)) { 138 return Integer.MIN_VALUE; 139 } else if ("+Infinity".equals(key)) { 140 return Integer.MAX_VALUE; 141 } 142 return Math.round(Float.parseFloat(key)); 143 } 144}