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