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