/* * Copyright (c) 2015 Spotify AB. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.spotify.heroic.elasticsearch.index; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Optional; import com.spotify.heroic.common.Duration; import lombok.ToString; import org.elasticsearch.action.count.CountRequestBuilder; import org.elasticsearch.action.deletebyquery.DeleteByQueryRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.Client; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; @ToString public class RotatingIndexMapping implements IndexMapping { public static final Duration DEFAULT_INTERVAL = Duration.of(7, TimeUnit.DAYS); public static final int DEFAULT_MAX_READ_INDICES = 2; public static final int DEFAULT_MAX_WRITE_INDICES = 1; public static final String DEFAULT_PATTERN = "heroic-%s"; private final long interval; private final int maxReadIndices; private final int maxWriteIndices; private final String pattern; @JsonCreator public RotatingIndexMapping( @JsonProperty("interval") Duration interval, @JsonProperty("maxReadIndices") Integer maxReadIndices, @JsonProperty("maxWriteIndices") Integer maxWriteIndices, @JsonProperty("pattern") String pattern ) { this.interval = Optional.fromNullable(interval).or(DEFAULT_INTERVAL).convert(TimeUnit.MILLISECONDS); this.maxReadIndices = verifyPositiveInt(Optional.fromNullable(maxReadIndices).or(DEFAULT_MAX_READ_INDICES), "maxReadIndices"); this.maxWriteIndices = verifyPositiveInt(Optional.fromNullable(maxWriteIndices).or(DEFAULT_MAX_WRITE_INDICES), "maxWriteIndices"); this.pattern = verifyPattern(Optional.fromNullable(pattern).or(DEFAULT_PATTERN)); } private String verifyPattern(String pattern) { if (!pattern.contains("%s")) { throw new IllegalArgumentException( "pattern '" + pattern + "' does not contain a string substitude '%s'"); } return pattern; } private int verifyPositiveInt(int value, String name) { if (value < 1) { throw new IllegalArgumentException(name + "=" + value + " is not a positive integer"); } return value; } @Override public String template() { return String.format(pattern, "*"); } private String[] indices(int maxIndices, long now) { long curr = now - (now % interval); final List<String> indices = new ArrayList<>(); for (int i = 0; i < maxIndices; i++) { long date = curr - (interval * i); if (date < 0) { break; } indices.add(String.format(pattern, date)); } return indices.toArray(new String[indices.size()]); } protected String[] readIndices(long now) throws NoIndexSelectedException { String[] indices = indices(maxReadIndices, now); if (indices.length == 0) { throw new NoIndexSelectedException(); } return indices; } @Override public String[] readIndices() throws NoIndexSelectedException { return readIndices(System.currentTimeMillis()); } protected String[] writeIndices(long now) { return indices(maxWriteIndices, now); } @Override public String[] writeIndices() { return writeIndices(System.currentTimeMillis()); } @Override public DeleteByQueryRequestBuilder deleteByQuery( final Client client, final String type ) throws NoIndexSelectedException { return client .prepareDeleteByQuery(readIndices()) .setIndicesOptions(options()) .setTypes(type); } @Override public SearchRequestBuilder search( final Client client, final String type ) throws NoIndexSelectedException { return client.prepareSearch(readIndices()).setIndicesOptions(options()).setTypes(type); } @Override public CountRequestBuilder count(final Client client, final String type) throws NoIndexSelectedException { return client.prepareCount(readIndices()).setIndicesOptions(options()).setTypes(type); } private IndicesOptions options() { return IndicesOptions.fromOptions(true, true, false, false); } public static Builder builder() { return new Builder(); } public static final class Builder { private Duration interval; private Integer maxReadIndices; private Integer maxWriteIndices; private String pattern; public Builder interval(Duration interval) { this.interval = checkNotNull(interval, "interval"); return this; } public Builder maxReadIndices(Integer maxReadIndices) { this.maxReadIndices = maxReadIndices; return this; } public Builder maxWriteIndices(Integer maxWriteIndices) { this.maxWriteIndices = maxWriteIndices; return this; } public Builder pattern(String pattern) { this.pattern = pattern; return this; } public RotatingIndexMapping build() { return new RotatingIndexMapping(interval, maxReadIndices, maxWriteIndices, pattern); } } }