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.elasticsearch.index.query.BoolQueryBuilder; 037import org.elasticsearch.index.query.QueryBuilder; 038import org.elasticsearch.index.query.QueryBuilders; 039import org.elasticsearch.index.query.RangeQueryBuilder; 040import org.elasticsearch.search.aggregations.AggregationBuilders; 041import org.elasticsearch.search.aggregations.BucketOrder; 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 049import com.fasterxml.jackson.annotation.JsonIgnore; 050 051/** 052 * @since 6.0 053 */ 054public class HistogramAggregate extends MultiBucketAggregate<BucketRange> { 055 056 private Integer interval; 057 058 public HistogramAggregate(AggregateDefinition definition, DocumentModel searchDocument) { 059 super(definition, searchDocument); 060 } 061 062 @JsonIgnore 063 @Override 064 public HistogramAggregationBuilder getEsAggregate() { 065 HistogramAggregationBuilder ret = AggregationBuilders.histogram(getId()).field(getField()); 066 Map<String, String> props = getProperties(); 067 ret.interval(getInterval()); 068 if (props.containsKey(AGG_MIN_DOC_COUNT_PROP)) { 069 ret.minDocCount(Long.parseLong(props.get(AGG_MIN_DOC_COUNT_PROP))); 070 } 071 if (props.containsKey(AGG_ORDER_PROP)) { 072 switch (props.get(AGG_ORDER_PROP).toLowerCase()) { 073 case AGG_ORDER_COUNT_DESC: 074 ret.order(BucketOrder.count(false)); 075 break; 076 case AGG_ORDER_COUNT_ASC: 077 ret.order(BucketOrder.count(true)); 078 break; 079 case AGG_ORDER_KEY_DESC: 080 ret.order(BucketOrder.key(false)); 081 break; 082 case AGG_ORDER_KEY_ASC: 083 ret.order(BucketOrder.key(true)); 084 break; 085 default: 086 throw new IllegalArgumentException("Invalid order: " + props.get(AGG_ORDER_PROP)); 087 } 088 } 089 if (props.containsKey(AGG_EXTENDED_BOUND_MAX_PROP) && props.containsKey(AGG_EXTENDED_BOUND_MIN_PROP)) { 090 ret.extendedBounds(Long.parseLong(props.get(AGG_EXTENDED_BOUND_MIN_PROP)), 091 Long.parseLong(props.get(AGG_EXTENDED_BOUND_MAX_PROP))); 092 } 093 return ret; 094 } 095 096 @JsonIgnore 097 @Override 098 public QueryBuilder getEsFilter() { 099 if (getSelection().isEmpty()) { 100 return null; 101 } 102 BoolQueryBuilder ret = QueryBuilders.boolQuery(); 103 for (String sel : getSelection()) { 104 RangeQueryBuilder rangeFilter = QueryBuilders.rangeQuery(getField()); 105 long from = Float.valueOf(sel).longValue(); 106 long to = from + getInterval(); 107 rangeFilter.gte(from).lt(to); 108 ret.should(rangeFilter); 109 } 110 return ret; 111 } 112 113 @JsonIgnore 114 @Override 115 public void parseEsBuckets(Collection<? extends MultiBucketsAggregation.Bucket> buckets) { 116 List<BucketRange> nxBuckets = new ArrayList<>(buckets.size()); 117 for (MultiBucketsAggregation.Bucket bucket : buckets) { 118 Histogram.Bucket histoBucket = (Histogram.Bucket) bucket; 119 int from = parseInt(histoBucket.getKeyAsString()); 120 nxBuckets.add( 121 new BucketRange(bucket.getKeyAsString(), from, from + getInterval(), histoBucket.getDocCount())); 122 } 123 this.buckets = nxBuckets; 124 } 125 126 public int getInterval() { 127 if (interval == null) { 128 Map<String, String> props = getProperties(); 129 if (props.containsKey(AGG_INTERVAL_PROP)) { 130 interval = Integer.parseInt(props.get(AGG_INTERVAL_PROP)); 131 } else { 132 throw new IllegalArgumentException("interval property must be defined for " + toString()); 133 } 134 } 135 return interval; 136 } 137 138 protected int parseInt(String key) { 139 if ("-Infinity".equals(key)) { 140 return Integer.MIN_VALUE; 141 } else if ("+Infinity".equals(key)) { 142 return Integer.MAX_VALUE; 143 } 144 return Math.round(Float.parseFloat(key)); 145 } 146}