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.DateHistogramBuilder; 047import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; 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 058/** 059 * @since 6.0 060 */ 061public class DateHistogramAggregate extends AggregateEsBase<BucketRangeDate> { 062 063 public DateHistogramAggregate(AggregateDefinition definition, DocumentModel searchDocument) { 064 super(definition, searchDocument); 065 } 066 067 @JsonIgnore 068 @Override 069 public DateHistogramBuilder getEsAggregate() { 070 DateHistogramBuilder ret = AggregationBuilders.dateHistogram(getId()) 071 .field(getField()) 072 .timeZone(DateTimeZone.getDefault().getID()); 073 Map<String, String> props = getProperties(); 074 if (props.containsKey(AGG_INTERVAL_PROP)) { 075 ret.interval(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(props.get(AGG_EXTENDED_BOUND_MIN_PROP), props.get(AGG_EXTENDED_BOUND_MAX_PROP)); 100 } 101 if (props.containsKey(AGG_TIME_ZONE_PROP)) { 102 ret.timeZone(props.get(AGG_TIME_ZONE_PROP)); 103 } 104 if (props.containsKey(AGG_PRE_ZONE_PROP)) { 105 ret.timeZone(props.get(AGG_PRE_ZONE_PROP)); 106 } 107 if (props.containsKey(AGG_FORMAT_PROP)) { 108 ret.format(props.get(AGG_FORMAT_PROP)); 109 } 110 return ret; 111 } 112 113 @JsonIgnore 114 @Override 115 public QueryBuilder getEsFilter() { 116 if (getSelection().isEmpty()) { 117 return null; 118 } 119 BoolQueryBuilder ret = QueryBuilders.boolQuery(); 120 for (String sel : getSelection()) { 121 RangeQueryBuilder rangeFilter = QueryBuilders.rangeQuery(getField()); 122 DateTime from = convertStringToDate(sel); 123 DateTime to = DateHelper.plusDuration(from, getInterval()); 124 rangeFilter.gte(from.getMillis()).lt(to.getMillis()).format(ElasticSearchConstants.EPOCH_MILLIS_FORMAT); 125 ret.should(rangeFilter); 126 } 127 return ret; 128 } 129 130 private DateTime convertStringToDate(String date) { 131 Map<String, String> props = getProperties(); 132 DateTimeFormatter fmt; 133 if (props.containsKey(AGG_FORMAT_PROP)) { 134 fmt = DateTimeFormat.forPattern(props.get(AGG_FORMAT_PROP)); 135 } else { 136 throw new IllegalArgumentException("format property must be defined for " + toString()); 137 } 138 if (props.containsKey(AGG_TIME_ZONE_PROP)) { 139 fmt = fmt.withZone(DateTimeZone.forID(props.get(AGG_TIME_ZONE_PROP))); 140 } 141 return fmt.parseDateTime(date); 142 } 143 144 @JsonIgnore 145 @Override 146 public void parseEsBuckets(Collection<? extends MultiBucketsAggregation.Bucket> buckets) { 147 List<BucketRangeDate> nxBuckets = new ArrayList<>(buckets.size()); 148 for (MultiBucketsAggregation.Bucket bucket : buckets) { 149 DateTime from = (DateTime) bucket.getKey(); 150 DateTime to = DateHelper.plusDuration(from, getInterval()); 151 nxBuckets.add(new BucketRangeDate(bucket.getKeyAsString(), from, to, bucket.getDocCount())); 152 } 153 this.buckets = nxBuckets; 154 } 155 156 private String getInterval() { 157 String ret; 158 Map<String, String> props = getProperties(); 159 if (props.containsKey(AGG_INTERVAL_PROP)) { 160 ret = props.get(AGG_INTERVAL_PROP); 161 } else { 162 throw new IllegalArgumentException("interval property must be defined for " + toString()); 163 } 164 return ret; 165 } 166 167}