/* * 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.http.metadata; import com.google.common.collect.ImmutableList; import com.spotify.heroic.QueryDateRange; import com.spotify.heroic.QueryManager; import com.spotify.heroic.common.DateRange; import com.spotify.heroic.common.JavaxRestFramework; import com.spotify.heroic.common.OptionalLimit; import com.spotify.heroic.common.Series; import com.spotify.heroic.filter.Filter; import com.spotify.heroic.filter.TrueFilter; import com.spotify.heroic.metadata.CountSeries; import com.spotify.heroic.metadata.DeleteSeries; import com.spotify.heroic.metadata.FindKeys; import com.spotify.heroic.metadata.FindSeries; import com.spotify.heroic.metadata.FindTags; import com.spotify.heroic.metadata.WriteMetadata; import com.spotify.heroic.suggest.KeySuggest; import com.spotify.heroic.suggest.MatchOptions; import com.spotify.heroic.suggest.TagKeyCount; import com.spotify.heroic.suggest.TagSuggest; import com.spotify.heroic.suggest.TagValueSuggest; import com.spotify.heroic.suggest.TagValuesSuggest; import com.spotify.heroic.time.Clock; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.MediaType; import lombok.Data; @Path("metadata") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class MetadataResource { private final Clock clock; private final JavaxRestFramework httpAsync; private final QueryManager query; private final MetadataResourceCache cache; @Inject public MetadataResource( Clock clock, JavaxRestFramework httpAsync, QueryManager query, MetadataResourceCache cache ) { this.clock = clock; this.httpAsync = httpAsync; this.query = query; this.cache = cache; } @POST @Path("tags") public void tags(@Suspended final AsyncResponse response, final MetadataQueryBody request) throws ExecutionException { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of(request.getLimit().orElse(MetadataQueryBody.DEFAULT_LIMIT))); httpAsync.bind(response, cache.findTags(Optional.empty(), new FindTags.Request(c.getFilter(), c.getRange(), c.getLimit()))); } @POST @Path("keys") public void keys(@Suspended final AsyncResponse response, final MetadataQueryBody request) throws ExecutionException { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of(request.getLimit().orElse(MetadataQueryBody.DEFAULT_LIMIT))); httpAsync.bind(response, cache.findKeys(Optional.empty(), new FindKeys.Request(c.getFilter(), c.getRange(), c.getLimit()))); } @PUT @Path("series") public void addSeries(@Suspended final AsyncResponse response, final Series series) { final DateRange range = DateRange.now(clock); httpAsync.bind(response, query.useDefaultGroup().writeSeries(new WriteMetadata.Request(series, range))); } @POST @Path("series") public void getTimeSeries( @Suspended final AsyncResponse response, final MetadataQueryBody request ) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of(request.getLimit().orElse(MetadataQueryBody.DEFAULT_LIMIT))); httpAsync.bind(response, query .useDefaultGroup() .findSeries(new FindSeries.Request(c.getFilter(), c.getRange(), c.getLimit()))); } @DELETE @Path("series") public void deleteTimeSeries( @Suspended final AsyncResponse response, final MetadataQueryBody request ) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange); httpAsync.bind(response, query .useDefaultGroup() .deleteSeries(new DeleteSeries.Request(c.getFilter(), c.getRange(), c.getLimit()))); } @POST @Path("series-count") public void seriesCount(@Suspended final AsyncResponse response, final MetadataCount request) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange); httpAsync.bind(response, query .useDefaultGroup() .countSeries(new CountSeries.Request(c.getFilter(), c.getRange(), c.getLimit()))); } @POST @Path("tagkey-count") public void tagkeyCount( @Suspended final AsyncResponse response, final MetadataTagKeySuggest request ) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of(request.getLimit().orElse(MetadataTagKeySuggest.DEFAULT_LIMIT))); httpAsync.bind(response, query .useDefaultGroup() .tagKeyCount(new TagKeyCount.Request(c.getFilter(), c.getRange(), c.getLimit(), OptionalLimit.of(10)))); } @POST @Path("key-suggest") public void keySuggest( @Suspended final AsyncResponse response, final MetadataKeySuggest request ) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of(request.getLimit().orElse(MetadataKeySuggest.DEFAULT_LIMIT))); final MatchOptions match = request .getMatch() .map(MatchOptions.Builder::build) .orElse(MetadataKeySuggest.DEFAULT_MATCH); httpAsync.bind(response, query .useDefaultGroup() .keySuggest(new KeySuggest.Request(c.getFilter(), c.getRange(), c.getLimit(), match, request.getKey()))); } @POST @Path("tag-suggest") public void tagSuggest( @Suspended final AsyncResponse response, final MetadataTagSuggest request ) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of(request.getLimit().orElse(MetadataTagSuggest.DEFAULT_LIMIT))); final MatchOptions match = request .getMatch() .map(MatchOptions.Builder::build) .orElse(MetadataTagSuggest.DEFAULT_MATCH); httpAsync.bind(response, query .useDefaultGroup() .tagSuggest(new TagSuggest.Request(c.getFilter(), c.getRange(), c.getLimit(), match, request.getKey(), request.getValue()))); } @POST @Path("tag-value-suggest") public void tagValueSuggest( @Suspended final AsyncResponse response, final MetadataTagValueSuggest request ) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of( request.getLimit().orElse(MetadataTagValueSuggest.DEFAULT_LIMIT))); httpAsync.bind(response, query .useDefaultGroup() .tagValueSuggest(new TagValueSuggest.Request(c.getFilter(), c.getRange(), c.getLimit(), request.getKey()))); } @POST @Path("tag-values-suggest") public void tagValuesSuggest( @Suspended final AsyncResponse response, final MetadataTagValuesSuggest request ) { final RequestCriteria c = toCriteria(request::getFilter, request::getRange, () -> OptionalLimit.of( request.getLimit().orElse(MetadataTagValuesSuggest.DEFAULT_LIMIT))); final OptionalLimit groupLimit = request.getGroupLimit().map(OptionalLimit::of).orElseGet(OptionalLimit::empty); final List<String> exclude = request.getExclude().orElseGet(ImmutableList::of); httpAsync.bind(response, query .useDefaultGroup() .tagValuesSuggest( new TagValuesSuggest.Request(c.getFilter(), c.getRange(), c.getLimit(), groupLimit, exclude))); } private RequestCriteria toCriteria( final Supplier<Optional<Filter>> optionalFilter, final Supplier<Optional<QueryDateRange>> optionalRange ) { return toCriteria(optionalFilter, optionalRange, OptionalLimit::empty); } private RequestCriteria toCriteria( final Supplier<Optional<Filter>> optionalFilter, final Supplier<Optional<QueryDateRange>> optionalRange, final Supplier<OptionalLimit> limit ) { final long now = clock.currentTimeMillis(); final Filter c = optionalFilter.get().orElseGet(TrueFilter::get); final DateRange range = optionalRange .get() .map(r -> r.buildDateRange(now)) .orElseGet( () -> new DateRange(now - TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS), now)); return new RequestCriteria(c, range, limit.get()); } @Data static class RequestCriteria { private final Filter filter; private final DateRange range; private final OptionalLimit limit; } }