You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by sp...@apache.org on 2018/05/29 18:06:56 UTC
[37/43] sentry git commit: SENTRY-2208: Refactor out Sentry service
into own module from sentry-provider-db (Anthony Young-Garner,
reviewed by Sergio Pena, Steve Moist, Na Li)
http://git-wip-us.apache.org/repos/asf/sentry/blob/b97f5c7a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/QueryParamBuilder.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/QueryParamBuilder.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/QueryParamBuilder.java
deleted file mode 100644
index 6075e3f..0000000
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/QueryParamBuilder.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/**
- * 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 org.apache.sentry.provider.db.service.persistent;
-
-import com.google.common.base.Joiner;
-import org.apache.sentry.provider.db.service.model.MSentryRole;
-import org.apache.sentry.provider.db.service.model.MSentryUser;
-
-import javax.annotation.concurrent.NotThreadSafe;
-import javax.jdo.Query;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * The QueryParamBuilder provides mechanism for constructing complex JDOQL queries with parameters.
- * <p>
- * There are many places where we want to construct a non-trivial parametrized query,
- * often with sub-queries. A simple query expression is just a list of conditions joined
- * by operation (either && or ||). More complicated query may contain sub-expressions which may contain
- * further sub-expressions, for example {@code (AA && (B | (C && D)))}.
- * <p>
- * Query may contain parameters, which usually come from external sources (e.g. Thrift requests). For example,
- * to search for a record containing specific database name we can use something like
- * {@code "dbName == " + request.getDbName()}. This opens a possibility JDOQL injection with carefully
- * constructed requests. To avoid this we need to parameterize the query into something like
- * {@code "dbName == :dbMame"} and pass {@code dbName} as a query parameter. (The colon in {@code :dbName}
- * tells Datanucleus to automatically figure out the type of the {@code dbName} parameter). We collect all
- * such parameters in a map and use {@code executeWithMap()} method of the query to pass all collected
- * parameters to a query.
- * <h2>Representing the query and sub-queries</h2>
- *
- * A query is represented as
- * <ul>
- * <li>Top-level query operation which is always && or ||</li>
- * <li>List of strings that constitute simple subexpressions which are joined with the top level
- * operator when the final query string is constructed</li>
- * <li>List of sub-expressions. Each sub-expression is another QueryParamBuilder that shares
- * the parameter map with the parent. Usually the top-level operation of the sub-expression
- * is the inverse of the top-level operation if the parent. Since
- * {@code (A && (B && C))} can be simplified as {@code (A && B && C)}, there is no
- * need for parent and child to have the same top-level operation.
- * Children are added using the {@link #newChild()} method.</li>
- * </ul>
- *
- * <h2>Constructing the string representation of a query</h2>
- *
- * Once the query is fully constructed, we can get its string reopresentation suitable for the
- * {@code setFilter()} method of {@link javax.jdo.Query} by using {@link #toString()} method
- * which does the following:
- * <ul>
- * <li>Combines all accumulated simple subexpressions using the top-level operator</li>
- * <li>Recursively combines children QueryParamBuilder objects using their {@link #toString()}
- * methods</li>
- * <li>Joins result with the top-level operation</li>
- * </ul>
- * <em>NOTE that we do not guarantee specific order of expressions within each level</em>.
- * <p>
- * The class also provides useful common methods for checking that some field is or
- * isn't <em>NULL</em> and method <em>addSet()</em> to add dynamic set of key/values<p>
- *
- * Most class methods return <em>this</em> so it is possible to chain calls together.
- * <p>
- *
- * The class is not thread-safe.
- * <p>
- * Examples:
- * <ol>
- * <li>
- * <pre>{@code
- * QueryParamBuilder p = newQueryParamBuilder();
- * p.add("key1", "val1").add("key2", "val2")
- *
- * // Returns "(this.key1 == :key1 && this.key2 == :key2)"
- * String queryStr = p.toString();
- *
- * // Returns map {"key1": "val1", "key2": "val2"}
- * Map<String, Object> args = p.getArguments();
- *
- * Query query = pm.newQuery(Foo.class);
- * query.setFilter(queryStr);
- * query.executeWIthMap(args);
- * }</pre>
- * </li>
- * <li>
- * <pre>{@code
- * QueryParamBuilder p = newQueryParamBuilder();
- * p.add("key1", "val1").add("key2", "val2")
- * .newChild() // Inverts logical op from && to ||
- * .add("key3", "val3")
- * .add("key4", "val4").
- *
- * // Returns "(this.key1 == :val1 && this.key2 == :val2 && \
- * // (this.key3 == :val4 || this.key4 == val4))"
- * String queryStr1 = p.toString()
- * }</pre>
- * </li>
- * </ol>
- *
- * @see <a href="http://www.datanucleus.org/products/datanucleus/jdo/jdoql.html">Datanucleus JDOQL</a>
- */
-@NotThreadSafe
-public class QueryParamBuilder {
-
- /**
- * Representation of the top-level query operator.
- * Query is built by joining all parts with the specified Op.
- */
- enum Op {
- AND(" && "),
- OR(" || ");
-
- public String toString() {
- return value;
- }
-
- private final String value;
-
- /** Constructor from string */
- Op(String val) {
- this.value = val;
- }
- }
-
- // Query parts that will be joined with Op
- private final List<String> queryParts = new LinkedList<>();
- // List of children - allocated lazily when children are added
- private List<QueryParamBuilder> children;
- // Query Parameters
- private final Map<String, Object> arguments;
- // paramId is used for automatically generating variable names
- private final AtomicLong paramId;
- // Join operation
- private final String operation;
-
- /**
- * Create new {@link QueryParamBuilder}
- * @return the default {@link QueryParamBuilder} with && top-level operation.
- */
- public static QueryParamBuilder newQueryParamBuilder() {
- return new QueryParamBuilder();
- }
-
- /**
- * Create new {@link QueryParamBuilder} with specific top-level operator
- * @param operation top-level operation for subexpressions
- * @return {@link QueryParamBuilder} with specified top-level operator
- */
- public static QueryParamBuilder newQueryParamBuilder(Op operation) {
- return new QueryParamBuilder(operation);
- }
-
- /**
- * Create a new child builder and attach to this one. Child's join operation is the
- * inverse of the parent
- * @return new child of the QueryBuilder
- */
- public QueryParamBuilder newChild() {
- // Reverse operation of this builder
- Op operation = this.operation.equals(Op.AND.toString()) ? Op.OR : Op.AND;
- return this.newChild(operation);
- }
-
- /**
- * Create a new child builder attached to this one
- * @param operation - join operation
- * @return new child of the QueryBuilder
- */
- private QueryParamBuilder newChild(Op operation) {
- QueryParamBuilder child = new QueryParamBuilder(this, operation);
- if (children == null) {
- children = new LinkedList<>();
- }
- children.add(child);
- return child;
- }
-
- /**
- * Get query arguments
- * @return query arguments as a map of arg name and arg value suitable for
- * query.executeWithMap
- */
- public Map<String, Object> getArguments() {
- return arguments;
- }
-
- /**
- * Get query string - reconstructs the query string from all the parts and children.
- * @return Query string which can be matched with arguments to execute a query.
- */
- @Override
- public String toString() {
- if (children == null && queryParts.isEmpty()) {
- return "";
- }
- if (children == null) {
- return "(" + Joiner.on(operation).join(queryParts) + ")";
- }
- // Concatenate our query parts with all children
- List<String> result = new LinkedList<>(queryParts);
- for (Object child: children) {
- result.add(child.toString());
- }
- return "(" + Joiner.on(operation).join(result) + ")";
- }
-
- /**
- * Add parameter for field fieldName with given value where value is any Object
- * @param fieldName Field name to query for
- * @param value Field value (can be any Object)
- */
- public QueryParamBuilder addObject(String fieldName, Object value) {
- return addCustomParam("this." + fieldName + " == :" + fieldName,
- fieldName, value);
- }
-
- /**
- * Add string parameter for field fieldName
- * @param fieldName name of the field
- * @param value String value. Value is normalized - converted to lower case and trimmed
- * @return this
- */
- public QueryParamBuilder add(String fieldName, String value) {
- return addCommon(fieldName, value, false);
- }
-
- /**
- * Add string parameter to field value with or without normalization
- * @param fieldName field name of the field
- * @param value String value, inserted as is if preserveCase is true, normalized otherwise
- * @param preserveCase if true, trm and lowercase the value.
- * @return this
- */
- public QueryParamBuilder add(String fieldName, String value, boolean preserveCase) {
- return addCommon(fieldName, value, preserveCase);
- }
-
- /**
- * Add condition that fieldName is not equal to NULL
- * @param fieldName field name to compare to NULL
- * @return this
- */
- public QueryParamBuilder addNotNull(String fieldName) {
- queryParts.add(String.format("this.%s != \"%s\"", fieldName, SentryStore.NULL_COL));
- return this;
- }
-
- /**
- * Add condition that fieldName is equal to NULL
- * @param fieldName field name to compare to NULL
- * @return this
- */
- public QueryParamBuilder addNull(String fieldName) {
- queryParts.add(String.format("this.%s == \"%s\"", fieldName, SentryStore.NULL_COL));
- return this;
- }
-
- /**
- * Add custom string for evaluation together with a single parameter.
- * This is used in cases where we need expression different from this.name == value
- * @param expr String expression containing ':< paramName>' somewhere
- * @param paramName parameter name
- * @param value parameter value
- * @return this
- */
- QueryParamBuilder addCustomParam(String expr, String paramName, Object value) {
- arguments.put(paramName, value);
- queryParts.add(expr);
- return this;
- }
-
- /**
- * Add arbitrary query string without parameters
- * @param expr String expression
- * @return this
- */
- QueryParamBuilder addString(String expr) {
- queryParts.add(expr);
- return this;
- }
-
- /**
- * Add common filter for set of Sentry roles. This is used to simplify creating filters for
- * privileges belonging to the specified set of roles.
- * @param query Query used for search
- * @param paramBuilder paramBuilder for parameters
- * @param roleNames set of role names
- * @return paramBuilder supplied or a new one if the supplied one is null.
- */
- public static QueryParamBuilder addRolesFilter(Query query, QueryParamBuilder paramBuilder,
- Set<String> roleNames) {
- query.declareVariables(MSentryRole.class.getName() + " role");
- if (paramBuilder == null) {
- paramBuilder = new QueryParamBuilder();
- }
- if (roleNames == null || roleNames.isEmpty()) {
- return paramBuilder;
- }
- paramBuilder.newChild().addSet("role.roleName == ", roleNames);
- paramBuilder.addString("roles.contains(role)");
- return paramBuilder;
- }
-
- /**
- * Add common filter for set of Sentry users. This is used to simplify creating filters for
- * privileges belonging to the specified set of users.
- * @param query Query used for search
- * @param paramBuilder paramBuilder for parameters
- * @param userNames set of user names
- * @return paramBuilder supplied or a new one if the supplied one is null.
- */
- public static QueryParamBuilder addUsersFilter(Query query, QueryParamBuilder paramBuilder,
- Set<String> userNames) {
- query.declareVariables(MSentryUser.class.getName() + " user");
- if (paramBuilder == null) {
- paramBuilder = new QueryParamBuilder();
- }
- if (userNames == null || userNames.isEmpty()) {
- return paramBuilder;
- }
- paramBuilder.newChild().addSet("user.userName == ", userNames);
- paramBuilder.addString("users.contains(user)");
- return paramBuilder;
- }
-
- /**
- * Add multiple conditions for set of values.
- * <p>
- * Example:
- * <pre>
- * Set<String>names = new HashSet<>();
- * names.add("foo");
- * names.add("bar");
- * names.add("bob");
- * paramBuilder.addSet("prefix == ", names);
- * // Expect:"(prefix == :var0 && prefix == :var1 && prefix == :var2)"
- * paramBuilder.toString());
- * // paramBuilder(getArguments()) contains mapping for var0, var1 and var2
- * </pre>
- * @param prefix common prefix to use for expression
- * @param values
- * @return this
- */
- QueryParamBuilder addSet(String prefix, Iterable<String> values) {
- if (values == null) {
- return this;
- }
-
- // Add expressions of the form 'prefix :var$i'
- for(String name: values) {
- // Append index to the varName
- String vName = "var" + paramId.toString();
- addCustomParam(prefix + ":" + vName, vName, name.trim().toLowerCase());
- paramId.incrementAndGet();
- }
- return this;
- }
-
- /**
- * Construct a default QueryParamBuilder joining arguments with &&
- */
- private QueryParamBuilder() {
- this(Op.AND);
- }
-
- /**
- * Construct generic QueryParamBuilder
- * @param operation join operation (AND or OR)
- */
- private QueryParamBuilder(Op operation) {
- this.arguments = new HashMap<>();
- this.operation = operation.toString();
- this.paramId = new AtomicLong(0);
- }
-
- /**
- * Internal constructor used for children - reuses arguments from parent
- * @param parent parent element
- * @param operation join operation
- */
- private QueryParamBuilder(QueryParamBuilder parent, Op operation) {
- this.arguments = parent.getArguments();
- this.operation = operation.toString();
- this.paramId = parent.paramId;
- }
-
- /**
- * common code for adding string values
- * @param fieldName field name to add
- * @param value field value
- * @param preserveCase if true, do not trim and lower
- * @return this
- * @throws IllegalArgumentException if fieldName was already added before
- */
- private QueryParamBuilder addCommon(String fieldName, String value,
- boolean preserveCase) {
- Object oldValue;
- if (preserveCase) {
- oldValue = arguments.put(fieldName, SentryStore.toNULLCol(SentryStore.safeTrim(value)));
- } else {
- oldValue = arguments.put(fieldName, SentryStore.toNULLCol(SentryStore.safeTrimLower(value)));
- }
- if (oldValue != null) {
- // Attempt to insert the same field twice
- throw new IllegalArgumentException("field " + fieldName + "already exists");
- }
- queryParts.add("this." + fieldName + " == :" + fieldName);
- return this;
- }
-}