com.spotify.heroic.metadata.elasticsearch.ElasticsearchMetadataModule.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.heroic.metadata.elasticsearch.ElasticsearchMetadataModule.java

Source

/*
 * 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.metadata.elasticsearch;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.HashCode;
import com.google.common.util.concurrent.RateLimiter;
import com.spotify.heroic.ExtraParameters;
import com.spotify.heroic.common.DynamicModuleId;
import com.spotify.heroic.common.Groups;
import com.spotify.heroic.common.ModuleId;
import com.spotify.heroic.dagger.PrimaryComponent;
import com.spotify.heroic.elasticsearch.BackendType;
import com.spotify.heroic.elasticsearch.Connection;
import com.spotify.heroic.elasticsearch.ConnectionModule;
import com.spotify.heroic.elasticsearch.DefaultRateLimitedCache;
import com.spotify.heroic.elasticsearch.DisabledRateLimitedCache;
import com.spotify.heroic.elasticsearch.RateLimitedCache;
import com.spotify.heroic.lifecycle.LifeCycle;
import com.spotify.heroic.lifecycle.LifeCycleManager;
import com.spotify.heroic.metadata.MetadataBackend;
import com.spotify.heroic.metadata.MetadataModule;
import dagger.Component;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import eu.toolchain.async.Managed;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.inject.Named;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;

@Data
@ModuleId("elasticsearch")
public final class ElasticsearchMetadataModule implements MetadataModule, DynamicModuleId {
    private static final int DEFAULT_DELETE_PARALLELISM = 20;
    private static final double DEFAULT_WRITES_PER_SECOND = 3000d;
    private static final long DEFAULT_RATE_LIMIT_SLOW_START_SECONDS = 0L;
    private static final long DEFAULT_WRITE_CACHE_DURATION_MINUTES = 240L;
    public static final String DEFAULT_GROUP = "elasticsearch";
    public static final String DEFAULT_TEMPLATE_NAME = "heroic-metadata";

    private final Optional<String> id;
    private final Groups groups;
    private final ConnectionModule connection;
    private final String templateName;
    private final Double writesPerSecond;
    private final Long rateLimitSlowStartSeconds;
    private final Long writeCacheDurationMinutes;
    private final int deleteParallelism;
    private final boolean configure;

    private static Supplier<BackendType> defaultSetup = MetadataBackendKV::backendType;

    private static final Map<String, Supplier<BackendType>> backendTypes = new HashMap<>();

    static {
        backendTypes.put("kv", defaultSetup);
        backendTypes.put("v1", MetadataBackendV1::backendType);
    }

    public static List<String> types() {
        return ImmutableList.copyOf(backendTypes.keySet());
    }

    @JsonIgnore
    private final Supplier<BackendType> backendTypeBuilder;

    @JsonCreator
    public ElasticsearchMetadataModule(@JsonProperty("id") Optional<String> id,
            @JsonProperty("groups") Optional<Groups> groups,
            @JsonProperty("connection") Optional<ConnectionModule> connection,
            @JsonProperty("writesPerSecond") Optional<Double> writesPerSecond,
            @JsonProperty("rateLimitSlowStartSeconds") Optional<Long> rateLimitSlowStartSeconds,
            @JsonProperty("writeCacheDurationMinutes") Optional<Long> writeCacheDurationMinutes,
            @JsonProperty("deleteParallelism") Optional<Integer> deleteParallelism,
            @JsonProperty("templateName") Optional<String> templateName,
            @JsonProperty("backendType") Optional<String> backendType,
            @JsonProperty("configure") Optional<Boolean> configure) {
        this.id = id;
        this.groups = groups.orElseGet(Groups::empty).or(DEFAULT_GROUP);
        this.connection = connection.orElseGet(ConnectionModule::buildDefault);
        this.writesPerSecond = writesPerSecond.orElse(DEFAULT_WRITES_PER_SECOND);
        this.rateLimitSlowStartSeconds = rateLimitSlowStartSeconds.orElse(DEFAULT_RATE_LIMIT_SLOW_START_SECONDS);
        this.writeCacheDurationMinutes = writeCacheDurationMinutes.orElse(DEFAULT_WRITE_CACHE_DURATION_MINUTES);
        this.deleteParallelism = deleteParallelism.orElse(DEFAULT_DELETE_PARALLELISM);
        this.templateName = templateName.orElse(DEFAULT_TEMPLATE_NAME);
        this.backendTypeBuilder = backendType.flatMap(bt -> ofNullable(backendTypes.get(bt))).orElse(defaultSetup);
        this.configure = configure.orElse(false);
    }

    @Override
    public Optional<String> id() {
        return id;
    }

    @Override
    public Exposed module(final PrimaryComponent primary, final Depends depends, final String id) {
        final BackendType backendType = backendTypeBuilder.get();

        return DaggerElasticsearchMetadataModule_C.builder().primaryComponent(primary).depends(depends)
                .connectionModule(connection).m(new M(groups, templateName, backendType, writesPerSecond,
                        rateLimitSlowStartSeconds, writeCacheDurationMinutes))
                .build();
    }

    @ElasticsearchScope
    @Component(modules = { M.class, ConnectionModule.class }, dependencies = { PrimaryComponent.class,
            Depends.class })
    interface C extends Exposed {
        @Override
        MetadataBackend backend();

        @Override
        LifeCycle life();
    }

    @RequiredArgsConstructor
    @Module
    class M {
        public static final String ELASTICSEARCH_CONFIGURE_PARAM = "elasticsearch.configure";

        private final Groups groups;
        private final String templateName;
        private final BackendType backendType;
        private final Double writesPerSecond;
        private final Long rateLimitSlowStartSeconds;
        private final Long writeCacheDurationMinutes;

        @Provides
        @ElasticsearchScope
        public Groups groups() {
            return groups;
        }

        @Provides
        @ElasticsearchScope
        public Managed<Connection> connection(ConnectionModule.Provider provider) {
            return provider.construct(templateName, backendType);
        }

        @Provides
        @ElasticsearchScope
        @Named("configure")
        public boolean configure(ExtraParameters params) {
            return configure || params.contains(ExtraParameters.CONFIGURE)
                    || params.contains(ELASTICSEARCH_CONFIGURE_PARAM);
        }

        @Provides
        @ElasticsearchScope
        @Named("deleteParallelism")
        public int deleteParallelism() {
            return deleteParallelism;
        }

        @Provides
        @ElasticsearchScope
        public RateLimitedCache<Pair<String, HashCode>> writeCache() {
            final Cache<Pair<String, HashCode>, Boolean> cache = CacheBuilder.newBuilder().concurrencyLevel(4)
                    .expireAfterWrite(writeCacheDurationMinutes, TimeUnit.MINUTES).build();

            if (writesPerSecond <= 0d) {
                return new DisabledRateLimitedCache<>(cache.asMap());
            }

            return new DefaultRateLimitedCache<>(cache.asMap(),
                    RateLimiter.create(writesPerSecond, rateLimitSlowStartSeconds, TimeUnit.SECONDS));
        }

        @Provides
        @ElasticsearchScope
        MetadataBackend backend(Lazy<MetadataBackendKV> kv, Lazy<MetadataBackendV1> v1) {
            if (backendType.getType().equals(MetadataBackendV1.class)) {
                return v1.get();
            }

            return kv.get();
        }

        @Provides
        @ElasticsearchScope
        LifeCycle life(LifeCycleManager manager, Lazy<MetadataBackendKV> kv, Lazy<MetadataBackendV1> v1) {
            if (backendType.getType().equals(MetadataBackendV1.class)) {
                return manager.build(v1.get());
            }

            return manager.build(kv.get());
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private Optional<String> id = empty();
        private Optional<Groups> groups = empty();
        private Optional<ConnectionModule> connection = empty();
        private Optional<Double> writesPerSecond = empty();
        private Optional<Long> rateLimitSlowStartSeconds = empty();
        private Optional<Long> writeCacheDurationMinutes = empty();
        private Optional<Integer> deleteParallelism = empty();
        private Optional<String> templateName = empty();
        private Optional<String> backendType = empty();
        private Optional<Boolean> configure = empty();

        public Builder id(final String id) {
            checkNotNull(id, "id");
            this.id = of(id);
            return this;
        }

        public Builder groups(final Groups groups) {
            checkNotNull(groups, "groups");
            this.groups = of(groups);
            return this;
        }

        public Builder connection(final ConnectionModule connection) {
            checkNotNull(connection, "connection");
            this.connection = of(connection);
            return this;
        }

        public Builder writesPerSecond(final double writesPerSecond) {
            checkNotNull(writesPerSecond, "writesPerSecond");
            this.writesPerSecond = of(writesPerSecond);
            return this;
        }

        public Builder rateLimitSlowStartSeconds(final long rateLimitSlowStartSeconds) {
            checkNotNull(rateLimitSlowStartSeconds, "rateLimitSlowStartSeconds");
            this.rateLimitSlowStartSeconds = of(rateLimitSlowStartSeconds);
            return this;
        }

        public Builder writeCacheDurationMinutes(final long writeCacheDurationMinutes) {
            this.writeCacheDurationMinutes = of(writeCacheDurationMinutes);
            return this;
        }

        public Builder deleteParallelism(final int deleteParallelism) {
            this.deleteParallelism = of(deleteParallelism);
            return this;
        }

        public Builder templateName(final String templateName) {
            checkNotNull(templateName, "templateName");
            this.templateName = of(templateName);
            return this;
        }

        public Builder backendType(final String backendType) {
            checkNotNull(backendType, "backendType");
            this.backendType = of(backendType);
            return this;
        }

        public Builder configure(final boolean configure) {
            this.configure = of(configure);
            return this;
        }

        public ElasticsearchMetadataModule build() {
            return new ElasticsearchMetadataModule(id, groups, connection, writesPerSecond,
                    rateLimitSlowStartSeconds, writeCacheDurationMinutes, deleteParallelism, templateName,
                    backendType, configure);
        }
    }
}