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.BucketOrder; 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.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 MultiBucketAggregate<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()) 073 .field(getField()) 074 .timeZone(DateTimeZone.getDefault()); 075 Map<String, String> props = getProperties(); 076 if (props.containsKey(AGG_INTERVAL_PROP)) { 077 ret.dateHistogramInterval(new DateHistogramInterval(props.get(AGG_INTERVAL_PROP))); 078 } 079 if (props.containsKey(AGG_MIN_DOC_COUNT_PROP)) { 080 ret.minDocCount(Long.parseLong(props.get(AGG_MIN_DOC_COUNT_PROP))); 081 } 082 if (props.containsKey(AGG_ORDER_PROP)) { 083 switch (props.get(AGG_ORDER_PROP).toLowerCase()) { 084 case AGG_ORDER_COUNT_DESC: 085 ret.order(BucketOrder.count(false)); 086 break; 087 case AGG_ORDER_COUNT_ASC: 088 ret.order(BucketOrder.count(true)); 089 break; 090 case AGG_ORDER_KEY_DESC: 091 ret.order(BucketOrder.key(false)); 092 break; 093 case AGG_ORDER_KEY_ASC: 094 ret.order(BucketOrder.key(true)); 095 break; 096 default: 097 throw new IllegalArgumentException("Invalid order: " + props.get(AGG_ORDER_PROP)); 098 } 099 } 100 if (props.containsKey(AGG_EXTENDED_BOUND_MAX_PROP) && props.containsKey(AGG_EXTENDED_BOUND_MIN_PROP)) { 101 ret.extendedBounds( 102 new ExtendedBounds(props.get(AGG_EXTENDED_BOUND_MIN_PROP), props.get(AGG_EXTENDED_BOUND_MAX_PROP))); 103 } 104 if (props.containsKey(AGG_TIME_ZONE_PROP)) { 105 ret.timeZone(DateTimeZone.forID(props.get(AGG_TIME_ZONE_PROP))); 106 } 107 if (props.containsKey(AGG_PRE_ZONE_PROP)) { 108 ret.timeZone(DateTimeZone.forID(props.get(AGG_PRE_ZONE_PROP))); 109 } 110 if (props.containsKey(AGG_FORMAT_PROP)) { 111 ret.format(props.get(AGG_FORMAT_PROP)); 112 } 113 return ret; 114 } 115 116 @JsonIgnore 117 @Override 118 public QueryBuilder getEsFilter() { 119 if (getSelection().isEmpty()) { 120 return null; 121 } 122 BoolQueryBuilder ret = QueryBuilders.boolQuery(); 123 for (String sel : getSelection()) { 124 RangeQueryBuilder rangeFilter = QueryBuilders.rangeQuery(getField()); 125 DateTime from = convertStringToDate(sel); 126 DateTime to = DateHelper.plusDuration(from, getInterval()); 127 rangeFilter.gte(from.getMillis()).lt(to.getMillis()).format(ElasticSearchConstants.EPOCH_MILLIS_FORMAT); 128 ret.should(rangeFilter); 129 } 130 return ret; 131 } 132 133 private DateTime convertStringToDate(String date) { 134 Map<String, String> props = getProperties(); 135 DateTimeFormatter fmt; 136 if (props.containsKey(AGG_FORMAT_PROP)) { 137 fmt = DateTimeFormat.forPattern(props.get(AGG_FORMAT_PROP)); 138 } else { 139 throw new IllegalArgumentException("format property must be defined for " + toString()); 140 } 141 if (props.containsKey(AGG_TIME_ZONE_PROP)) { 142 fmt = fmt.withZone(DateTimeZone.forID(props.get(AGG_TIME_ZONE_PROP))); 143 } 144 return fmt.parseDateTime(date); 145 } 146 147 @JsonIgnore 148 @Override 149 public void parseEsBuckets(Collection<? extends MultiBucketsAggregation.Bucket> buckets) { 150 List<BucketRangeDate> nxBuckets = new ArrayList<>(buckets.size()); 151 for (MultiBucketsAggregation.Bucket bucket : buckets) { 152 DateTime from = (DateTime) bucket.getKey(); 153 DateTime to = DateHelper.plusDuration(from, getInterval()); 154 nxBuckets.add(new BucketRangeDate(bucket.getKeyAsString(), from, to, bucket.getDocCount())); 155 } 156 this.buckets = nxBuckets; 157 } 158 159 private String getInterval() { 160 String ret; 161 Map<String, String> props = getProperties(); 162 if (props.containsKey(AGG_INTERVAL_PROP)) { 163 ret = props.get(AGG_INTERVAL_PROP); 164 } else { 165 throw new IllegalArgumentException("interval property must be defined for " + toString()); 166 } 167 return ret; 168 } 169 170}