001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bdelbosc
016 */
017package org.nuxeo.elasticsearch.aggregate;
018
019import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_EXTENDED_BOUND_MAX_PROP;
020import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_EXTENDED_BOUND_MIN_PROP;
021import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_INTERVAL_PROP;
022import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_MIN_DOC_COUNT_PROP;
023import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_COUNT_ASC;
024import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_COUNT_DESC;
025import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_KEY_ASC;
026import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_KEY_DESC;
027import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_PROP;
028
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.List;
032import java.util.Map;
033
034import org.codehaus.jackson.annotate.JsonIgnore;
035import org.elasticsearch.index.query.FilterBuilders;
036import org.elasticsearch.index.query.OrFilterBuilder;
037import org.elasticsearch.index.query.RangeFilterBuilder;
038import org.elasticsearch.search.aggregations.AggregationBuilders;
039import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
040import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
041import org.elasticsearch.search.aggregations.bucket.histogram.HistogramBuilder;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.platform.query.api.AggregateDefinition;
044import org.nuxeo.ecm.platform.query.core.BucketRange;
045
046/**
047 * @since 6.0
048 */
049public class HistogramAggregate extends AggregateEsBase<BucketRange> {
050
051    private Integer interval;
052
053    public HistogramAggregate(AggregateDefinition definition, DocumentModel searchDocument) {
054        super(definition, searchDocument);
055    }
056
057    @JsonIgnore
058    @Override
059    public HistogramBuilder getEsAggregate() {
060        HistogramBuilder ret = AggregationBuilders.histogram(getId()).field(getField());
061        Map<String, String> props = getProperties();
062        ret.interval(getInterval());
063        if (props.containsKey(AGG_MIN_DOC_COUNT_PROP)) {
064            ret.minDocCount(Long.parseLong(props.get(AGG_MIN_DOC_COUNT_PROP)));
065        }
066        if (props.containsKey(AGG_ORDER_PROP)) {
067            switch (props.get(AGG_ORDER_PROP).toLowerCase()) {
068            case AGG_ORDER_COUNT_DESC:
069                ret.order(Histogram.Order.COUNT_DESC);
070                break;
071            case AGG_ORDER_COUNT_ASC:
072                ret.order(Histogram.Order.COUNT_ASC);
073                break;
074            case AGG_ORDER_KEY_DESC:
075                ret.order(Histogram.Order.KEY_DESC);
076                break;
077            case AGG_ORDER_KEY_ASC:
078                ret.order(Histogram.Order.KEY_ASC);
079                break;
080            default:
081                throw new IllegalArgumentException("Invalid order: " + props.get(AGG_ORDER_PROP));
082            }
083        }
084        if (props.containsKey(AGG_EXTENDED_BOUND_MAX_PROP) && props.containsKey(AGG_EXTENDED_BOUND_MIN_PROP)) {
085            ret.extendedBounds(Long.parseLong(props.get(AGG_EXTENDED_BOUND_MIN_PROP)),
086                    Long.parseLong(props.get(AGG_EXTENDED_BOUND_MAX_PROP)));
087        }
088        return ret;
089    }
090
091    @JsonIgnore
092    @Override
093    public OrFilterBuilder getEsFilter() {
094        if (getSelection().isEmpty()) {
095            return null;
096        }
097        OrFilterBuilder ret = FilterBuilders.orFilter();
098        for (String sel : getSelection()) {
099            RangeFilterBuilder rangeFilter = FilterBuilders.rangeFilter(getField());
100            long from = Long.parseLong(sel);
101            long to = from + getInterval();
102            rangeFilter.gte(from).lt(to);
103            ret.add(rangeFilter);
104        }
105        return ret;
106    }
107
108    @JsonIgnore
109    @Override
110    public void parseEsBuckets(Collection<? extends MultiBucketsAggregation.Bucket> buckets) {
111        List<BucketRange> nxBuckets = new ArrayList<>(buckets.size());
112        for (MultiBucketsAggregation.Bucket bucket : buckets) {
113            Histogram.Bucket histoBucket = (Histogram.Bucket) bucket;
114            nxBuckets.add(new BucketRange(bucket.getKey(), histoBucket.getKeyAsNumber(),
115                    histoBucket.getKeyAsNumber().intValue() + getInterval(), histoBucket.getDocCount()));
116        }
117        this.buckets = nxBuckets;
118    }
119
120    public int getInterval() {
121        if (interval == null) {
122            Map<String, String> props = getProperties();
123            if (props.containsKey(AGG_INTERVAL_PROP)) {
124                interval = Integer.parseInt(props.get(AGG_INTERVAL_PROP));
125            } else {
126                throw new IllegalArgumentException("interval property must be defined for " + toString());
127            }
128        }
129        return interval;
130    }
131}