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_FORMAT_PROP; 024import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_INTERVAL_PROP; 025import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_MIN_DOC_COUNT_PROP; 026import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_COUNT_ASC; 027import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_COUNT_DESC; 028import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_KEY_ASC; 029import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_KEY_DESC; 030import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_ORDER_PROP; 031import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_PRE_ZONE_PROP; 032import static org.nuxeo.elasticsearch.ElasticSearchConstants.AGG_TIME_ZONE_PROP; 033 034import java.util.ArrayList; 035import java.util.Collection; 036import java.util.List; 037import java.util.Map; 038 039import org.codehaus.jackson.annotate.JsonIgnore; 040import org.elasticsearch.index.query.BoolQueryBuilder; 041import org.elasticsearch.index.query.QueryBuilder; 042import org.elasticsearch.index.query.QueryBuilders; 043import org.elasticsearch.index.query.RangeQueryBuilder; 044import org.elasticsearch.search.aggregations.AggregationBuilders; 045import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; 046import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder; 047import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; 048import org.elasticsearch.search.aggregations.bucket.histogram.ExtendedBounds; 049import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; 050import org.joda.time.DateTime; 051import org.joda.time.DateTimeZone; 052import org.joda.time.format.DateTimeFormat; 053import org.joda.time.format.DateTimeFormatter; 054import org.nuxeo.ecm.core.api.DocumentModel; 055import org.nuxeo.ecm.platform.query.api.AggregateDefinition; 056import org.nuxeo.ecm.platform.query.core.BucketRangeDate; 057import org.nuxeo.elasticsearch.ElasticSearchConstants; 058 059/** 060 * @since 6.0 061 */ 062public class DateHistogramAggregate extends AggregateEsBase<BucketRangeDate> { 063 064 public DateHistogramAggregate(AggregateDefinition definition, DocumentModel searchDocument) { 065 super(definition, searchDocument); 066 } 067 068 @JsonIgnore 069 @Override 070 public DateHistogramAggregationBuilder getEsAggregate() { 071 DateHistogramAggregationBuilder ret = AggregationBuilders.dateHistogram(getId()).field(getField()).timeZone( 072 DateTimeZone.getDefault()); 073 Map<String, String> props = getProperties(); 074 if (props.containsKey(AGG_INTERVAL_PROP)) { 075 ret.dateHistogramInterval(new DateHistogramInterval(props.get(AGG_INTERVAL_PROP))); 076 } 077 if (props.containsKey(AGG_MIN_DOC_COUNT_PROP)) { 078 ret.minDocCount(Long.parseLong(props.get(AGG_MIN_DOC_COUNT_PROP))); 079 } 080 if (props.containsKey(AGG_ORDER_PROP)) { 081 switch (props.get(AGG_ORDER_PROP).toLowerCase()) { 082 case AGG_ORDER_COUNT_DESC: 083 ret.order(Histogram.Order.COUNT_DESC); 084 break; 085 case AGG_ORDER_COUNT_ASC: 086 ret.order(Histogram.Order.COUNT_ASC); 087 break; 088 case AGG_ORDER_KEY_DESC: 089 ret.order(Histogram.Order.KEY_DESC); 090 break; 091 case AGG_ORDER_KEY_ASC: 092 ret.order(Histogram.Order.KEY_ASC); 093 break; 094 default: 095 throw new IllegalArgumentException("Invalid order: " + props.get(AGG_ORDER_PROP)); 096 } 097 } 098 if (props.containsKey(AGG_EXTENDED_BOUND_MAX_PROP) && props.containsKey(AGG_EXTENDED_BOUND_MIN_PROP)) { 099 ret.extendedBounds( 100 new ExtendedBounds(props.get(AGG_EXTENDED_BOUND_MIN_PROP), props.get(AGG_EXTENDED_BOUND_MAX_PROP))); 101 } 102 if (props.containsKey(AGG_TIME_ZONE_PROP)) { 103 ret.timeZone(DateTimeZone.forID(props.get(AGG_TIME_ZONE_PROP))); 104 } 105 if (props.containsKey(AGG_PRE_ZONE_PROP)) { 106 ret.timeZone(DateTimeZone.forID(props.get(AGG_PRE_ZONE_PROP))); 107 } 108 if (props.containsKey(AGG_FORMAT_PROP)) { 109 ret.format(props.get(AGG_FORMAT_PROP)); 110 } 111 return ret; 112 } 113 114 @JsonIgnore 115 @Override 116 public QueryBuilder getEsFilter() { 117 if (getSelection().isEmpty()) { 118 return null; 119 } 120 BoolQueryBuilder ret = QueryBuilders.boolQuery(); 121 for (String sel : getSelection()) { 122 RangeQueryBuilder rangeFilter = QueryBuilders.rangeQuery(getField()); 123 DateTime from = convertStringToDate(sel); 124 DateTime to = DateHelper.plusDuration(from, getInterval()); 125 rangeFilter.gte(from.getMillis()).lt(to.getMillis()).format(ElasticSearchConstants.EPOCH_MILLIS_FORMAT); 126 ret.should(rangeFilter); 127 } 128 return ret; 129 } 130 131 private DateTime convertStringToDate(String date) { 132 Map<String, String> props = getProperties(); 133 DateTimeFormatter fmt; 134 if (props.containsKey(AGG_FORMAT_PROP)) { 135 fmt = DateTimeFormat.forPattern(props.get(AGG_FORMAT_PROP)); 136 } else { 137 throw new IllegalArgumentException("format property must be defined for " + toString()); 138 } 139 if (props.containsKey(AGG_TIME_ZONE_PROP)) { 140 fmt = fmt.withZone(DateTimeZone.forID(props.get(AGG_TIME_ZONE_PROP))); 141 } 142 return fmt.parseDateTime(date); 143 } 144 145 @JsonIgnore 146 @Override 147 public void parseEsBuckets(Collection<? extends MultiBucketsAggregation.Bucket> buckets) { 148 List<BucketRangeDate> nxBuckets = new ArrayList<>(buckets.size()); 149 for (MultiBucketsAggregation.Bucket bucket : buckets) { 150 DateTime from = (DateTime) bucket.getKey(); 151 DateTime to = DateHelper.plusDuration(from, getInterval()); 152 nxBuckets.add(new BucketRangeDate(bucket.getKeyAsString(), from, to, bucket.getDocCount())); 153 } 154 this.buckets = nxBuckets; 155 } 156 157 private String getInterval() { 158 String ret; 159 Map<String, String> props = getProperties(); 160 if (props.containsKey(AGG_INTERVAL_PROP)) { 161 ret = props.get(AGG_INTERVAL_PROP); 162 } else { 163 throw new IllegalArgumentException("interval property must be defined for " + toString()); 164 } 165 return ret; 166 } 167 168}