/* * 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; import static com.spotify.heroic.common.Optionals.mergeOptional; import static com.spotify.heroic.common.Optionals.mergeOptionalList; import static com.spotify.heroic.common.Optionals.pickOptional; import static java.util.Objects.requireNonNull; import static java.util.Optional.empty; import static java.util.Optional.of; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.spotify.heroic.analytics.AnalyticsModule; import com.spotify.heroic.analytics.NullAnalyticsModule; import com.spotify.heroic.cache.CacheModule; import com.spotify.heroic.cache.noop.NoopCacheModule; import com.spotify.heroic.cluster.ClusterManagerModule; import com.spotify.heroic.common.Duration; import com.spotify.heroic.common.FeatureSet; import com.spotify.heroic.consumer.ConsumerModule; import com.spotify.heroic.generator.CoreGeneratorModule; import com.spotify.heroic.ingestion.IngestionModule; import com.spotify.heroic.jetty.JettyServerConnector; import com.spotify.heroic.metadata.MetadataManagerModule; import com.spotify.heroic.metric.MetricManagerModule; import com.spotify.heroic.querylogging.QueryLoggingModule; import com.spotify.heroic.querylogging.noop.NoopQueryLoggingModule; import com.spotify.heroic.shell.ShellServerModule; import com.spotify.heroic.statistics.StatisticsModule; import com.spotify.heroic.statistics.noop.NoopStatisticsModule; import com.spotify.heroic.suggest.SuggestManagerModule; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @RequiredArgsConstructor @Data public class HeroicConfig { public static final List<ConsumerModule> DEFAULT_CONSUMERS = ImmutableList.of(); public static final boolean DEFAULT_ENABLE_CORS = true; public static final Duration DEFAULT_START_TIMEOUT = Duration.of(5, TimeUnit.MINUTES); public static final Duration DEFAULT_STOP_TIMEOUT = Duration.of(1, TimeUnit.MINUTES); public static final String DEFAULT_VERSION = "HEAD"; public static final String DEFAULT_SERVICE = "The Heroic Time Series Database"; private final Optional<String> id; /** * The time core will wait for all services (implementing * {@link com.spotify.heroic.lifecycle.LifeCycle} * to start before giving up. */ private final Duration startTimeout; /** * The time core will wait for all services (implementing * {@link com.spotify.heroic.lifecycle.LifeCycle} * to stop before giving up. */ private final Duration stopTimeout; private final Optional<String> host; private final Optional<Integer> port; private final List<JettyServerConnector> connectors; private final Optional<Boolean> disableMetrics; private final boolean enableCors; private final Optional<String> corsAllowOrigin; private final FeatureSet features; private final ClusterManagerModule cluster; private final MetricManagerModule metric; private final MetadataManagerModule metadata; private final SuggestManagerModule suggest; private final CacheModule cache; private final IngestionModule ingestion; private final List<ConsumerModule> consumers; private final Optional<ShellServerModule> shellServer; private final AnalyticsModule analytics; private final CoreGeneratorModule generator; private final StatisticsModule statistics; private final QueryLoggingModule queryLogging; private final String version; private final String service; public static Builder builder() { return new Builder(); } static Optional<HeroicConfig.Builder> loadConfig(final ObjectMapper mapper, final Path path) { try (final InputStream in = Files.newInputStream(path)) { return loadConfig(mapper, in); } catch (final JsonMappingException e) { final JsonLocation location = e.getLocation(); final String message = String.format("%s[%d:%d]: %s", path, location == null ? null : location.getLineNr(), location == null ? null : location.getColumnNr(), e.getOriginalMessage()); throw new RuntimeException(message, e); } catch (final Exception e) { final String message = String.format("%s: %s", path, e.getMessage()); throw new RuntimeException(message, e); } } static Optional<HeroicConfig.Builder> loadConfigStream( final ObjectMapper mapper, final InputStream in ) { try { return loadConfig(mapper, in); } catch (final JsonMappingException e) { final JsonLocation location = e.getLocation(); final String message = String.format("[%d:%d]: %s", location == null ? null : location.getLineNr(), location == null ? null : location.getColumnNr(), e.getOriginalMessage()); throw new RuntimeException(message, e); } catch (final Exception e) { throw new RuntimeException(e); } } static Optional<HeroicConfig.Builder> loadConfig( final ObjectMapper mapper, final InputStream in ) throws JsonMappingException, IOException { final JsonParser parser = mapper.getFactory().createParser(in); if (parser.nextToken() == null) { return Optional.empty(); } return Optional.of(parser.readValueAs(HeroicConfig.Builder.class)); } static List<JettyServerConnector.Builder> defaultConnectors() { return ImmutableList.of(JettyServerConnector.builder()); } @NoArgsConstructor @AllArgsConstructor public static class Builder { private Optional<String> id = empty(); private Optional<Duration> startTimeout = empty(); private Optional<Duration> stopTimeout = empty(); private Optional<String> host = empty(); private Optional<Integer> port = empty(); private Optional<List<JettyServerConnector.Builder>> connectors = empty(); private Optional<Boolean> disableMetrics = empty(); private Optional<Boolean> enableCors = empty(); private Optional<String> corsAllowOrigin = empty(); private Optional<FeatureSet> features = empty(); private Optional<ClusterManagerModule.Builder> cluster = empty(); private Optional<MetricManagerModule.Builder> metrics = empty(); private Optional<MetadataManagerModule.Builder> metadata = empty(); private Optional<SuggestManagerModule.Builder> suggest = empty(); private Optional<CacheModule.Builder> cache = empty(); private Optional<IngestionModule.Builder> ingestion = empty(); private Optional<List<ConsumerModule.Builder>> consumers = empty(); private Optional<ShellServerModule> shellServer = empty(); private Optional<AnalyticsModule.Builder> analytics = empty(); private Optional<CoreGeneratorModule.Builder> generator = empty(); private Optional<StatisticsModule> statistics = empty(); private Optional<QueryLoggingModule> queryLogging = empty(); private Optional<String> version = empty(); private Optional<String> service = empty(); public Builder enableCors(boolean enableCors) { this.enableCors = of(enableCors); return this; } public Builder startTimeout(Duration startTimeout) { this.startTimeout = of(startTimeout); return this; } public Builder stopTimeout(Duration stopTimeout) { this.stopTimeout = of(stopTimeout); return this; } public Builder disableMetrics(boolean disableMetrics) { this.disableMetrics = of(disableMetrics); return this; } public Builder host(String host) { this.host = of(host); return this; } public Builder port(Integer port) { this.port = of(port); return this; } public Builder features(FeatureSet features) { this.features = of(features); return this; } public Builder cluster(ClusterManagerModule.Builder cluster) { this.cluster = of(cluster); return this; } public Builder metrics(MetricManagerModule.Builder metrics) { this.metrics = of(metrics); return this; } public Builder metadata(MetadataManagerModule.Builder metadata) { this.metadata = of(metadata); return this; } public Builder suggest(SuggestManagerModule.Builder suggest) { this.suggest = of(suggest); return this; } public Builder cache(CacheModule.Builder cache) { this.cache = of(cache); return this; } public Builder ingestion(IngestionModule.Builder ingestion) { this.ingestion = of(ingestion); return this; } public Builder consumers(List<ConsumerModule.Builder> consumers) { requireNonNull(consumers, "consumers"); this.consumers = of(consumers); return this; } public Builder analytics(AnalyticsModule.Builder analytics) { this.analytics = of(analytics); return this; } public Builder statistics(StatisticsModule statistics) { this.statistics = of(statistics); return this; } public Builder shellServer(ShellServerModule shellServer) { this.shellServer = of(shellServer); return this; } public Builder queryLogging(QueryLoggingModule queryLogging) { this.queryLogging = of(queryLogging); return this; } public Builder merge(Builder o) { // @formatter:off return new Builder( pickOptional(id, o.id), pickOptional(startTimeout, o.startTimeout), pickOptional(stopTimeout, o.stopTimeout), pickOptional(host, o.host), pickOptional(port, o.port), mergeOptionalList(connectors, o.connectors), pickOptional(disableMetrics, o.disableMetrics), pickOptional(enableCors, o.enableCors), pickOptional(corsAllowOrigin, o.corsAllowOrigin), mergeOptional(features, o.features, FeatureSet::combine), mergeOptional(cluster, o.cluster, ClusterManagerModule.Builder::merge), mergeOptional(metrics, o.metrics, MetricManagerModule.Builder::merge), mergeOptional(metadata, o.metadata, MetadataManagerModule.Builder::merge), mergeOptional(suggest, o.suggest, SuggestManagerModule.Builder::merge), pickOptional(cache, o.cache), mergeOptional(ingestion, o.ingestion, IngestionModule.Builder::merge), mergeOptionalList(consumers, o.consumers), pickOptional(shellServer, o.shellServer), pickOptional(analytics, o.analytics), mergeOptional(generator, o.generator, CoreGeneratorModule.Builder::merge), pickOptional(statistics, o.statistics), pickOptional(queryLogging, o.queryLogging), pickOptional(service, o.service), pickOptional(version, o.version) ); // @formatter:on } public HeroicConfig build() { final List<JettyServerConnector> connectors = ImmutableList.copyOf(this.connectors .orElseGet(HeroicConfig::defaultConnectors) .stream() .map(JettyServerConnector.Builder::build) .iterator()); final String defaultVersion = loadDefaultVersion().orElse(DEFAULT_VERSION); // @formatter:off return new HeroicConfig( id, startTimeout.orElse(DEFAULT_START_TIMEOUT), stopTimeout.orElse(DEFAULT_STOP_TIMEOUT), host, port, connectors, disableMetrics, enableCors.orElse(DEFAULT_ENABLE_CORS), corsAllowOrigin, features.orElseGet(FeatureSet::empty), cluster.orElseGet(ClusterManagerModule::builder).build(), metrics.orElseGet(MetricManagerModule::builder).build(), metadata.orElseGet(MetadataManagerModule::builder).build(), suggest.orElseGet(SuggestManagerModule::builder).build(), cache.orElseGet(NoopCacheModule::builder).build(), ingestion.orElseGet(IngestionModule::builder).build(), consumers.map(c -> c.stream().map(ConsumerModule.Builder::build).iterator()).map (ImmutableList::copyOf).orElseGet(ImmutableList::of), shellServer, analytics.map(AnalyticsModule.Builder::build).orElseGet(NullAnalyticsModule::new), generator.orElseGet(CoreGeneratorModule::builder).build(), statistics.orElseGet(NoopStatisticsModule::new), queryLogging.orElseGet(NoopQueryLoggingModule::new), version.orElse(defaultVersion), service.orElse(DEFAULT_SERVICE) ); // @formatter:on } private Optional<String> loadDefaultVersion() { try (final InputStream in = getClass() .getClassLoader() .getResourceAsStream("com.spotify.heroic/version")) { if (in == null) { return Optional.empty(); } final BufferedReader reader = new BufferedReader(new InputStreamReader(in, Charsets.UTF_8)); return Optional.of(reader.readLine()); } catch (final Exception e) { log.warn("Failed to load version file", e); return Optional.empty(); } } } }