[
https://issues.apache.org/jira/browse/RYA-119?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15703553#comment-15703553
]
ASF GitHub Bot commented on RYA-119:
------------------------------------
Github user isper3at commented on a diff in the pull request:
https://github.com/apache/incubator-rya/pull/124#discussion_r89906753
--- Diff:
dao/mongodb.rya/src/main/java/org/apache/rya/mongodb/document/visibility/DocumentVisibility.java
---
@@ -0,0 +1,584 @@
+/*
+ * 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.rya.mongodb.document.visibility;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.io.WritableComparator;
+
+/**
+ * Validate the document visibility is a valid expression and set the
visibility for a Mutation. See {@link
DocumentVisibility#DocumentVisibility(byte[])} for the
+ * definition of an expression.
+ *
+ * <p>
+ * The expression is a sequence of characters from the set [A-Za-z0-9_-.]
along with the binary operators "&" and "|" indicating that both operands
are
+ * necessary, or the either is necessary. The following are valid
expressions for visibility:
+ *
+ * <pre>
+ * A
+ * A|B
+ * (A|B)&(C|D)
+ * orange|(red&yellow)
+ * </pre>
+ *
+ * <p>
+ * The following are not valid expressions for visibility:
+ *
+ * <pre>
+ * A|B&C
+ * A=B
+ * A|B|
+ * A&|B
+ * ()
+ * )
+ * dog|!cat
+ * </pre>
+ *
+ * <p>
+ * In addition to the base set of visibilities, any character can be used
in the expression if it is quoted. If the quoted term contains '"' or '\',
then
+ * escape the character with '\'. The {@link #quote(String)} method can be
used to properly quote and escape terms automatically. The following is an
example of
+ * a quoted term:
+ *
+ * <pre>
+ * "A#C" & B
+ * </pre>
+ */
+public class DocumentVisibility {
+
+ Node node = null;
+ private byte[] expression;
+
+ /**
+ * Accessor for the underlying byte string.
+ *
+ * @return byte array representation of a visibility expression
+ */
+ public byte[] getExpression() {
+ return expression;
+ }
+
+ /**
+ * The node types in a parse tree for a visibility expression.
+ */
+ public static enum NodeType {
+ EMPTY, TERM, OR, AND,
+ }
+
+ /**
+ * All empty nodes are equal and represent the same value.
+ */
+ private static final Node EMPTY_NODE = new Node("".getBytes(),
NodeType.EMPTY, 0);
+
+ /**
+ * A node in the parse tree for a visibility expression.
+ */
+ public static class Node {
+ /**
+ * An empty list of nodes.
+ */
+ public final static List<Node> EMPTY = Collections.emptyList();
+ NodeType type;
+ int start;
+ int end;
+ List<Node> children = EMPTY;
+ byte[] expression;
+
+ public Node(final byte[] expression, final NodeType type, final int
start) {
+ this.type = type;
+ this.start = start;
+ this.end = start + 1;
+ this.expression = expression;
+ }
+
+ public Node(final byte[] expression, final int start, final int end) {
+ this.type = NodeType.TERM;
+ this.start = start;
+ this.end = end;
+ this.expression = expression;
+ }
+
+ public void add(final Node child) {
+ if (children == EMPTY) {
+ children = new ArrayList<>();
+ }
+
+ children.add(child);
+ }
+
+ public NodeType getType() {
+ return type;
+ }
+
+ public List<Node> getChildren() {
+ return children;
+ }
+
+ public int getTermStart() {
+ return start;
+ }
+
+ public int getTermEnd() {
+ return end;
+ }
+
+ public byte[] getExpression() {
+ return expression;
+ }
+
+ public ByteSequence getTerm(final byte expression[]) {
+ if (type != NodeType.TERM) {
+ throw new RuntimeException();
+ }
+
+ if (expression[start] == '"') {
+ // its a quoted term
+ final int qStart = start + 1;
+ final int qEnd = end - 1;
+
+ return new ArrayByteSequence(expression, qStart, qEnd - qStart);
+ }
+ return new ArrayByteSequence(expression, start, end - start);
+ }
+ }
+
+ /**
+ * A node comparator. Nodes sort according to node type, terms sort
lexicographically. AND and OR nodes sort by number of children, or if the same
by
+ * corresponding children.
+ */
+ public static class NodeComparator implements Comparator<Node>,
Serializable {
+
+ private static final long serialVersionUID = 1L;
+ byte[] text;
+
+ /**
+ * Creates a new comparator.
+ *
+ * @param text
+ * expression string, encoded in UTF-8
+ */
+ public NodeComparator(final byte[] text) {
+ this.text = text;
+ }
+
+ @Override
+ public int compare(final Node a, final Node b) {
+ int diff = a.type.ordinal() - b.type.ordinal();
+ if (diff != 0) {
+ return diff;
+ }
+ switch (a.type) {
+ case EMPTY:
+ return 0; // All empty nodes are the same
+ case TERM:
+ return WritableComparator.compareBytes(text, a.start, a.end -
a.start, text, b.start, b.end - b.start);
+ case OR:
+ case AND:
+ diff = a.children.size() - b.children.size();
+ if (diff != 0) {
+ return diff;
+ }
+ for (int i = 0; i < a.children.size(); i++) {
+ diff = compare(a.children.get(i), b.children.get(i));
+ if (diff != 0) {
+ return diff;
+ }
+ }
+ }
+ return 0;
+ }
+ }
+
+ /*
+ * Convience method that delegates to normalize with a new
NodeComparator constructed using the supplied expression.
+ */
+ public static Node normalize(final Node root, final byte[] expression) {
+ return normalize(root, expression, new NodeComparator(expression));
+ }
+
+ // @formatter:off
+ /*
+ * Walks an expression's AST in order to:
+ * 1) roll up expressions with the same operant (`a&(b&c) becomes
a&b&c`)
+ * 2) sorts labels lexicographically (permutations of `a&b&c` are
re-ordered to appear as `a&b&c`)
+ * 3) dedupes labels (`a&b&a` becomes `a&b`)
+ */
+ // @formatter:on
+ public static Node normalize(final Node root, final byte[] expression,
final NodeComparator comparator) {
+ if (root.type != NodeType.TERM) {
+ final TreeSet<Node> rolledUp = new TreeSet<>(comparator);
+ final java.util.Iterator<Node> itr = root.children.iterator();
+ while (itr.hasNext()) {
+ final Node c = normalize(itr.next(), expression, comparator);
+ if (c.type == root.type) {
+ rolledUp.addAll(c.children);
+ itr.remove();
+ }
+ }
+ rolledUp.addAll(root.children);
+ root.children.clear();
+ root.children.addAll(rolledUp);
+
+ // need to promote a child if it's an only child
+ if (root.children.size() == 1) {
+ return root.children.get(0);
+ }
+ }
+
+ return root;
+ }
+
+ /*
+ * Walks an expression's AST and appends a string representation to a
supplied StringBuilder. This method adds parens where necessary.
+ */
+ public static void stringify(final Node root, final byte[] expression,
final StringBuilder out) {
+ if (root.type == NodeType.TERM) {
+ out.append(new String(expression, root.start, root.end - root.start,
UTF_8));
+ } else {
+ String sep = "";
+ for (final Node c : root.children) {
+ out.append(sep);
+ final boolean parens = (c.type != NodeType.TERM && root.type !=
c.type);
+ if (parens) {
+ out.append("(");
+ }
+ stringify(c, expression, out);
+ if (parens) {
+ out.append(")");
+ }
+ sep = root.type == NodeType.AND ? "&" : "|";
+ }
+ }
+ }
+
+ /**
+ * Generates a byte[] that represents a normalized, but logically
equivalent, form of this evaluator's expression.
+ *
+ * @return normalized expression in byte[] form
+ */
+ public byte[] flatten() {
+ final Node normRoot = normalize(node, expression);
+ final StringBuilder builder = new StringBuilder(expression.length);
+ stringify(normRoot, expression, builder);
+ return builder.toString().getBytes(UTF_8);
+ }
+
+ private static class DocumentVisibilityParser {
+ private int index = 0;
+ private int parens = 0;
+
+ public DocumentVisibilityParser() {}
+
+ Node parse(final byte[] expression) {
+ if (expression.length > 0) {
+ final Node node = parse_(expression);
+ if (node == null) {
+ throw new BadArgumentException("operator or missing parens", new
String(expression, UTF_8), index - 1);
+ }
+ if (parens != 0) {
+ throw new BadArgumentException("parenthesis mis-match", new
String(expression, UTF_8), index - 1);
+ }
+ return node;
+ }
+ return null;
+ }
+
+ Node processTerm(final int start, final int end, final Node expr,
final byte[] expression) {
+ if (start != end) {
+ if (expr != null) {
+ throw new BadArgumentException("expression needs | or
&", new String(expression, UTF_8), start);
+ }
+ return new Node(expression, start, end);
+ }
+ if (expr == null) {
+ throw new BadArgumentException("empty term", new
String(expression, UTF_8), start);
+ }
+ return expr;
+ }
+
+ Node parse_(final byte[] expression) {
+ Node result = null;
+ Node expr = null;
+ final int wholeTermStart = index;
+ int subtermStart = index;
+ boolean subtermComplete = false;
+
+ while (index < expression.length) {
+ switch (expression[index++]) {
+ case '&': {
+ expr = processTerm(subtermStart, index - 1, expr, expression);
+ if (result != null) {
+ if (!result.type.equals(NodeType.AND)) {
+ throw new BadArgumentException("cannot mix &
and |", new String(expression, UTF_8), index - 1);
+ }
+ } else {
+ result = new Node(expression, NodeType.AND, wholeTermStart);
+ }
+ result.add(expr);
+ expr = null;
+ subtermStart = index;
+ subtermComplete = false;
+ break;
+ }
+ case '|': {
+ expr = processTerm(subtermStart, index - 1, expr, expression);
+ if (result != null) {
+ if (!result.type.equals(NodeType.OR)) {
+ throw new BadArgumentException("cannot mix |
and &", new String(expression, UTF_8), index - 1);
+ }
+ } else {
+ result = new Node(expression, NodeType.OR, wholeTermStart);
+ }
+ result.add(expr);
+ expr = null;
+ subtermStart = index;
+ subtermComplete = false;
+ break;
+ }
+ case '(': {
+ parens++;
+ if (subtermStart != index - 1 || expr != null) {
+ throw new BadArgumentException("expression
needs & or |", new String(expression, UTF_8), index - 1);
+ }
+ expr = parse_(expression);
+ subtermStart = index;
+ subtermComplete = false;
+ break;
+ }
+ case ')': {
+ parens--;
+ final Node child = processTerm(subtermStart, index - 1, expr,
expression);
+ if (child == null && result == null) {
+ throw new BadArgumentException("empty
expression not allowed", new String(expression, UTF_8), index);
+ }
+ if (result == null) {
+ return child;
+ }
+ if (result.type == child.type) {
+ for (final Node c : child.children) {
+ result.add(c);
+ }
+ } else {
+ result.add(child);
+ }
+ result.end = index - 1;
+ return result;
+ }
+ case '"': {
+ if (subtermStart != index - 1) {
+ throw new BadArgumentException("expression
needs & or |", new String(expression, UTF_8), index - 1);
+ }
+
+ while (index < expression.length && expression[index] != '"') {
+ if (expression[index] == '\\') {
+ index++;
+ if (expression[index] != '\\' && expression[index] != '"')
{
+ throw new BadArgumentException("invalid
escaping within quotes", new String(expression, UTF_8), index - 1);
+ }
+ }
+ index++;
+ }
+
+ if (index == expression.length) {
+ throw new BadArgumentException("unclosed
quote", new String(expression, UTF_8), subtermStart);
+ }
+
+ if (subtermStart + 1 == index) {
+ throw new BadArgumentException("empty term",
new String(expression, UTF_8), subtermStart);
+ }
+
+ index++;
+
+ subtermComplete = true;
+
+ break;
+ }
+ default: {
+ if (subtermComplete) {
+ throw new BadArgumentException("expression
needs & or |", new String(expression, UTF_8), index - 1);
+ }
+
+ final byte c = expression[index - 1];
+ if (!Authorizations.isValidAuthChar(c)) {
+ throw new BadArgumentException("bad character
(" + c + ")", new String(expression, UTF_8), index - 1);
+ }
+ }
+ }
+ }
+ final Node child = processTerm(subtermStart, index, expr,
expression);
+ if (result != null) {
+ result.add(child);
+ result.end = index;
+ } else {
+ result = child;
+ }
+ if (result.type != NodeType.TERM) {
+ if (result.children.size() < 2) {
+ throw new BadArgumentException("missing term", new
String(expression, UTF_8), index);
+ }
+ }
+ return result;
+ }
+ }
+
+ private void validate(final byte[] expression) {
+ if (expression != null && expression.length > 0) {
+ final DocumentVisibilityParser p = new DocumentVisibilityParser();
+ node = p.parse(expression);
+ } else {
+ node = EMPTY_NODE;
+ }
+ this.expression = expression;
+ }
+
+ /**
+ * Creates an empty visibility. Normally, elements with empty visibility
can be seen by everyone. Though, one could change this behavior with filters.
+ *
+ * @see #DocumentVisibility(String)
+ */
+ public DocumentVisibility() {
+ this(new byte[] {});
+ }
+
+ /**
+ * Creates a document visibility for a Mutation.
+ *
+ * @param expression
+ * An expression of the rights needed to see this mutation. The
expression syntax is defined at the class-level documentation
+ */
+ public DocumentVisibility(final String expression) {
+ this(expression.getBytes(UTF_8));
+ }
+
+ /**
+ * Creates a document visibility for a Mutation.
+ *
+ * @param expression
+ * visibility expression
+ * @see #DocumentVisibility(String)
+ */
+ public DocumentVisibility(final Text expression) {
+ this(TextUtil.getBytes(expression));
+ }
+
+ /**
+ * Creates a document visibility for a Mutation from a string already
encoded in UTF-8 bytes.
+ *
+ * @param expression
+ * visibility expression, encoded as UTF-8 bytes
+ * @see #DocumentVisibility(String)
+ */
+ public DocumentVisibility(final byte[] expression) {
+ validate(expression);
+ }
+
+ @Override
+ public String toString() {
+ return "[" + new String(expression, UTF_8) + "]";
+ }
+
+ /**
+ * See {@link #equals(DocumentVisibility)}
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof DocumentVisibility) {
+ return equals(obj);
+ }
+ return false;
+ }
+
+ /**
+ * Compares two DocumentVisibilities for string equivalence, not as a
meaningful comparison of terms and conditions.
+ *
+ * @param otherLe
+ * other document visibility
+ * @return true if this visibility equals the other via string comparison
+ */
+ public boolean equals(final DocumentVisibility otherLe) {
+ return Arrays.equals(expression, otherLe.expression);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(expression);
+ }
+
+ /**
+ * Gets the parse tree for this document visibility.
+ *
+ * @return parse tree node
+ */
+ public Node getParseTree() {
+ return node;
+ }
+
+ /**
+ * Properly quotes terms in a document visibility expression. If no
quoting is needed, then nothing is done.
+ *
+ * <p>
+ * Examples of using quote :
+ *
+ * <pre>
+ * import static
org.apache.rya.mongodb.document.visibility.DocumentVisibility.quote;
+ * .
+ * .
+ * .
+ * DocumentVisibility dv = new DocumentVisibility(quote("A#C")
+ "&" + quote("FOO"));
+ * </pre>
+ *
+ * @param term
+ * term to quote
+ * @return quoted term (unquoted if unnecessary)
+ */
+ public static String quote(final String term) {
--- End diff --
why not create a new class level String of a UTF_8 quote? UTF_8_QUOTE +
"some auth" + UTF_8_QUOTE
> Add visibility support to MongoDB
> ---------------------------------
>
> Key: RYA-119
> URL: https://issues.apache.org/jira/browse/RYA-119
> Project: Rya
> Issue Type: Improvement
> Components: dao
> Affects Versions: 3.2.10
> Reporter: Andrew Smith
> Assignee: Eric White
>
> Currently, when querying mongo, visibility is ignored. Need to add support
> for visibility when querying mongo.
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)