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