From 167c6741d445ef60364b7f9547018cfd2ee5dc54 Mon Sep 17 00:00:00 2001 From: jsabin Date: Thu, 2 Mar 2017 21:14:49 -0700 Subject: [PATCH] Manual merge of pull request #53 "Adding initial support to query tags service". --- .gitignore | 3 + pom.xml | 327 +++++++++--------- .../org/kairosdb/client/AbstractClient.java | 31 +- src/main/java/org/kairosdb/client/Client.java | 31 +- .../java/org/kairosdb/client/JsonMapper.java | 2 - .../client/builder/AbstractQueryBuilder.java | 213 ++++++++++++ .../kairosdb/client/builder/QueryBuilder.java | 253 +++----------- .../client/builder/QueryTagBuilder.java | 78 +++++ .../client/builder/QueryTagMetric.java | 79 +++++ .../client/response/QueryResponse.java | 4 +- .../client/response/QueryTagResponse.java | 114 ++++++ .../kairosdb/client/response/TagQuery.java | 22 ++ .../kairosdb/client/response/TagResult.java | 29 ++ .../client/builder/QueryTagBuilderTest.java | 133 +++++++ .../client/response/QueryResponseTest.java | 1 + .../client/response/QueryTagResponseTest.java | 134 +++++++ .../resources/query_tag_response_valid.json | 23 ++ 17 files changed, 1080 insertions(+), 397 deletions(-) create mode 100644 src/main/java/org/kairosdb/client/builder/AbstractQueryBuilder.java create mode 100644 src/main/java/org/kairosdb/client/builder/QueryTagBuilder.java create mode 100644 src/main/java/org/kairosdb/client/builder/QueryTagMetric.java create mode 100644 src/main/java/org/kairosdb/client/response/QueryTagResponse.java create mode 100644 src/main/java/org/kairosdb/client/response/TagQuery.java create mode 100644 src/main/java/org/kairosdb/client/response/TagResult.java create mode 100644 src/test/java/org/kairosdb/client/builder/QueryTagBuilderTest.java create mode 100644 src/test/java/org/kairosdb/client/response/QueryTagResponseTest.java create mode 100644 src/test/resources/query_tag_response_valid.json diff --git a/.gitignore b/.gitignore index 090692a..6258417 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ out/* dependency.txt .tablesawcache .idea ++.classpath ++.project ++.settings \ No newline at end of file diff --git a/pom.xml b/pom.xml index ad68d25..c1f36b2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,14 +1,16 @@ - - 4.0.0 + 4.0.0 - org.kairosdb - client - 2.2.0-SNAPSHOT - jar + org.kairosdb + client + 2.2.0-SNAPSHOT + jar - kairosclient - Java client for pushing and querying data to/from KairosDB + kairosclient + Java client for pushing and querying data to/from KairosDB + http://kairosdb.org @@ -36,78 +38,78 @@ - - - com.google.guava - guava - 14.0 - - - org.hamcrest - hamcrest-all - 1.1 - test - - - org.apache.httpcomponents - httpclient - 4.3.3 - - - commons-lang - commons-lang - 2.6 - - - commons-io - commons-io - 2.2 - - - com.google.code.findbugs - jsr305 - 2.0.0 - + + + com.google.guava + guava + 14.0 + + + org.hamcrest + hamcrest-all + 1.1 + test + + + org.apache.httpcomponents + httpclient + 4.3.3 + + + commons-lang + commons-lang + 2.6 + + + commons-io + commons-io + 2.2 + + + com.google.code.findbugs + jsr305 + 2.0.0 + - - junit - junit - 4.11 - test - - - com.google.code.gson - gson - 2.2.4 - - - org.kairosdb - kairosdb - 1.1.2-1 - test - + + junit + junit + 4.11 + test + + + com.google.code.gson + gson + 2.2.4 + + + org.kairosdb + kairosdb + 1.1.3-1 + test + - - org.mockito - mockito-core - 1.9.5 - test - - + + org.mockito + mockito-core + 1.9.5 + test + + - - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - http://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + http://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + @@ -116,96 +118,97 @@ ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - 1.6 - 1.6 - - - - org.jacoco - jacoco-maven-plugin - 0.7.7.201606060606 - - - prepare-agent - - prepare-agent - - - - report - prepare-package - - report - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + 1.6 + 1.6 + + + + org.jacoco + jacoco-maven-plugin + 0.7.7.201606060606 + + + prepare-agent + + prepare-agent + + + + report + prepare-package + + report + + + + - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.2 - true - - ossrh - https://oss.sonatype.org/ - false - - - - + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.2 + true + + ossrh + https://oss.sonatype.org/ + false + + + + diff --git a/src/main/java/org/kairosdb/client/AbstractClient.java b/src/main/java/org/kairosdb/client/AbstractClient.java index 752201e..c23e5c4 100644 --- a/src/main/java/org/kairosdb/client/AbstractClient.java +++ b/src/main/java/org/kairosdb/client/AbstractClient.java @@ -1,27 +1,10 @@ -/* - * Copyright 2013 Proofpoint Inc. - * - * Licensed 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 org.kairosdb.client; import com.google.gson.stream.JsonReader; import org.kairosdb.client.builder.MetricBuilder; import org.kairosdb.client.builder.QueryBuilder; -import org.kairosdb.client.response.ErrorResponse; -import org.kairosdb.client.response.GetResponse; -import org.kairosdb.client.response.QueryResponse; -import org.kairosdb.client.response.Response; +import org.kairosdb.client.builder.QueryTagBuilder; +import org.kairosdb.client.response.*; import java.io.IOException; import java.io.InputStream; @@ -77,6 +60,16 @@ public GetResponse getTagValues() throws IOException return get(url + "/api/v1/tagvalues"); } + @Override + public QueryTagResponse queryTag(QueryTagBuilder builder) throws URISyntaxException, IOException + { + ClientResponse clientResponse = postData(builder.build(), url + "/api/v1/datapoints/query/tags"); + int responseCode = clientResponse.getStatusCode(); + + InputStream stream = clientResponse.getContentStream(); + return new QueryTagResponse(mapper, responseCode, stream); + } + @Override public QueryResponse query(QueryBuilder builder) throws URISyntaxException, IOException { diff --git a/src/main/java/org/kairosdb/client/Client.java b/src/main/java/org/kairosdb/client/Client.java index 464f855..e070518 100644 --- a/src/main/java/org/kairosdb/client/Client.java +++ b/src/main/java/org/kairosdb/client/Client.java @@ -1,24 +1,11 @@ -/* - * Copyright 2013 Proofpoint Inc. - * - * Licensed 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 org.kairosdb.client; import org.kairosdb.client.builder.MetricBuilder; import org.kairosdb.client.builder.QueryBuilder; +import org.kairosdb.client.builder.QueryTagBuilder; import org.kairosdb.client.response.GetResponse; import org.kairosdb.client.response.QueryResponse; +import org.kairosdb.client.response.QueryTagResponse; import org.kairosdb.client.response.Response; import java.io.IOException; @@ -60,6 +47,17 @@ public interface Client */ QueryResponse query(QueryBuilder builder) throws URISyntaxException, IOException; + /** + * Queries KairosDB tags using the query built by the builder. + * + * @param builder query tag builder + * @return response from the server + * @throws URISyntaxException if the host or post is invalid + * @throws IOException problem occurred querying the server + */ + QueryTagResponse queryTag(QueryTagBuilder builder) throws URISyntaxException, IOException; + + /** * Sends metrics from the builder to the KairosDB server. * @@ -74,8 +72,8 @@ public interface Client * Deletes a metric. This is the metric and all its data points. * * @param name the metric to delete - * @throws IOException problem occurred sending to the server * @return response from the server + * @throws IOException problem occurred sending to the server */ Response deleteMetric(String name) throws IOException; @@ -99,6 +97,7 @@ public interface Client /** * Shuts down the client. Should be called when done using the client. + * * @throws IOException if could not shutdown the client */ void shutdown() throws IOException; diff --git a/src/main/java/org/kairosdb/client/JsonMapper.java b/src/main/java/org/kairosdb/client/JsonMapper.java index f346d4a..b97ce5e 100644 --- a/src/main/java/org/kairosdb/client/JsonMapper.java +++ b/src/main/java/org/kairosdb/client/JsonMapper.java @@ -20,8 +20,6 @@ public JsonMapper(DataPointTypeRegistry typeRegistry) builder.registerTypeAdapter(GroupResult.class, new GroupByDeserializer()); builder.registerTypeAdapter(Result.class, new ResultsDeserializer(typeRegistry)); mapper = builder.create(); - - } public T fromJson(Reader json, Type typeOfT) diff --git a/src/main/java/org/kairosdb/client/builder/AbstractQueryBuilder.java b/src/main/java/org/kairosdb/client/builder/AbstractQueryBuilder.java new file mode 100644 index 0000000..39ca25f --- /dev/null +++ b/src/main/java/org/kairosdb/client/builder/AbstractQueryBuilder.java @@ -0,0 +1,213 @@ +package org.kairosdb.client.builder; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; + +import java.io.IOException; +import java.util.Date; + +import static com.google.common.base.Preconditions.*; + +/** + * Abstract class for querying KairosDB. + * @param the builder + */ +public abstract class AbstractQueryBuilder> +{ + @SerializedName("start_absolute") + protected Long startAbsolute; + + @SerializedName("end_absolute") + protected Long endAbsolute; + + @SerializedName("start_relative") + protected RelativeTime startRelative; + + @SerializedName("end_relative") + protected RelativeTime endRelative; + + protected transient Gson mapper; + + protected AbstractQueryBuilder() + { + mapper = buildGson(); + } + + /** + * Builds Gson used by this implementation + */ + protected abstract Gson buildGson(); + + /** + * Returns the absolute range start time. + * + * @return absolute range start time + */ + public Date getStartAbsolute() + { + return new Date(startAbsolute); + } + + /** + * Returns the absolute range end time. + * + * @return absolute range end time + */ + public Date getEndAbsolute() + { + return new Date(endAbsolute); + } + + /** + * Returns the relative range start time. + * + * @return relative range start time + */ + public RelativeTime getStartRelative() + { + return startRelative; + } + + /** + * Returns the relative range end time. + * + * @return relative range end time + */ + public RelativeTime getEndRelative() + { + return endRelative; + } + + /** + * The beginning time of the time range. + * + * @param start start time + * @return the builder + */ + @SuppressWarnings({"unchecked", "ConstantConditions"}) + public B setStart(Date start) + { + checkNotNull(start, "start cannot be null"); + checkArgument(startRelative == null, "Both relative and absolute start times cannot be set."); + + this.startAbsolute = start.getTime(); + checkArgument(startAbsolute <= System.currentTimeMillis(), "Start time cannot be in the future."); + return (B) this; + } + + /** + * The beginning time of the time range relative to now. For example, return all data points that starting 2 days + * ago. + * + * @param duration relative time value + * @param unit unit of time + * @return the builder + */ + @SuppressWarnings({"unchecked", "ConstantConditions"}) + public B setStart(int duration, TimeUnit unit) + { + checkArgument(duration > 0, "duration must be greater than 0"); + checkNotNull(unit, "unit cannot be null"); + checkArgument(startAbsolute == null, "Both relative and absolute start times cannot be set."); + + startRelative = new RelativeTime(duration, unit); + checkArgument(startRelative.getTimeRelativeTo(System.currentTimeMillis()) <= System.currentTimeMillis(), "Start time cannot be in the future."); + return (B) this; + } + + /** + * The ending value of the time range. Must be later in time than the start time. An end time is not required + * and default to now. + * + * @param end end time + * @return the builder + */ + @SuppressWarnings("unchecked") + public B setEnd(Date end) + { + checkArgument(endRelative == null, "Both relative and absolute end times cannot be set."); + this.endAbsolute = end.getTime(); + return (B) this; + } + + /** + * The ending time of the time range relative to now. + * + * @param duration relative time value + * @param unit unit of time + * @return the builder + */ + @SuppressWarnings({"unchecked", "ConstantConditions"}) + public B setEnd(int duration, TimeUnit unit) + { + checkNotNull(unit, "unit cannot be null"); + checkArgument(duration > 0, "duration must be greater than 0"); + checkArgument(endAbsolute == null, "Both relative and absolute end times cannot be set."); + endRelative = new RelativeTime(duration, unit); + return (B) this; + } + + + /** + * Returns the JSON string built by the builder. This is the JSON that can be used by the client to query KairosDB + * + * @return JSON + * @throws IOException if the query is invalid and cannot be converted to JSON + */ + public String build() throws IOException + { + validateTimes(); + + return mapper.toJson(this); + } + + protected void validateTimes() + { + checkState(startAbsolute != null || startRelative != null, "Start time must be specified"); + + if (endAbsolute != null) + { + if (startAbsolute != null) + TimeValidator.validateEndTimeLaterThanStartTime(startAbsolute, endAbsolute); + else + TimeValidator.validateEndTimeLaterThanStartTime(startRelative, endAbsolute); + } + else if (endRelative != null) + { + if (startAbsolute != null) + TimeValidator.validateEndTimeLaterThanStartTime(startAbsolute, endRelative); + else + TimeValidator.validateEndTimeLaterThanStartTime(startRelative, endRelative); + } + } + + @SuppressWarnings("SimplifiableIfStatement") + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + AbstractQueryBuilder that = (AbstractQueryBuilder) o; + + if (startAbsolute != null ? !startAbsolute.equals(that.startAbsolute) : that.startAbsolute != null) + return false; + if (endAbsolute != null ? !endAbsolute.equals(that.endAbsolute) : that.endAbsolute != null) + return false; + if (startRelative != null ? !startRelative.equals(that.startRelative) : that.startRelative != null) + return false; + return endRelative != null ? endRelative.equals(that.endRelative) : that.endRelative == null; + } + + @Override + public int hashCode() + { + int result = startAbsolute != null ? startAbsolute.hashCode() : 0; + result = 31 * result + (endAbsolute != null ? endAbsolute.hashCode() : 0); + result = 31 * result + (startRelative != null ? startRelative.hashCode() : 0); + result = 31 * result + (endRelative != null ? endRelative.hashCode() : 0); + return result; + } +} diff --git a/src/main/java/org/kairosdb/client/builder/QueryBuilder.java b/src/main/java/org/kairosdb/client/builder/QueryBuilder.java index 79ab06e..6cf9bf9 100644 --- a/src/main/java/org/kairosdb/client/builder/QueryBuilder.java +++ b/src/main/java/org/kairosdb/client/builder/QueryBuilder.java @@ -1,18 +1,3 @@ -/* - * Copyright 2013 Proofpoint Inc. - * - * Licensed 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 org.kairosdb.client.builder; import com.google.common.collect.ListMultimap; @@ -23,13 +8,12 @@ import org.kairosdb.client.builder.grouper.CustomGrouper; import org.kairosdb.client.serializer.*; -import java.io.IOException; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.TimeZone; -import static com.google.common.base.Preconditions.*; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static org.kairosdb.client.util.Preconditions.checkNotNullOrEmpty; /** @@ -46,20 +30,8 @@ * all matching data points that occurred between the last 30 minutes up to and including the last 10 minutes are returned. */ @SuppressWarnings("UnusedDeclaration") -public class QueryBuilder +public class QueryBuilder extends AbstractQueryBuilder { - @SerializedName("start_absolute") - private Long startAbsolute; - - @SerializedName("end_absolute") - private Long endAbsolute; - - @SerializedName("start_relative") - private RelativeTime startRelative; - - @SerializedName("end_relative") - private RelativeTime endRelative; - @SerializedName("cache_time") private int cacheTime; @@ -67,9 +39,14 @@ public class QueryBuilder private TimeZone timeZone; private List metrics = new ArrayList(); - private transient Gson mapper; private QueryBuilder() + { + super(); + } + + @Override + protected Gson buildGson() { GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(CustomAggregator.class, new CustomAggregatorSerializer()); @@ -78,72 +55,7 @@ private QueryBuilder() builder.registerTypeAdapter(QueryMetric.Order.class, new OrderSerializer()); builder.registerTypeAdapter(TimeZone.class, new TimeZoneSerializer()); - mapper = builder.create(); - } - - /** - * The beginning time of the time range. - * - * @param start start time - * @return the builder - */ - public QueryBuilder setStart(Date start) - { - checkNotNull(start); - checkArgument(startRelative == null, "Both relative and absolute start times cannot be set."); - - this.startAbsolute = start.getTime(); - checkArgument(startAbsolute <= System.currentTimeMillis(), "Start time cannot be in the future."); - return this; - } - - /** - * The beginning time of the time range relative to now. For example, return all data points that starting 2 days - * ago. - * - * @param duration relative time value - * @param unit unit of time - * @return the builder - */ - public QueryBuilder setStart(int duration, TimeUnit unit) - { - checkArgument(duration > 0); - checkNotNull(unit); - checkArgument(startAbsolute == null, "Both relative and absolute start times cannot be set."); - - startRelative = new RelativeTime(duration, unit); - checkArgument(startRelative.getTimeRelativeTo(System.currentTimeMillis()) <= System.currentTimeMillis(), "Start time cannot be in the future."); - return this; - } - - /** - * The ending value of the time range. Must be later in time than the start time. An end time is not required - * and default to now. - * - * @param end end time - * @return the builder - */ - public QueryBuilder setEnd(Date end) - { - checkArgument(endRelative == null, "Both relative and absolute end times cannot be set."); - this.endAbsolute = end.getTime(); - return this; - } - - /** - * The ending time of the time range relative to now. - * - * @param duration relative time value - * @param unit unit of time - * @return the builder - */ - public QueryBuilder setEnd(int duration, TimeUnit unit) - { - checkNotNull(unit); - checkArgument(duration > 0); - checkArgument(endAbsolute == null, "Both relative and absolute end times cannot be set."); - endRelative = new RelativeTime(duration, unit); - return this; + return builder.create(); } /** @@ -184,46 +96,6 @@ public QueryMetric addMetric(String name) return metric; } - /** - * Returns the absolute range start time. - * - * @return absolute range start time - */ - public Date getStartAbsolute() - { - return new Date(startAbsolute); - } - - /** - * Returns the absolute range end time. - * - * @return absolute range end time - */ - public Date getEndAbsolute() - { - return new Date(endAbsolute); - } - - /** - * Returns the relative range start time. - * - * @return relative range start time - */ - public RelativeTime getStartRelative() - { - return startRelative; - } - - /** - * Returns the relative range end time. - * - * @return relative range end time - */ - public RelativeTime getEndRelative() - { - return endRelative; - } - /** * Returns the cache time. * @@ -256,6 +128,7 @@ public TimeZone getTimeZone() return timeZone; } + @SuppressWarnings("ConstantConditions") public QueryBuilder setTimeZone(TimeZone timeZone) { checkNotNull(timeZone, "timezone cannot be null"); @@ -264,75 +137,63 @@ public QueryBuilder setTimeZone(TimeZone timeZone) return this; } - /** - * Returns the JSON string built by the builder. This is the JSON that can be used by the client to query KairosDB - * - * @return JSON - * @throws IOException if the query is invalid and cannot be converted to JSON + /* (non-Javadoc) + * @see java.lang.Object#hashCode() */ - public String build() throws IOException - { - validateTimes(); - - return mapper.toJson(this); - } - - private void validateTimes() + @Override + public int hashCode() { - checkState(startAbsolute != null || startRelative != null, "Start time must be specified"); - - if (endAbsolute != null) - { - if (startAbsolute != null) - TimeValidator.validateEndTimeLaterThanStartTime(startAbsolute, endAbsolute); - else - TimeValidator.validateEndTimeLaterThanStartTime(startRelative, endAbsolute); - } - else if (endRelative != null) - { - if (startAbsolute != null) - TimeValidator.validateEndTimeLaterThanStartTime(startAbsolute, endRelative); - else - TimeValidator.validateEndTimeLaterThanStartTime(startRelative, endRelative); - } + final int prime = 31; + int result = super.hashCode(); + result = prime * result + cacheTime; + result = prime * result + ((metrics == null) ? 0 : metrics.hashCode()); + result = prime * result + ((timeZone == null) ? 0 : timeZone.hashCode()); + return result; } - @SuppressWarnings("SimplifiableIfStatement") + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ @Override - public boolean equals(Object o) + public boolean equals(Object obj) { - if (this == o) + if (this == obj) + { return true; - if (o == null || getClass() != o.getClass()) - return false; - - QueryBuilder that = (QueryBuilder) o; - - if (cacheTime != that.cacheTime) - return false; - if (startAbsolute != null ? !startAbsolute.equals(that.startAbsolute) : that.startAbsolute != null) + } + if (!super.equals(obj)) + { return false; - if (endAbsolute != null ? !endAbsolute.equals(that.endAbsolute) : that.endAbsolute != null) + } + if (getClass() != obj.getClass()) + { return false; - if (startRelative != null ? !startRelative.equals(that.startRelative) : that.startRelative != null) + } + QueryBuilder other = (QueryBuilder) obj; + if (cacheTime != other.cacheTime) + { return false; - if (endRelative != null ? !endRelative.equals(that.endRelative) : that.endRelative != null) + } + if (metrics == null) + { + if (other.metrics != null) + { + return false; + } + } else if (!metrics.equals(other.metrics)) + { return false; - if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) + } + if (timeZone == null) + { + if (other.timeZone != null) + { + return false; + } + } else if (!timeZone.equals(other.timeZone)) + { return false; - return !(metrics != null ? !metrics.equals(that.metrics) : that.metrics != null); - } - - @Override - public int hashCode() - { - int result = startAbsolute != null ? startAbsolute.hashCode() : 0; - result = 31 * result + (endAbsolute != null ? endAbsolute.hashCode() : 0); - result = 31 * result + (startRelative != null ? startRelative.hashCode() : 0); - result = 31 * result + (endRelative != null ? endRelative.hashCode() : 0); - result = 31 * result + cacheTime; - result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0); - result = 31 * result + (metrics != null ? metrics.hashCode() : 0); - return result; + } + return true; } } \ No newline at end of file diff --git a/src/main/java/org/kairosdb/client/builder/QueryTagBuilder.java b/src/main/java/org/kairosdb/client/builder/QueryTagBuilder.java new file mode 100644 index 0000000..6ecea1b --- /dev/null +++ b/src/main/java/org/kairosdb/client/builder/QueryTagBuilder.java @@ -0,0 +1,78 @@ +package org.kairosdb.client.builder; + +import com.google.common.collect.ListMultimap; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.kairosdb.client.serializer.ListMultiMapSerializer; + +import java.util.ArrayList; +import java.util.List; + +import static org.kairosdb.client.util.Preconditions.checkNotNullOrEmpty; + +/** + * Builder used to create the JSON to query tags from KairosDB. + *
+ *
+ * This is similar to a regular query but just returns the tags (no data points) + * for the range specified. The time range can be specified as absolute or relative. + * Absolute times are a given point in time. Relative times are relative to now. + * The end time is not required and defaults to now. + *
+ *
+ * For example, if you specify a relative start time of 30 minutes, all matching data points for the last 30 minutes + * will be returned. If you specify a relative start time of 30 minutes and a relative end time of 10 minutes, then + * all matching data points that occurred between the last 30 minutes up to and including the last 10 minutes are returned. + */ +public class QueryTagBuilder extends AbstractQueryBuilder +{ + private List metrics = new ArrayList(); + + private QueryTagBuilder() + { + super(); + } + + @Override + protected Gson buildGson() + { + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapter(ListMultimap.class, new ListMultiMapSerializer()); + return builder.create(); + } + + /** + * Returns a new query tag builder. + * + * @return new query tag builder + */ + public static QueryTagBuilder getInstance() + { + return new QueryTagBuilder(); + } + + /** + * The metric to query tag for. + * + * @param name metric name + * @return the builder + */ + public QueryTagMetric addMetric(String name) + { + checkNotNullOrEmpty(name, "Name cannot be null or empty."); + + QueryTagMetric metric = new QueryTagMetric(name); + metrics.add(metric); + return metric; + } + + /** + * Returns the list metrics to query for tags. + * + * @return metrics + */ + public List getMetrics() + { + return metrics; + } +} \ No newline at end of file diff --git a/src/main/java/org/kairosdb/client/builder/QueryTagMetric.java b/src/main/java/org/kairosdb/client/builder/QueryTagMetric.java new file mode 100644 index 0000000..caee45e --- /dev/null +++ b/src/main/java/org/kairosdb/client/builder/QueryTagMetric.java @@ -0,0 +1,79 @@ +package org.kairosdb.client.builder; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; + +import java.util.Arrays; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.kairosdb.client.util.Preconditions.checkNotNullOrEmpty; + +/** + * Query request for tags. You can narrow down the query by adding tags. + * Only metrics that include the tag and matches one of the values are returned. + */ +public class QueryTagMetric +{ + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + private final String name; + + private final ListMultimap tags = ArrayListMultimap.create(); + + public QueryTagMetric(String name) + { + this.name = checkNotNullOrEmpty(name, "name cannot be null or empty"); + } + + /** + * Add a map of tags. This narrows the query to only show metadata associated with the tags' values. + * + * @param tags tags to add + * @return the metric + */ + public QueryTagMetric addTags(Map tags) + { + checkNotNull(tags); + + for (String key : tags.keySet()) + { + this.tags.put(key, tags.get(key)); + } + + return this; + } + + /** + * Adds a tag with multiple values. This narrows the query to only show metadata associated with the tag's values. + * + * @param name tag name + * @param values tag values + * @return the metric + */ + public QueryTagMetric addTag(String name, String... values) + { + checkNotNullOrEmpty(name, "name cannot be null or empty"); + checkArgument(values.length > 0, "value must be greater than 0"); + + for (String value : values) + { + checkNotNullOrEmpty(value, "value cannot be null or empty"); + } + + tags.putAll(name, Arrays.asList(values)); + + return (this); + } + + public String getName() + { + return name; + } + + public ListMultimap getTags() + { + return tags; + } + +} \ No newline at end of file diff --git a/src/main/java/org/kairosdb/client/response/QueryResponse.java b/src/main/java/org/kairosdb/client/response/QueryResponse.java index 530aa2a..e71ddef 100644 --- a/src/main/java/org/kairosdb/client/response/QueryResponse.java +++ b/src/main/java/org/kairosdb/client/response/QueryResponse.java @@ -24,7 +24,7 @@ import java.io.InputStreamReader; import java.util.*; -import static com.google.gson.internal.$Gson$Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkNotNull; /** * Response returned by KairosDB. @@ -42,7 +42,7 @@ public class QueryResponse extends Response public QueryResponse(JsonMapper mapper, int responseCode, InputStream stream) throws IOException { super(responseCode); - this.mapper = checkNotNull(mapper); + this.mapper = checkNotNull(mapper, "mapper cannot be null"); this.responseCode = responseCode; this.body = getBody(stream); this.queries = getQueries(); diff --git a/src/main/java/org/kairosdb/client/response/QueryTagResponse.java b/src/main/java/org/kairosdb/client/response/QueryTagResponse.java new file mode 100644 index 0000000..9e0528b --- /dev/null +++ b/src/main/java/org/kairosdb/client/response/QueryTagResponse.java @@ -0,0 +1,114 @@ +package org.kairosdb.client.response; + +import com.google.gson.JsonSyntaxException; +import org.kairosdb.client.JsonMapper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Response returned by KairosDB. + */ +public class QueryTagResponse extends Response +{ + private final int responseCode; + private final JsonMapper mapper; + + private List results; + private String body; + + @SuppressWarnings("ConstantConditions") + public QueryTagResponse(JsonMapper mapper, int responseCode, InputStream stream) throws IOException + { + super(responseCode); + this.mapper = checkNotNull(mapper, "mapper cannot be null"); + this.responseCode = responseCode; + this.body = getBody(stream); + this.results = getQueries(); + } + + /** + * Returns a list of query results returned by KairosDB. If status code is not + * successful, call getErrors to get errors returned. + * + * @return list of query results or empty list of no data or if an error is returned. + * @throws IOException if could not map response to Queries object + * @throws JsonSyntaxException if the response is not JSON or is invalid JSON + */ + public List getQueries() throws IOException + { + if (results != null) + return results; + + if (getBody() != null) + { + // We only get JSON if the response is a 200, 400 or 500 error + if (responseCode == 400 || responseCode == 500) + { + ErrorResponse errorResponse = mapper.fromJson(body, ErrorResponse.class); + addErrors(errorResponse.getErrors()); + return Collections.emptyList(); + } + else if (responseCode == 200) + { + KairosTagsResponse response = mapper.fromJson(body, KairosTagsResponse.class); + return response.getQueries(); + } + } + + return Collections.emptyList(); + } + + /** + * Returns the body response as a string. + * + * @return body as a string or empty string. + */ + public String getBody() + { + return body; + } + + public String getBody(InputStream stream) throws IOException + { + if (stream == null) + return ""; + + StringBuilder builder = new StringBuilder(); + BufferedReader reader = null; + try + { + reader = new BufferedReader(new InputStreamReader(stream)); + String line; + while ((line = reader.readLine()) != null) + { + builder.append(line); + } + } + finally + { + if (reader != null) + reader.close(); + } + + body = builder.toString(); + return body; + } + + private class KairosTagsResponse + { + private List queries = new ArrayList(); + + public List getQueries() + { + return queries; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/kairosdb/client/response/TagQuery.java b/src/main/java/org/kairosdb/client/response/TagQuery.java new file mode 100644 index 0000000..e229677 --- /dev/null +++ b/src/main/java/org/kairosdb/client/response/TagQuery.java @@ -0,0 +1,22 @@ +package org.kairosdb.client.response; + +import java.util.List; + +/** + * Resulting object from a tag query. + */ +public class TagQuery +{ + private List results; + + public TagQuery(List results) + { + this.results = results; + } + + public List getResults() + { + return results; + } + +} \ No newline at end of file diff --git a/src/main/java/org/kairosdb/client/response/TagResult.java b/src/main/java/org/kairosdb/client/response/TagResult.java new file mode 100644 index 0000000..dcd10d3 --- /dev/null +++ b/src/main/java/org/kairosdb/client/response/TagResult.java @@ -0,0 +1,29 @@ +package org.kairosdb.client.response; + +import java.util.List; +import java.util.Map; + +/** + * Tag Query Results. This is the results of a single query. + */ +public class TagResult +{ + private String name; + private Map> tags; + + public TagResult(String name, Map> tags) + { + this.name = name; + this.tags = tags; + } + + public String getName() + { + return name; + } + + public Map> getTags() + { + return tags; + } +} \ No newline at end of file diff --git a/src/test/java/org/kairosdb/client/builder/QueryTagBuilderTest.java b/src/test/java/org/kairosdb/client/builder/QueryTagBuilderTest.java new file mode 100644 index 0000000..6443b7e --- /dev/null +++ b/src/test/java/org/kairosdb/client/builder/QueryTagBuilderTest.java @@ -0,0 +1,133 @@ +package org.kairosdb.client.builder; + +import org.junit.Test; + +import java.io.IOException; +import java.util.Date; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +public class QueryTagBuilderTest +{ + @Test(expected = NullPointerException.class) + public void test_MetricNameNull_Invalid() + { + QueryTagBuilder.getInstance().addMetric(null); + } + + @Test(expected = IllegalArgumentException.class) + public void test_MetricNameEmpty_Invalid() + { + QueryTagBuilder.getInstance().addMetric(""); + } + + @Test(expected = NullPointerException.class) + public void test_AbsoluteStartNull_Invalid() + { + QueryTagBuilder.getInstance().setStart(null); + } + + @Test(expected = IllegalArgumentException.class) + public void test_AbsoluteStartAndRelativeStartSet_Invalid() + { + QueryTagBuilder.getInstance().setStart(new Date()).setStart(3, TimeUnit.DAYS); + } + + @Test(expected = IllegalArgumentException.class) + public void test_RelativeStartAndAbsoluteStartSet_Invalid() + { + QueryTagBuilder.getInstance().setStart(3, TimeUnit.DAYS).setStart(new Date()); + } + + @Test(expected = NullPointerException.class) + public void test_RelativeStartUnitNull_Invalid() throws IOException + { + QueryTagBuilder.getInstance().setStart(3, null); + } + + @Test(expected = IllegalArgumentException.class) + public void test_RelativeStartValueZero_Invalid() throws IOException + { + QueryTagBuilder.getInstance().setStart(0, TimeUnit.DAYS); + } + + @Test(expected = IllegalArgumentException.class) + public void test_RelativeStartValueNegative_Invalid() throws IOException + { + QueryTagBuilder.getInstance().setStart(-1, TimeUnit.DAYS); + } + + @Test(expected = NullPointerException.class) + public void test_RelativeEndUnitNull_Invalid() throws IOException + { + QueryTagBuilder.getInstance().setEnd(3, null); + } + + @Test(expected = IllegalArgumentException.class) + public void test_RelativeEndValueZero_Invalid() throws IOException + { + QueryTagBuilder.getInstance().setEnd(0, TimeUnit.DAYS); + } + + @Test(expected = IllegalArgumentException.class) + public void test_RelativeEndValueNegative_Invalid() throws IOException + { + QueryTagBuilder.getInstance().setEnd(-1, TimeUnit.DAYS); + } + + @Test(expected = IllegalStateException.class) + public void test_startTimeNotSpecified_Invalid() throws IOException + { + QueryTagBuilder.getInstance().build(); + } + + @Test(expected = IllegalStateException.class) + public void test_endTimeAbsoluteBeforeStartTimeAbsolute_invalid() throws IOException + { + QueryTagBuilder.getInstance() + .setStart(new Date()) + .setEnd(new Date(System.currentTimeMillis() - 10000)) + .build(); + } + + @Test(expected = IllegalStateException.class) + public void test_endTimeRelativeBeforeThanStartTimeRelative_invalid() throws IOException + { + QueryTagBuilder.getInstance() + .setStart(2, TimeUnit.DAYS) + .setEnd(2, TimeUnit.WEEKS) + .build(); + } + + @Test(expected = IllegalStateException.class) + public void test_endTimeRelativeBeforeStartTimeAbsolute_invalid() throws IOException + { + QueryTagBuilder.getInstance() + .setStart(new Date()) + .setEnd(2, TimeUnit.WEEKS) + .build(); + } + + @Test(expected = IllegalStateException.class) + public void test_endTimeAbsoluteBeforeStartTimeRelative_invalid() throws IOException + { + QueryTagBuilder.getInstance() + .setStart(60, TimeUnit.SECONDS) + .setEnd(new Date(1000)) + .build(); + } + + @Test + public void test() throws IOException + { + QueryTagBuilder builder = QueryTagBuilder.getInstance() + .setStart(1, TimeUnit.HOURS); + builder.addMetric("metricName") + .addTag("foo", "bar") + .addTag("fi", "fum"); + + assertThat(builder.build(), equalTo("{\"metrics\":[{\"name\":\"metricName\",\"tags\":{\"fi\":[\"fum\"],\"foo\":[\"bar\"]}}],\"start_relative\":{\"value\":1,\"unit\":\"HOURS\"}}")); + } + +} \ No newline at end of file diff --git a/src/test/java/org/kairosdb/client/response/QueryResponseTest.java b/src/test/java/org/kairosdb/client/response/QueryResponseTest.java index 901d7a9..d540d05 100644 --- a/src/test/java/org/kairosdb/client/response/QueryResponseTest.java +++ b/src/test/java/org/kairosdb/client/response/QueryResponseTest.java @@ -121,6 +121,7 @@ public void getQueriesWith200ErrorNoJson() throws IOException /** * Verify result if something other than 200, 400, and 500 response code. */ + @Test public void getQueriesWith300NoJson() throws IOException { String responseBody = "Not JSON"; diff --git a/src/test/java/org/kairosdb/client/response/QueryTagResponseTest.java b/src/test/java/org/kairosdb/client/response/QueryTagResponseTest.java new file mode 100644 index 0000000..c58ff98 --- /dev/null +++ b/src/test/java/org/kairosdb/client/response/QueryTagResponseTest.java @@ -0,0 +1,134 @@ +package org.kairosdb.client.response; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import com.google.gson.JsonSyntaxException; +import org.junit.Before; +import org.junit.Test; +import org.kairosdb.client.DataPointTypeRegistry; +import org.kairosdb.client.JsonMapper; +import org.kairosdb.client.builder.DataFormatException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.junit.Assert.assertThat; + +public class QueryTagResponseTest +{ + private JsonMapper mapper; + + @Before + public void setup() + { + mapper = new JsonMapper(new DataPointTypeRegistry()); + } + + @Test(expected = NullPointerException.class) + public void testConstructorNullMapperInvalid() throws IOException + { + new QueryTagResponse(null, 200, new ByteArrayInputStream("bogus".getBytes())); + } + + @Test + public void getJson() throws IOException + { + String json = Resources.toString(Resources.getResource("query_tag_response_valid.json"), Charsets.UTF_8); + json = json.replaceAll(System.getProperty("line.separator"), ""); // remove newlines so strings can compare + + QueryTagResponse response = new QueryTagResponse(mapper, 200, new ByteArrayInputStream(json.getBytes())); + + assertThat(response.getBody(), equalTo(json)); + assertThat(response.getStatusCode(), equalTo(200)); + assertThat(response.getErrors().size(), equalTo(0)); + } + + @Test + public void getJsonWithErrors() throws IOException + { + String json = "{\"errors\":[\"query.start_time relative or absolute time must be set\"]}"; + + QueryTagResponse response = new QueryTagResponse(mapper, 400, new ByteArrayInputStream(json.getBytes())); + + assertThat(response.getBody(), equalTo(json)); + assertThat(response.getStatusCode(), equalTo(400)); + assertThat(response.getErrors().size(), equalTo(1)); + assertThat(response.getErrors().get(0), equalTo("query.start_time relative or absolute time must be set")); + } + + @Test + public void getQueriesWithErrors() throws IOException + { + String json = "{\"errors\":[\"query.start_time relative or absolute time must be set\"]}"; + + QueryTagResponse response = new QueryTagResponse(mapper, 400, new ByteArrayInputStream(json.getBytes())); + + assertThat(response.getQueries(), equalTo(Collections.emptyList())); + assertThat(response.getBody(), equalTo(json)); + assertThat(response.getStatusCode(), equalTo(400)); + assertThat(response.getErrors().size(), equalTo(1)); + assertThat(response.getErrors().get(0), equalTo("query.start_time relative or absolute time must be set")); + } + + @Test + public void getQueries() throws IOException, DataFormatException + { + String json = Resources.toString(Resources.getResource("query_tag_response_valid.json"), Charsets.UTF_8); + json = json.replaceAll(System.getProperty("line.separator"), ""); // remove newlines so strings can compare + + QueryTagResponse response = new QueryTagResponse(mapper, 200, new ByteArrayInputStream(json.getBytes())); + + List queries = response.getQueries(); + assertThat(response.getBody(), equalTo(json)); + assertThat(response.getStatusCode(), equalTo(200)); + assertThat(response.getErrors().size(), equalTo(0)); + assertThat(queries.get(0).getResults().get(0).getTags().get("host"), hasItems("localhost", "host2")); + assertThat(queries.get(0).getResults().get(0).getTags().get("rollup"), hasItems("kairos.import_export_unit_test_rollup")); + assertThat(queries.get(0).getResults().get(0).getTags().get("status"), hasItems("success")); + } + + @Test(expected = JsonSyntaxException.class) + public void getQueriesWith400ErrorNoJson() throws IOException + { + String responseBody = "Not JSON"; + + new QueryTagResponse(mapper, 400, new ByteArrayInputStream(responseBody.getBytes())); + } + + @Test(expected = JsonSyntaxException.class) + public void getQueriesWith500ErrorNoJson() throws IOException + { + String responseBody = "Not JSON"; + + new QueryTagResponse(mapper, 500, new ByteArrayInputStream(responseBody.getBytes())); + } + + @Test(expected = JsonSyntaxException.class) + public void getQueriesWith200ErrorNoJson() throws IOException + { + String responseBody = "Not JSON"; + + new QueryTagResponse(mapper, 200, new ByteArrayInputStream(responseBody.getBytes())); + } + + /** + * Verify result if something other than 200, 400, and 500 response code. + */ + @Test + public void getQueriesWith300NoJson() throws IOException + { + String responseBody = "Not JSON"; + + QueryTagResponse response = new QueryTagResponse(mapper, 300, new ByteArrayInputStream(responseBody.getBytes())); + + assertThat(response.getQueries(), equalTo(Collections.emptyList())); + assertThat(response.getBody(), equalTo(responseBody)); + assertThat(response.getStatusCode(), equalTo(300)); + assertThat(response.getErrors().size(), equalTo(0)); + } + +} \ No newline at end of file diff --git a/src/test/resources/query_tag_response_valid.json b/src/test/resources/query_tag_response_valid.json new file mode 100644 index 0000000..ca63de0 --- /dev/null +++ b/src/test/resources/query_tag_response_valid.json @@ -0,0 +1,23 @@ +{ + "queries": [ + { + "results": [ + { + "name": "kairosdb.datastore.query_time", + "tags": { + "host": [ + "localhost", + "host2" + ], + "rollup": [ + "kairos.import_export_unit_test_rollup" + ], + "status": [ + "success" + ] + } + } + ] + } + ] +} \ No newline at end of file