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_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.common.unit.TimeValue; 041import org.elasticsearch.index.query.FilterBuilder; 042import org.elasticsearch.index.query.FilterBuilders; 043import org.elasticsearch.index.query.OrFilterBuilder; 044import org.elasticsearch.index.query.RangeFilterBuilder; 045import org.elasticsearch.search.aggregations.AggregationBuilders; 046import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; 047import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram; 048import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramBuilder; 049import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; 050import org.joda.time.DateTime; 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; 056 057/** 058 * @since 6.0 059 */ 060public class DateHistogramAggregate extends AggregateEsBase<BucketRangeDate> { 061 062 Long intervalMillis; 063 064 public DateHistogramAggregate(AggregateDefinition definition, DocumentModel searchDocument) { 065 super(definition, searchDocument); 066 } 067 068 @JsonIgnore 069 @Override 070 public DateHistogramBuilder getEsAggregate() { 071 DateHistogramBuilder ret = AggregationBuilders.dateHistogram(getId()).field(getField()); 072 Map<String, String> props = getProperties(); 073 if (props.containsKey(AGG_INTERVAL_PROP)) { 074 ret.interval(new DateHistogram.Interval(props.get(AGG_INTERVAL_PROP))); 075 } 076 if (props.containsKey(AGG_MIN_DOC_COUNT_PROP)) { 077 ret.minDocCount(Long.parseLong(props.get(AGG_MIN_DOC_COUNT_PROP))); 078 } 079 if (props.containsKey(AGG_ORDER_PROP)) { 080 switch (props.get(AGG_ORDER_PROP).toLowerCase()) { 081 case AGG_ORDER_COUNT_DESC: 082 ret.order(Histogram.Order.COUNT_DESC); 083 break; 084 case AGG_ORDER_COUNT_ASC: 085 ret.order(Histogram.Order.COUNT_ASC); 086 break; 087 case AGG_ORDER_KEY_DESC: 088 ret.order(Histogram.Order.KEY_DESC); 089 break; 090 case AGG_ORDER_KEY_ASC: 091 ret.order(Histogram.Order.KEY_ASC); 092 break; 093 default: 094 throw new IllegalArgumentException("Invalid order: " + props.get(AGG_ORDER_PROP)); 095 } 096 } 097 if (props.containsKey(AGG_EXTENDED_BOUND_MAX_PROP) && props.containsKey(AGG_EXTENDED_BOUND_MIN_PROP)) { 098 ret.extendedBounds(props.get(AGG_EXTENDED_BOUND_MIN_PROP), props.get(AGG_EXTENDED_BOUND_MAX_PROP)); 099 } 100 if (props.containsKey(AGG_TIME_ZONE_PROP)) { 101 ret.timeZone(props.get(AGG_TIME_ZONE_PROP)); 102 } 103 if (props.containsKey(AGG_PRE_ZONE_PROP)) { 104 ret.timeZone(props.get(AGG_PRE_ZONE_PROP)); 105 } 106 if (props.containsKey(AGG_FORMAT_PROP)) { 107 ret.format(props.get(AGG_FORMAT_PROP)); 108 } 109 return ret; 110 } 111 112 @JsonIgnore 113 @Override 114 public FilterBuilder getEsFilter() { 115 if (getSelection().isEmpty()) { 116 return null; 117 } 118 OrFilterBuilder ret = FilterBuilders.orFilter(); 119 for (String sel : getSelection()) { 120 RangeFilterBuilder rangeFilter = FilterBuilders.rangeFilter(getField()); 121 long from = convertStringToDate(sel); 122 long to = from + getIntervalInMillis(); 123 rangeFilter.gte(from).lt(to); 124 ret.add(rangeFilter); 125 } 126 return ret; 127 } 128 129 private long convertStringToDate(String date) { 130 Map<String, String> props = getProperties(); 131 DateTimeFormatter fmt; 132 if (props.containsKey(AGG_FORMAT_PROP)) { 133 fmt = DateTimeFormat.forPattern(props.get(AGG_FORMAT_PROP)); 134 } else { 135 throw new IllegalArgumentException("format property must be defined for " + toString()); 136 } 137 // TODO should take in account all the locale zone stuff ... 138 return fmt.parseDateTime(date).getMillis(); 139 } 140 141 @JsonIgnore 142 @Override 143 public void parseEsBuckets(Collection<? extends MultiBucketsAggregation.Bucket> buckets) { 144 List<BucketRangeDate> nxBuckets = new ArrayList<>(buckets.size()); 145 for (MultiBucketsAggregation.Bucket bucket : buckets) { 146 DateHistogram.Bucket dateHistoBucket = (DateHistogram.Bucket) bucket; 147 DateTime from = getDateTime(dateHistoBucket.getKeyAsDate()); 148 DateTime to = addInterval(from); 149 nxBuckets.add(new BucketRangeDate(bucket.getKey(), from, to, dateHistoBucket.getDocCount())); 150 } 151 this.buckets = nxBuckets; 152 } 153 154 private DateTime addInterval(DateTime from) { 155 return new DateTime(from.getMillis() + getIntervalInMillis()); 156 } 157 158 public long getIntervalInMillis() { 159 if (intervalMillis == null) { 160 String interval; 161 Map<String, String> props = getProperties(); 162 if (props.containsKey(AGG_INTERVAL_PROP)) { 163 interval = props.get(AGG_INTERVAL_PROP); 164 } else { 165 throw new IllegalArgumentException("interval property must be defined for " + toString()); 166 } 167 interval = convertToTimeValueString(interval); 168 intervalMillis = (long) TimeValue.parseTimeValue(interval, null).getMillis(); 169 } 170 return intervalMillis; 171 } 172 173 private String convertToTimeValueString(String interval) { 174 switch (interval.toLowerCase()) { 175 case "second": 176 return "1s"; 177 case "minute": 178 return "1m"; 179 case "hour": 180 return "1h"; 181 case "day": 182 return "1d"; 183 case "week": 184 return "7d"; 185 case "year": 186 return "365d"; 187 // may be wrong here ... 188 case "month": 189 return "30d"; 190 case "quarter": 191 return "91d"; 192 default: 193 // already in ms 194 } 195 return interval; 196 } 197 198}