blob: c49e2442784d620c9ccb54d00409798eea56798c [file] [log] [blame]
/**
* 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.hdfs;
import java.util.*;
import com.google.common.base.Joiner;
import org.apache.hadoop.fs.Path;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A non thread-safe implementation of {@link AuthzPaths}. It abstracts over the
* core data-structures used to efficiently handle request from clients of
* the {@link AuthzPaths} paths. All updates to this class is handled by the
* thread safe {@link UpdateableAuthzPaths} class
*/
public class HMSPaths implements AuthzPaths {
private static final Logger LOG = LoggerFactory.getLogger(HMSPaths.class);
@VisibleForTesting
static List<String> getPathElements(String path) {
String trimmedPath = path.trim();
if (trimmedPath.charAt(0) != Path.SEPARATOR_CHAR) {
throw new IllegalArgumentException("It must be an absolute path: " +
trimmedPath);
}
List<String> list = new ArrayList<String>(32);
int idx = 0;
int found = trimmedPath.indexOf(Path.SEPARATOR_CHAR, idx);
while (found > -1) {
if (found > idx) {
list.add(trimmedPath.substring(idx, found));
}
idx = found + 1;
found = trimmedPath.indexOf(Path.SEPARATOR_CHAR, idx);
}
if (idx < trimmedPath.length()) {
list.add(trimmedPath.substring(idx));
}
return list;
}
@VisibleForTesting
static List<List<String>> getPathsElements(List<String> paths) {
List<List<String>> pathsElements = new ArrayList<List<String>>(paths.size());
for (String path : paths) {
pathsElements.add(getPathElements(path));
}
return pathsElements;
}
// used for more compact logging
static List<String> assemblePaths(List<List<String>> pathElements) {
if (pathElements == null) {
return Collections.emptyList();
}
List<String> paths = new ArrayList<>(pathElements.size());
for (List<String> path : pathElements) {
StringBuffer sb = new StringBuffer();
for (String elem : path) {
sb.append(Path.SEPARATOR_CHAR).append(elem);
}
paths.add(sb.toString());
}
return paths;
}
@VisibleForTesting
enum EntryType {
DIR(true),
PREFIX(false),
AUTHZ_OBJECT(false);
private boolean removeIfDangling;
private EntryType(boolean removeIfDangling) {
this.removeIfDangling = removeIfDangling;
}
public boolean isRemoveIfDangling() {
return removeIfDangling;
}
public byte getByte() {
return (byte)toString().charAt(0);
}
public static EntryType fromByte(byte b) {
switch (b) {
case ((byte)'D'):
return DIR;
case ((byte)'P'):
return PREFIX;
case ((byte)'A'):
return AUTHZ_OBJECT;
default:
return null;
}
}
}
/**
* Entry represents a node in the tree that {@see HMSPaths} uses to organize the auth objects.
* This tree maps the entries in the filesystem namespace in HDFS, and the auth objects are
* associated to each entry.
*
* Each individual entry in the tree contains a children map that maps the path element
* (filename) to the child entry.
*
* For example, for a HDFS file or directory, "hdfs://foo/bar", it is presented in HMSPaths as the
* following tree, of which the root node is {@link HMSPaths#root}.
*
* Entry("/", children: {
* "foo": Entry("foo", children: {
* "bar": Entry("bar"),
* "zoo": Entry("zoo"),
* }),
* "tmp": Entry("tmp"),
* ...
* });
*
* Note that the URI scheme is not presented in the tree.
*/
@VisibleForTesting
static class Entry {
private Entry parent;
private EntryType type;
private String pathElement;
// A set (or single object when set size is 1) of authorizable objects associated
// with this entry. Authorizable object should be case insensitive. The set is
// allocated lazily to avoid wasting memory due to empty sets.
private Object authzObjs;
// Path of child element to the path entry mapping, e.g. 'b' -> '/a/b'
// This is allocated lazily to avoid wasting memory due to empty maps.
private Map<String, Entry> children;
/**
* Construct an Entry with one authzObj.
*
* @param parent the parent node. If not specified, this entry is a root entry.
* @param pathElement the path element of this entry on the tree.
* @param type Entry type.
* @param authzObj the authzObj.
*/
Entry(Entry parent, String pathElement, EntryType type, String authzObj) {
this.parent = parent;
this.type = type;
this.pathElement = pathElement.intern();
addAuthzObj(authzObj);
}
/**
* Construct an Entry with a set of authz objects.
* @param parent the parent node. If not specified, this entry is a root entry.
* @param pathElement the path element of this entry on the tree.
* @param type entry type.
* @param authzObjs a collection of authz objects.
*/
Entry(Entry parent, String pathElement, EntryType type, Collection<String> authzObjs) {
this.parent = parent;
this.type = type;
this.pathElement = pathElement.intern();
addAuthzObjs(authzObjs);
}
Entry getChild(String pathElement) {
if (children == null) {
return null;
}
return children.get(pathElement);
}
void putChild(String pathElement, Entry entry) {
if (children == null) {
// We allocate this map lazily and with small initial capacity to avoid
// memory waste due to empty and underpopulated maps.
children = new HashMap<>(2);
}
LOG.debug("[putChild]Adding {} as child to {}", entry.toString(), this.toString());
children.put(pathElement.intern(), entry);
}
Entry removeChild(String pathElement) {
LOG.debug("[removeChild]Removing {} from children", pathElement);
return children.remove(pathElement);
}
boolean hasChildren() { return children != null && !children.isEmpty(); }
int numChildren() { return children == null ? 0 : children.size(); }
Collection<Entry> childrenValues() {
return children != null ? children.values() : Collections.<Entry>emptyList();
}
void clearAuthzObjs() {
LOG.debug("Clearing authzObjs from {}", this.toString());
authzObjs = null;
}
void removeAuthzObj(String authzObj) {
LOG.debug("Removing {} from {}", authzObj, authzObjs);
if (authzObjs != null) {
if (authzObjs instanceof Set) {
Set<String> authzObjsSet = (Set<String>) authzObjs;
authzObjsSet.remove(authzObj);
if (authzObjsSet.size() == 1) {
authzObjs = authzObjsSet.iterator().next();
}
} else if (authzObjs.equals(authzObj)){
authzObjs = null;
}
}
}
void addAuthzObj(String authzObj) {
LOG.debug("Adding {} to {}", authzObj, this.toString());
if (authzObj != null) {
if (authzObjs == null) {
authzObjs = authzObj;
} else {
Set<String> authzObjsSet;
if (authzObjs instanceof String) {
if (authzObjs.equals(authzObj)) {
return;
} else {
authzObjs = authzObjsSet = newTreeSetWithElement((String) authzObjs);
}
} else {
authzObjsSet = (Set) authzObjs;
}
authzObjsSet.add(authzObj.intern());
}
}
LOG.debug("Added {} to {}", authzObj, this.toString());
}
void addAuthzObjs(Collection<String> authzObjs) {
LOG.debug("Adding {} to {}", authzObjs, this.toString());
if (authzObjs != null) {
for (String authzObj : authzObjs) {
addAuthzObj(authzObj.intern());
}
}
}
private void setType(EntryType type) {
this.type = type;
}
protected void removeParent() {
parent = null;
}
@Override
public String toString() {
return String.format("Entry[%s:%s -> authObj: %s]",
type, getFullPath(), authzObjsToString());
}
private String authzObjsToString() {
if (authzObjs == null) {
return "";
} else if (authzObjs instanceof String) {
return (String) authzObjs;
} else {
return Joiner.on(",").join((Set) authzObjs);
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Entry other = (Entry) obj;
if (parent == null) {
if (other.parent != null) {
return false;
}
} else if (!parent.equals(other.parent)) {
return false;
}
if (type == null) {
if (other.type != null) {
return false;
}
} else if (!type.equals(other.type)) {
return false;
}
if (pathElement == null) {
if (other.pathElement != null) {
return false;
}
} else if (!pathElement.equals(other.pathElement)) {
return false;
}
if (authzObjs == null) {
if (other.authzObjs != null) {
return false;
}
} else if (!authzObjs.equals(other.authzObjs)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((parent == null) ? 0 : parent.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((pathElement == null) ? 0 : pathElement.hashCode());
result = prime * result + ((authzObjs == null) ? 0 : authzObjs.hashCode());
return result;
}
/**
* Create all missing parent entries for an given entry, and return the parent entry for
* the entry.
*
* For example, if {@code pathElements} is ["a", "b", "c"], it creates entries "/a" and "/a/b"
* if they do not exist, and then returns "/a/b" as the parent entry.
*
* @param pathElements path elements of the entry.
* @return the direct parent entry of the given entry.
*/
private Entry createParent(List<String> pathElements) {
Entry parent = this;
LOG.debug("[createParent]Trying to create entires for {} ", pathElements);
// The loop is resilient to 0 or 1 element list.
for (int i = 0; i < pathElements.size() - 1; i++) {
String elem = pathElements.get(i);
Entry child = parent.getChild(elem);
if (child == null) {
child = new Entry(parent, elem, EntryType.DIR, (String) null);
parent.putChild(elem, child);
LOG.debug("[createParent] Entry {} created", child.toString());
}
parent = child;
}
return parent;
}
/**
* Create a child entry based on the path, type and authzObj that
* associates with it.
*
* @param pathElements a path split into segments.
* @param type the type of the child entry.
* @param authzObj the authorizable Object associates with the entry.
* @return Returns the child entry.
*/
private Entry createChild(List<String> pathElements, EntryType type,
String authzObj) {
LOG.debug("[createChild] Creating child for {} with path {}", authzObj, pathElements);
// Create all the parent entries on the path if they do not exist.
Entry entryParent = createParent(pathElements);
String lastPathElement = pathElements.get(pathElements.size() - 1);
Entry child = entryParent.getChild(lastPathElement);
// Create the child entry if not found. If found and the entry is
// already a prefix or authzObj type, then only add the authzObj.
// If the entry already existed as dir, we change it to be a authzObj,
// and add the authzObj.
if (child == null) {
child = new Entry(entryParent, lastPathElement, type, authzObj);
entryParent.putChild(lastPathElement, child);
LOG.debug("Created child entry {}", child);
} else if (type == EntryType.AUTHZ_OBJECT &&
(child.getType() == EntryType.PREFIX || child.getType() == EntryType.AUTHZ_OBJECT)) {
child.addAuthzObj(authzObj);
LOG.debug("[createChild] Found Child {}, updated authzObj", child.toString());
} else if (type == EntryType.AUTHZ_OBJECT &&
child.getType() == EntryType.DIR) {
child.addAuthzObj(authzObj);
child.setType(EntryType.AUTHZ_OBJECT);
LOG.debug("[createChild] Found Child {}, updated authzObj", child.toString());
LOG.debug("[createChild] Updating type to", child.getType().toString());
}
return child;
}
public static Entry createRoot(boolean asPrefix) {
LOG.debug("Creating entry for root");
return new Entry(null, "/", asPrefix
? EntryType.PREFIX : EntryType.DIR, (String) null);
}
private String toPath(List<String> arr) {
StringBuilder sb = new StringBuilder();
for (String s : arr) {
sb.append(Path.SEPARATOR).append(s);
}
return sb.toString();
}
public Entry createPrefix(List<String> pathElements) {
LOG.debug("Creating entries for prefix paths", pathElements.toString());
Entry prefix = findPrefixEntry(pathElements);
if (prefix != null) {
throw new IllegalArgumentException(String.format(
"%s: createPrefix(%s): cannot add prefix under an existing prefix '%s'",
this, pathElements, prefix.getFullPath()));
}
return createChild(pathElements, EntryType.PREFIX, null);
}
public Entry createAuthzObjPath(List<String> pathElements, String authzObj) {
Entry entry = null;
LOG.debug("createAuthzObjPath authzObj:{} paths: {}", authzObj, pathElements);
Entry prefix = findPrefixEntry(pathElements);
if (prefix != null) {
// we only create the entry if is under a prefix, else we ignore it
entry = createChild(pathElements, EntryType.AUTHZ_OBJECT, authzObj);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("%s: createAuthzObjPath(%s, %s): outside of prefix, skipping",
this, authzObj, pathElements));
}
}
return entry;
}
/**
* Delete this entry from its parent.
*/
private void deleteFromParent() {
LOG.debug("[deleteFromParent] Attempting to remove path: {}", this.getFullPath());
if (getParent() != null) {
LOG.debug("Child in Parent Entry with path: {} is removed", getParent().getFullPath());
getParent().removeChild(getPathElement());
getParent().deleteIfDangling();
parent = null;
} else {
LOG.warn("Parent for {} not found", this.toString());
}
}
public void deleteAuthzObject(String authzObj) {
LOG.debug("[deleteAuthzObject] Removing authObj:{} from path {}", authzObj, this.toString());
if(!getAuthzObjs().contains(authzObj)) {
return;
}
if (getParent() != null) {
if (!hasChildren()) {
// Remove the authzObj on the path entry. If the path
// entry no longer maps to any authzObj, removes the
// entry recursively.
if (authzObjs != null) {
removeAuthzObj(authzObj);
}
if (authzObjs == null) {
LOG.debug("Deleting path {}", this.toString());
deleteFromParent();
}
} else {
// if the entry was for an authz object and has children, we
// change it to be a dir entry. And remove the authzObj on
// the path entry.
if (getType() == EntryType.AUTHZ_OBJECT) {
if (authzObjs != null) {
removeAuthzObj(authzObj);
}
if(getAuthzObjsSize() == 0) {
LOG.debug("Entry with path: {} is changed to DIR", this.getFullPath());
setType(EntryType.DIR);
}
}
}
}
}
/**
* Move this Entry under the new parent.
* @param newParent the new parent.
* @param pathElem the path element on the new path.
*
* @return true if success. Returns false if the target with the same name already exists.
*/
private void moveTo(Entry newParent, String pathElem) {
LOG.debug("Moving {} as a child to {}", this.toString(), newParent.toString());
Preconditions.checkNotNull(newParent);
Preconditions.checkArgument(!pathElem.isEmpty());
if (newParent.getChild(pathElem) != null) {
LOG.warn(String.format(
"Attempt to move %s to %s: entry with the same name %s already exists",
this, newParent, pathElem));
return;
}
deleteFromParent();
parent = newParent;
parent.putChild(pathElem, this);
pathElement = pathElem.intern();
}
public void delete() {
if (getParent() != null) {
if (!hasChildren()) {
deleteFromParent();
} else {
// if the entry was for an authz object and has children, we
// change it to be a dir entry.
if (getType() == EntryType.AUTHZ_OBJECT) {
setType(EntryType.DIR);
clearAuthzObjs();
LOG.debug("Entry with path: {} is changed to DIR", this.getFullPath());
}
}
}
}
private void deleteIfDangling() {
if (!hasChildren() && getType().isRemoveIfDangling()) {
LOG.debug("Deleting {} as it is dangling", this.toString());
delete();
}
}
public Entry getParent() {
return parent;
}
public EntryType getType() {
return type;
}
public String getPathElement() {
return pathElement;
}
/**
* @return the set of auth objects. The returned set should be used only
* for querying, not for any modifications. If you just want to find out
* the set size or whether it's empty, use the specialized getAuthzObjsSize()
* and isAuthzObjsEmpty() methods that performs better.
*/
Set<String> getAuthzObjs() {
if (authzObjs != null) {
if (authzObjs instanceof Set) {
return (Set<String>) authzObjs;
} else {
return newTreeSetWithElement((String) authzObjs);
}
} else {
return Collections.<String>emptySet();
}
}
int getAuthzObjsSize() {
if (authzObjs != null) {
if (authzObjs instanceof Set) {
return ((Set<String>) authzObjs).size();
} else {
return 1;
}
} else {
return 0;
}
}
boolean isAuthzObjsEmpty() {
return authzObjs == null;
}
private Set<String> newTreeSetWithElement(String el) {
Set<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
result.add(el);
return result;
}
public Entry findPrefixEntry(List<String> pathElements) {
Preconditions.checkArgument(pathElements != null,
"pathElements cannot be NULL");
return (getType() == EntryType.PREFIX)
? this : findPrefixEntry(pathElements, 0);
}
private Entry findPrefixEntry(List<String> pathElements, int index) {
Entry prefixEntry = null;
if (index == pathElements.size()) {
prefixEntry = null;
} else {
Entry child = getChild(pathElements.get(index));
if (child != null) {
if (child.getType() == EntryType.PREFIX) {
prefixEntry = child;
} else {
prefixEntry = child.findPrefixEntry(pathElements, index + 1);
}
}
}
return prefixEntry;
}
public Entry find(String[] pathElements, boolean isPartialMatchOk) {
Preconditions.checkArgument(
pathElements != null && pathElements.length > 0,
"pathElements cannot be NULL or empty");
return find(pathElements, 0, isPartialMatchOk, null);
}
private Entry find(String[] pathElements, int index,
boolean isPartialMatchOk, Entry lastAuthObj) {
Entry found = null;
if (index == pathElements.length) {
if (isPartialMatchOk && !isAuthzObjsEmpty()) {
found = this;
}
} else {
Entry child = getChild(pathElements[index]);
if (child != null) {
if (index == pathElements.length - 1) {
found = (!child.isAuthzObjsEmpty()) ? child : lastAuthObj;
} else {
found = child.find(pathElements, index + 1, isPartialMatchOk,
(!child.isAuthzObjsEmpty()) ? child : lastAuthObj);
}
} else {
if (isPartialMatchOk) {
found = lastAuthObj;
}
}
}
return found;
}
public String getFullPath() {
String path = getFullPath(this, new StringBuilder()).toString();
if (path.isEmpty()) {
path = Path.SEPARATOR;
}
return path;
}
private StringBuilder getFullPath(Entry entry, StringBuilder sb) {
if (entry.getParent() != null) {
getFullPath(entry.getParent(), sb).append(Path.SEPARATOR).append(
entry.getPathElement());
}
return sb;
}
}
private volatile Entry root;
private String[] prefixes;
// The hive authorized objects to path entries mapping.
// One authorized object can map to a set of path entries.
private Map<String, Set<Entry>> authzObjToEntries;
public HMSPaths() {
LOG.info(toString() + " (default) Initialized");
}
public HMSPaths(String[] pathPrefixes) {
boolean rootPrefix = false;
// Copy the array to avoid external modification
this.prefixes = Arrays.copyOf(pathPrefixes, pathPrefixes.length);
for (String pathPrefix : pathPrefixes) {
rootPrefix = rootPrefix || pathPrefix.equals(Path.SEPARATOR);
}
if (rootPrefix && pathPrefixes.length > 1) {
throw new IllegalArgumentException(
"Root is a path prefix, there cannot be other path prefixes");
}
root = Entry.createRoot(rootPrefix);
if (!rootPrefix) {
for (String pathPrefix : pathPrefixes) {
root.createPrefix(getPathElements(pathPrefix));
}
}
authzObjToEntries = new TreeMap<String, Set<Entry>>(String.CASE_INSENSITIVE_ORDER);
LOG.info("Sentry managed prefixes: " + prefixes.toString());
}
void _addAuthzObject(String authzObj, List<String> authzObjPaths) {
addAuthzObject(authzObj, getPathsElements(authzObjPaths));
}
void addAuthzObject(String authzObj, List<List<String>> authzObjPathElements) {
LOG.debug("Number of Objects: {}", authzObjToEntries.size());
LOG.debug(String.format("%s addAuthzObject(%s, %s)",
this, authzObj, assemblePaths(authzObjPathElements)));
Set<Entry> previousEntries = authzObjToEntries.get(authzObj);
Set<Entry> newEntries = new HashSet<Entry>(authzObjPathElements.size());
for (List<String> pathElements : authzObjPathElements) {
Entry e = root.createAuthzObjPath(pathElements, authzObj);
if (e != null) {
newEntries.add(e);
} else {
LOG.warn(String.format("%s addAuthzObject(%s, %s):" +
" Ignoring path %s, no prefix",
this, authzObj, assemblePaths(authzObjPathElements), pathElements));
}
}
authzObjToEntries.put(authzObj, newEntries);
LOG.debug("Path entries for {} are {}", authzObj, newEntries.toString());
if (previousEntries != null) {
previousEntries.removeAll(newEntries);
if (!previousEntries.isEmpty()) {
for (Entry entry : previousEntries) {
LOG.debug("Removing stale path {}", entry.toString());
entry.deleteAuthzObject(authzObj);
}
}
}
}
void addPathsToAuthzObject(String authzObj,
List<List<String>> authzObjPathElements, boolean createNew) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("%s addPathsToAuthzObject(%s, %s, %b)",
this, authzObj, assemblePaths(authzObjPathElements), createNew));
}
Set<Entry> entries = authzObjToEntries.get(authzObj);
if (entries != null) {
Set<Entry> newEntries = new HashSet<Entry>(authzObjPathElements.size());
for (List<String> pathElements : authzObjPathElements) {
Entry e = root.createAuthzObjPath(pathElements, authzObj);
if (e != null) {
newEntries.add(e);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("%s addPathsToAuthzObject(%s, %s, %b):" +
" Cannot create authz obj for path %s because it is outside of prefix",
this, authzObj, assemblePaths(authzObjPathElements), createNew, pathElements));
}
}
}
entries.addAll(newEntries);
LOG.debug("[addPathsToAuthzObject]Updated path entries for {} are {}", authzObj, entries.toString());
} else {
if (createNew) {
LOG.debug("No paths found for Object:{}, Adding new", authzObj);
addAuthzObject(authzObj, authzObjPathElements);
} else {
LOG.warn(String.format("%s addPathsToAuthzObject(%s, %s, %b):" +
" Path was not added to AuthzObject, could not find key in authzObjToPath",
this, authzObj, assemblePaths(authzObjPathElements), createNew));
}
}
}
void _addPathsToAuthzObject(String authzObj, List<String> authzObjPaths) {
addPathsToAuthzObject(authzObj, getPathsElements(authzObjPaths), false);
}
void addPathsToAuthzObject(String authzObj, List<List<String>> authzObjPaths) {
addPathsToAuthzObject(authzObj, authzObjPaths, false);
}
/*
1. Removes authzObj from all entries corresponding to the authzObjPathElements
( which also deletes the entry if no more authObjs to that path and does it recursively upwards)
2. Removes it from value of authzObjToPath Map for this authzObj key, does not reset entries to null even if entries is empty
*/
void deletePathsFromAuthzObject(String authzObj,
List<List<String>> authzObjPathElements) {
Set<Entry> entries = authzObjToEntries.get(authzObj);
if (entries != null) {
LOG.debug("[deletePathsFromAuthzObject] Paths for {} before delete are {}", authzObj, entries.toString());
for (List<String> pathElements : authzObjPathElements) {
Entry entry = root.find(
pathElements.toArray(new String[pathElements.size()]), false);
if (entry != null) {
entries.remove(entry);
entry.deleteAuthzObject(authzObj);
} else {
LOG.warn(String.format("%s deletePathsFromAuthzObject(%s, %s):" +
" Path %s was not deleted from AuthzObject, path not registered." +
" This is possible for implicit partition locations",
this, authzObj, assemblePaths(authzObjPathElements), pathElements));
}
}
LOG.debug("[deletePathsFromAuthzObject] Paths for {} after are {}", authzObj, entries.toString());
if(entries.size() == 0) {
authzObjToEntries.remove(authzObj);
LOG.debug("[deletePathsFromAuthzObject] Removing the mapping for {} as the entries are stale", authzObj);
}
} else {
LOG.warn(String.format("%s deletePathsFromAuthzObject(%s, %s):" +
" Path was not deleted from AuthzObject, could not find key in authzObjToPath",
this, authzObj, assemblePaths(authzObjPathElements)));
}
}
void deleteAuthzObject(String authzObj) {
LOG.debug(String.format("%s deleteAuthzObject(%s)", this, authzObj));
LOG.debug("Number of Objects: {}", authzObjToEntries.size());
Set<Entry> entries = authzObjToEntries.remove(authzObj);
if (entries != null) {
for (Entry entry : entries) {
entry.deleteAuthzObject(authzObj);
}
}
}
Set<String> findAuthzObject(List<String> pathElements) {
return findAuthzObject(pathElements.toArray(new String[0]));
}
@Override
public Set<String> findAuthzObject(String[] pathElements) {
return findAuthzObject(pathElements, true);
}
@Override
public Set<String> findAuthzObjectExactMatches(String[] pathElements) {
return findAuthzObject(pathElements, false);
}
/**
* Based on the isPartialOk flag, returns all authorizable Objects
* (database/table/partition) associated with the path, or if no match
* is found returns the first ancestor that has the associated
* authorizable objects.
*
* @param pathElements A path split into segments.
* @param isPartialOk Flag that indicates if patial path match is Ok or not.
* @return Returns a set of authzObjects authzObject associated with this path.
*/
public Set<String> findAuthzObject(String[] pathElements, boolean isPartialOk) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("%s findAuthzObject(%s, %b)",
this, Arrays.toString(pathElements), isPartialOk));
}
// Handle '/'
if (pathElements == null || pathElements.length == 0) {
return null;
}
Entry entry = root.find(pathElements, isPartialOk);
Set<String> authzObjSet = (entry != null) ? entry.getAuthzObjs() : null;
if ((authzObjSet == null || authzObjSet.isEmpty()) && LOG.isDebugEnabled()) {
LOG.debug(String.format("%s findAuthzObject(%s, %b) - no authzObject found",
this, Arrays.toString(pathElements), isPartialOk));
}
return authzObjSet;
}
/*
Following condition should be true: oldName != newName
If oldPath == newPath, Example: rename external table (only HMS meta data is updated)
=> new_table.add(new_path), new_table.add(old_table_partition_paths), old_table.dropAllPaths.
If oldPath != newPath, Example: rename managed table (HMS metadata is updated as well as physical files are moved to new location)
=> new_table.add(new_path), old_table.dropAllPaths.
*/
void renameAuthzObject(String oldName, List<List<String>> oldPathElems,
String newName, List<List<String>> newPathElems) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("%s renameAuthzObject({%s, %s} -> {%s, %s})",
this, oldName, assemblePaths(oldPathElems), newName, assemblePaths(newPathElems)));
}
if (oldPathElems == null || oldPathElems.isEmpty() ||
newPathElems == null || newPathElems.isEmpty() ||
newName == null || newName.equals(oldName)) {
LOG.warn(String.format("%s renameAuthzObject({%s, %s} -> {%s, %s})" +
": invalid inputs, skipping",
this, oldName, assemblePaths(oldPathElems), newName, assemblePaths(newPathElems)));
return;
}
// if oldPath == newPath, that is path has not changed as part of rename and hence new table
// needs to have old paths => new_table.add(old_table_partition_paths)
List<String> oldPathElements = oldPathElems.get(0);
List<String> newPathElements = newPathElems.get(0);
if (!oldPathElements.equals(newPathElements)) {
Entry oldEntry = root.find(oldPathElements.toArray(new String[0]), false);
Entry newParent = root.createParent(newPathElements);
if (oldEntry == null) {
LOG.warn(String.format("%s Moving old paths for renameAuthzObject({%s, %s} -> {%s, %s}) is skipped. Cannot find entry for old name",
this, oldName, assemblePaths(oldPathElems), newName, assemblePaths(newPathElems)));
} else {
oldEntry.moveTo(newParent, newPathElements.get(newPathElements.size() - 1));
}
}
// Re-write authObj from oldName to newName.
Set<Entry> entries = authzObjToEntries.get(oldName);
if (entries == null) {
LOG.warn(String.format("%s renameAuthzObject({%s, %s} -> {%s, %s}):" +
" cannot find oldName %s in authzObjToPath",
this, oldName, assemblePaths(oldPathElems), newName, assemblePaths(newPathElems), oldName));
} else {
authzObjToEntries.put(newName, entries);
for (Entry e : entries) {
e.addAuthzObj(newName);
if (e.getAuthzObjs().contains(oldName)) {
e.removeAuthzObj(oldName);
} else {
LOG.warn(String.format("%s renameAuthzObject({%s, %s} -> {%s, %s}):" +
" Unexpected state: authzObjToPath has an " +
"entry %s where one of the authz objects does not have oldName",
this, oldName, assemblePaths(oldPathElems), newName, assemblePaths(newPathElems), e));
}
}
}
// old_table.dropAllPaths
deleteAuthzObject(oldName);
}
@Override
public boolean isUnderPrefix(String[] pathElements) {
return root.findPrefixEntry(Lists.newArrayList(pathElements)) != null;
}
// Used by the serializer
String[] getPrefixes() {
return prefixes;
}
Entry getRootEntry() {
return root;
}
void setRootEntry(Entry root) {
this.root = root;
}
void setAuthzObjToEntryMapping(Map<String, Set<Entry>> mapping) {
authzObjToEntries = mapping;
}
/**
* For logging: collect all path entries into a list.
*
* Each Entry has informative toString() implementation,
* so we can print the returned value directly.
*
* Non-recursive traversal.
*/
public Collection<Entry> getAllEntries() {
Collection<Entry> entries = new ArrayList<>();
Stack<Entry> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
Entry entry = stack.pop();
entries.add(entry);
for (Entry child : entry.childrenValues()) { // handles entry.children == null
stack.push(child);
}
}
return entries;
}
@Override
public HMSPathsDumper getPathsDump() {
return new HMSPathsDumper(this);
}
@Override
public String toString() {
return String.format("%s:%s", getClass().getSimpleName(), Arrays.toString(prefixes));
}
public String dumpContent() {
return toString() + ": " + getAllEntries();
}
}