// This file is part of OpenTSDB. // Copyright (C) 2015 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 2.1 of the License, or (at your // option) any later version. This program is distributed in the hope that it // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser // General Public License for more details. You should have received a copy // of the GNU Lesser General Public License along with this program. If not, // see <http://www.gnu.org/licenses/>. package net.opentsdb.query.pojo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.google.common.base.Objects; import net.opentsdb.utils.JSON; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Pojo builder class used for serdes of the expression query * @since 2.3 */ @JsonIgnoreProperties(ignoreUnknown = true) @JsonDeserialize(builder = Query.Builder.class) public class Query extends Validatable { /** An optional name for the query */ private String name; /** The timespan component of the query */ private Timespan time; /** A list of filters */ private List<Filter> filters; /** A list of metrics */ private List<Metric> metrics; /** A list of expressions */ private List<Expression> expressions; /** A list of outputs */ private List<Output> outputs; /** * Default ctor * @param builder The builder to pull values from */ public Query(Builder builder) { this.name = builder.name; this.time = builder.time; this.filters = builder.filters; this.metrics = builder.metrics; this.expressions = builder.expressions; this.outputs = builder.outputs; } /** @return an optional name for the query */ public String getName() { return name; } /** @return the timespan component of the query */ public Timespan getTime() { return time; } /** @return a list of filters */ public List<Filter> getFilters() { return filters; } /** @return a list of metrics */ public List<Metric> getMetrics() { return metrics; } /** @return a list of expressions */ public List<Expression> getExpressions() { return expressions; } /** @return a list of outputs */ public List<Output> getOutputs() { return outputs; } /** @return A new builder for the query */ public static Builder Builder() { return new Builder(); } /** Validates the query * @throws IllegalArgumentException if one or more parameters were invalid */ public void validate() { if (time == null) { throw new IllegalArgumentException("missing time"); } validatePOJO(time, "time"); if (metrics == null || metrics.isEmpty()) { throw new IllegalArgumentException("missing or empty metrics"); } final Set<String> variable_ids = new HashSet<String>(); for (Metric metric : metrics) { if (variable_ids.contains(metric.getId())) { throw new IllegalArgumentException("duplicated metric id: " + metric.getId()); } variable_ids.add(metric.getId()); } final Set<String> filter_ids = new HashSet<String>(); for (Filter filter : filters) { if (filter_ids.contains(filter.getId())) { throw new IllegalArgumentException("duplicated filter id: " + filter.getId()); } filter_ids.add(filter.getId()); } for (Expression expression : expressions) { if (variable_ids.contains(expression.getId())) { throw new IllegalArgumentException("Duplicated variable or expression id: " + expression.getId()); } variable_ids.add(expression.getId()); } validateCollection(metrics, "metric"); if (filters != null) { validateCollection(filters, "filter"); } if (expressions != null) { validateCollection(expressions, "expression"); } validateFilters(); if (expressions != null) { validateCollection(expressions, "expression"); for (final Expression exp : expressions) { if (exp.getVariables() == null) { throw new IllegalArgumentException("No variables found for an " + "expression?! " + JSON.serializeToString(exp)); } for (final String var : exp.getVariables()) { if (!variable_ids.contains(var)) { throw new IllegalArgumentException("Expression [" + exp.getExpr() + "] was missing input " + var); } } } } } /** Validates the filters, making sure each metric has a filter * @throws IllegalArgumentException if one or more parameters were invalid */ private void validateFilters() { Set<String> ids = new HashSet<String>(); for (Filter filter : filters) { ids.add(filter.getId()); } for(Metric metric : metrics) { if (metric.getFilter() != null && !metric.getFilter().isEmpty() && !ids.contains(metric.getFilter())) { throw new IllegalArgumentException( String.format("unrecognized filter id %s in metric %s", metric.getFilter(), metric.getId())); } } } /** * Makes sure the ID has only letters and characters * @param id The ID to parse * @throws IllegalArgumentException if the ID is invalid */ public static void validateId(final String id) { if (id == null || id.isEmpty()) { throw new IllegalArgumentException("The ID cannot be null or empty"); } for (int i = 0; i < id.length(); i++) { final char c = id.charAt(i); if (!(Character.isLetterOrDigit(c))) { throw new IllegalArgumentException("Invalid id (\"" + id + "\"): illegal character: " + c); } } if (id.length() == 1) { if (Character.isDigit(id.charAt(0))) { throw new IllegalArgumentException("The ID cannot be an integer"); } } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Query query = (Query) o; return Objects.equal(query.expressions, expressions) && Objects.equal(query.filters, filters) && Objects.equal(query.metrics, metrics) && Objects.equal(query.name, name) && Objects.equal(query.outputs, outputs) && Objects.equal(query.time, time); } @Override public int hashCode() { return Objects.hashCode(name, time, filters, metrics, expressions, outputs); } /** * A builder for the query component of a query */ @JsonIgnoreProperties(ignoreUnknown = true) @JsonPOJOBuilder(buildMethodName = "build", withPrefix = "") public static final class Builder { @JsonProperty private String name; @JsonProperty private Timespan time; @JsonProperty private List<Filter> filters; @JsonProperty private List<Metric> metrics; @JsonProperty private List<Expression> expressions; @JsonProperty private List<Output> outputs; public Builder() { } public Builder setName(final String name) { this.name = name; return this; } public Builder setTime(final Timespan time) { this.time = time; return this; } public Builder setFilters(final List<Filter> filters) { this.filters = filters; return this; } public Builder setMetrics(final List<Metric> metrics) { this.metrics = metrics; return this; } public Builder setExpressions(final List<Expression> expressions) { this.expressions = expressions; return this; } public Builder setOutputs(final List<Output> outputs) { this.outputs = outputs; return this; } public Query build() { return new Query(this); } } }