blob: 88d3b97ec85fe3ffe261c4549b13975af27f484b [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.ranger.plugin.policyengine;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
import org.apache.ranger.plugin.model.RangerServiceDef;
import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceEvaluator;
import org.apache.ranger.plugin.resourcematcher.RangerAbstractResourceMatcher;
import org.apache.ranger.plugin.resourcematcher.RangerResourceMatcher;
import org.apache.ranger.plugin.util.RangerPerfTracer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class RangerResourceTrie<T extends RangerPolicyResourceEvaluator> {
private static final Log LOG = LogFactory.getLog(RangerResourceTrie.class);
private static final Log TRACE_LOG = RangerPerfTracer.getPerfLogger("resourcetrie.trace");
private static final Log PERF_TRIE_INIT_LOG = RangerPerfTracer.getPerfLogger("resourcetrie.init");
private static final Log PERF_TRIE_OP_LOG = RangerPerfTracer.getPerfLogger("resourcetrie.op");
private static final String DEFAULT_WILDCARD_CHARS = "*?";
private static final String TRIE_BUILDER_THREAD_COUNT = "ranger.policyengine.trie.builder.thread.count";
private final RangerServiceDef.RangerResourceDef resourceDef;
private final boolean optIgnoreCase;
private final boolean optWildcard;
private final String wildcardChars;
private final TrieNode<T> root;
private final boolean isOptimizedForRetrieval;
public RangerResourceTrie(RangerServiceDef.RangerResourceDef resourceDef, List<T> evaluators) {
this(resourceDef, evaluators, true, null);
}
public RangerResourceTrie(RangerResourceTrie<T> other) {
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_INIT_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_TRIE_INIT_LOG, "RangerResourceTrie.copyTrie(name=" + other.resourceDef.getName() + ")");
}
this.resourceDef = other.resourceDef;
this.optIgnoreCase = other.optIgnoreCase;
this.optWildcard = other.optWildcard;
this.wildcardChars = other.wildcardChars;
this.isOptimizedForRetrieval = false;
this.root = copyTrieSubtree(other.root, null);
RangerPerfTracer.logAlways(perf);
if (PERF_TRIE_INIT_LOG.isDebugEnabled()) {
PERF_TRIE_INIT_LOG.debug(toString());
}
if (TRACE_LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
root.toString("", sb);
TRACE_LOG.trace("Trie Dump from RangerResourceTrie.copyTrie(name=" + other.resourceDef.getName() + "):\n{" + sb.toString() + "}");
}
}
RangerResourceTrie(RangerServiceDef.RangerResourceDef resourceDef, List<T> evaluators, boolean isOptimizedForRetrieval, RangerPluginContext pluginContext) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerResourceTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + ", isOptimizedForRetrieval=" + isOptimizedForRetrieval + ")");
}
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_INIT_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_TRIE_INIT_LOG, "RangerResourceTrie.init(name=" + resourceDef.getName() + ")");
}
Configuration config = pluginContext != null ? pluginContext.getConfig() : null;
int builderThreadCount = config != null ? config.getInt(TRIE_BUILDER_THREAD_COUNT, 1) : 1;
if (builderThreadCount < 1) {
builderThreadCount = 1;
}
if (TRACE_LOG.isTraceEnabled()) {
TRACE_LOG.trace("builderThreadCount is set to [" + builderThreadCount + "]");
}
Map<String, String> matcherOptions = resourceDef.getMatcherOptions();
boolean optReplaceTokens = RangerAbstractResourceMatcher.getOptionReplaceTokens(matcherOptions);
String tokenReplaceSpecialChars = "";
if(optReplaceTokens) {
char delimiterStart = RangerAbstractResourceMatcher.getOptionDelimiterStart(matcherOptions);
char delimiterEnd = RangerAbstractResourceMatcher.getOptionDelimiterEnd(matcherOptions);
char delimiterEscape = RangerAbstractResourceMatcher.getOptionDelimiterEscape(matcherOptions);
tokenReplaceSpecialChars += delimiterStart;
tokenReplaceSpecialChars += delimiterEnd;
tokenReplaceSpecialChars += delimiterEscape;
}
this.resourceDef = resourceDef;
this.optIgnoreCase = RangerAbstractResourceMatcher.getOptionIgnoreCase(matcherOptions);
this.optWildcard = RangerAbstractResourceMatcher.getOptionWildCard(matcherOptions);
this.wildcardChars = optWildcard ? DEFAULT_WILDCARD_CHARS + tokenReplaceSpecialChars : "" + tokenReplaceSpecialChars;
this.isOptimizedForRetrieval = isOptimizedForRetrieval;
TrieNode<T> tmpRoot = buildTrie(resourceDef, evaluators, builderThreadCount);
if (builderThreadCount > 1 && tmpRoot == null) { // if multi-threaded trie-creation failed, build using a single thread
this.root = buildTrie(resourceDef, evaluators, 1);
} else {
this.root = tmpRoot;
}
wrapUpUpdate();
RangerPerfTracer.logAlways(perf);
if (PERF_TRIE_INIT_LOG.isDebugEnabled()) {
PERF_TRIE_INIT_LOG.debug(toString());
}
if (TRACE_LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
root.toString("", sb);
TRACE_LOG.trace("Trie Dump from RangerResourceTrie.init(name=" + resourceDef.getName() + "):\n{" + sb.toString() + "}");
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerResourceTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + ", isOptimizedForRetrieval=" + isOptimizedForRetrieval + "): " + toString());
}
}
public Set<T> getEvaluatorsForResource(Object resource) {
if (resource instanceof String) {
return getEvaluatorsForResource((String) resource);
} else if (resource instanceof Collection) {
if (CollectionUtils.isEmpty((Collection) resource)) { // treat empty collection same as empty-string
return getEvaluatorsForResource("");
} else {
@SuppressWarnings("unchecked")
Collection<String> resources = (Collection<String>) resource;
return getEvaluatorsForResources(resources);
}
}
return null;
}
public void add(RangerPolicyResource resource, T evaluator) {
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_INIT_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_TRIE_INIT_LOG, "RangerResourceTrie.add(name=" + resource + ")");
}
if (resource == null) {
if (evaluator.isAncestorOf(resourceDef)) {
root.addWildcardEvaluator(evaluator);
}
} else {
if (resource.getIsExcludes()) {
root.addWildcardEvaluator(evaluator);
} else {
if (CollectionUtils.isNotEmpty(resource.getValues())) {
for (String value : resource.getValues()) {
insert(root, value, resource.getIsRecursive(), evaluator);
}
}
}
}
RangerPerfTracer.logAlways(perf);
if (TRACE_LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
root.toString("", sb);
TRACE_LOG.trace("Trie Dump from RangerResourceTrie.add(name=" + resource + "):\n{" + sb.toString() + "}");
}
}
public void delete(RangerPolicyResource resource, T evaluator) {
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_INIT_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_TRIE_INIT_LOG, "RangerResourceTrie.delete(name=" + resource + ")");
}
boolean isRemoved = false;
if (resource.getIsExcludes()) {
isRemoved = root.removeWildcardEvaluator(evaluator);
}
if (!isRemoved) {
for (String value : resource.getValues()) {
TrieNode<T> node = getNodeForResource(value);
if (node != null) {
node.removeEvaluatorFromSubtree(evaluator);
}
}
}
RangerPerfTracer.logAlways(perf);
if (TRACE_LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
root.toString("", sb);
TRACE_LOG.trace("Trie Dump from RangerResourceTrie.delete(name=" + resource + "):\n{" + sb.toString() + "}");
}
}
public void wrapUpUpdate() {
if (root != null) {
root.wrapUpUpdate();
}
}
TrieNode<T> getRoot() {
return root;
}
private TrieNode<T> copyTrieSubtree(final TrieNode<T> source, final TrieNode<T> parent) {
if (TRACE_LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
source.toString(sb);
TRACE_LOG.trace("==> copyTrieSubtree(" + sb + ")");
}
TrieNode<T> dest = new TrieNode<>(source.str);
dest.setParent(parent);
synchronized (source.children) {
dest.isSetup = source.isSetup;
dest.isSharingParentWildcardEvaluators = source.isSharingParentWildcardEvaluators;
if (source.isSharingParentWildcardEvaluators) {
if (dest.getParent() != null) {
dest.wildcardEvaluators = dest.getParent().getWildcardEvaluators();
} else {
dest.wildcardEvaluators = null;
}
} else {
if (source.wildcardEvaluators != null) {
dest.wildcardEvaluators = new HashSet<>(source.wildcardEvaluators);
} else {
dest.wildcardEvaluators = null;
}
}
if (source.evaluators != null) {
if (source.evaluators == source.wildcardEvaluators) {
dest.evaluators = dest.wildcardEvaluators;
} else {
dest.evaluators = new HashSet<>(source.evaluators);
}
} else {
dest.evaluators = null;
}
}
Map<Character, TrieNode<T>> children = source.getChildren();
for (Map.Entry<Character, TrieNode<T>> entry : children.entrySet()) {
copyTrieSubtree(entry.getValue(), dest);
}
if (TRACE_LOG.isTraceEnabled()) {
StringBuilder sourceAsString = new StringBuilder(), destAsString = new StringBuilder();
source.toString(sourceAsString);
dest.toString(destAsString);
TRACE_LOG.trace("<== copyTrieSubtree(" + sourceAsString + ") : " + destAsString);
}
return dest;
}
private TrieNode<T> buildTrie(RangerServiceDef.RangerResourceDef resourceDef, List<T> evaluators, int builderThreadCount) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> buildTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + ", isMultiThreaded=" + (builderThreadCount > 1) + ")");
}
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_INIT_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_TRIE_INIT_LOG, "RangerResourceTrie.init(resourceDef=" + resourceDef.getName() + ")");
}
TrieNode<T> ret = new TrieNode<>(null);
final boolean isMultiThreaded = builderThreadCount > 1;
final List<ResourceTrieBuilderThread> builderThreads;
final Map<Character, Integer> builderThreadMap;
final String resourceName = resourceDef.getName();
int lastUsedThreadIndex = 0;
if (isMultiThreaded) {
builderThreads = new ArrayList<>();
for (int i = 0; i < builderThreadCount; i++) {
ResourceTrieBuilderThread t = new ResourceTrieBuilderThread();
t.setDaemon(true);
builderThreads.add(t);
t.start();
}
builderThreadMap = new HashMap<>();
} else {
builderThreads = null;
builderThreadMap = null;
}
for (T evaluator : evaluators) {
Map<String, RangerPolicyResource> policyResources = evaluator.getPolicyResource();
RangerPolicyResource policyResource = policyResources != null ? policyResources.get(resourceName) : null;
if (policyResource == null) {
if (evaluator.isAncestorOf(resourceDef)) {
ret.addWildcardEvaluator(evaluator);
}
continue;
}
if (policyResource.getIsExcludes()) {
ret.addWildcardEvaluator(evaluator);
} else {
RangerResourceMatcher resourceMatcher = evaluator.getResourceMatcher(resourceName);
if (resourceMatcher != null && (resourceMatcher.isMatchAny())) {
ret.addWildcardEvaluator(evaluator);
} else {
if (CollectionUtils.isNotEmpty(policyResource.getValues())) {
for (String resource : policyResource.getValues()) {
if (!isMultiThreaded) {
insert(ret, resource, policyResource.getIsRecursive(), evaluator);
} else {
try {
lastUsedThreadIndex = insert(ret, resource, policyResource.getIsRecursive(), evaluator, builderThreadMap, builderThreads, lastUsedThreadIndex);
} catch (InterruptedException ex) {
LOG.error("Failed to dispatch " + resource + " to " + builderThreads.get(lastUsedThreadIndex));
LOG.error("Failing and retrying with one thread");
ret = null;
break;
}
}
}
if (ret == null) {
break;
}
}
}
}
}
if (ret != null) {
if (isMultiThreaded) {
for (ResourceTrieBuilderThread t : builderThreads) {
try {
// Send termination signal to each thread
t.add("", false, null);
// Wait for threads to finish work
t.join();
ret.getChildren().putAll(t.getSubtrees());
} catch (InterruptedException ex) {
LOG.error("BuilderThread " + t + " was interrupted:", ex);
LOG.error("Failing and retrying with one thread");
ret = null;
break;
}
}
cleanUpThreads(builderThreads);
}
}
RangerPerfTracer.logAlways(perf);
if(LOG.isDebugEnabled()) {
LOG.debug("<== buildTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + ", isMultiThreaded=" + isMultiThreaded + ") :" + ret);
}
return ret;
}
private void cleanUpThreads(List<ResourceTrieBuilderThread> builderThreads) {
if (CollectionUtils.isNotEmpty(builderThreads)) {
for (ResourceTrieBuilderThread t : builderThreads) {
try {
if (t.isAlive()) {
t.interrupt();
t.join();
}
} catch (InterruptedException ex) {
LOG.error("Could not terminate thread " + t);
}
}
}
}
private TrieData getTrieData() {
TrieData ret = new TrieData();
root.populateTrieData(ret);
ret.maxDepth = getMaxDepth();
return ret;
}
private int getMaxDepth() {
return root.getMaxDepth();
}
private Character getLookupChar(char ch) {
return optIgnoreCase ? Character.toLowerCase(ch) : ch;
}
private Character getLookupChar(String str, int index) {
return getLookupChar(str.charAt(index));
}
private int insert(TrieNode<T> currentRoot, String resource, boolean isRecursive, T evaluator, Map<Character, Integer> builderThreadMap, List<ResourceTrieBuilderThread> builderThreads, int lastUsedThreadIndex) throws InterruptedException {
int ret = lastUsedThreadIndex;
final String prefix = getNonWildcardPrefix(resource);
if (StringUtils.isNotEmpty(prefix)) {
char c = getLookupChar(prefix.charAt(0));
Integer index = builderThreadMap.get(c);
if (index == null) {
ret = index = (lastUsedThreadIndex + 1) % builderThreads.size();
builderThreadMap.put(c, index);
}
builderThreads.get(index).add(resource, isRecursive, evaluator);
} else {
currentRoot.addWildcardEvaluator(evaluator);
}
return ret;
}
private void insert(TrieNode<T> currentRoot, String resource, boolean isRecursive, T evaluator) {
TrieNode<T> curr = currentRoot;
final String prefix = getNonWildcardPrefix(resource);
final boolean isWildcard = prefix.length() != resource.length();
if (StringUtils.isNotEmpty(prefix)) {
curr = curr.getOrCreateChild(prefix);
}
if(isWildcard || isRecursive) {
curr.addWildcardEvaluator(evaluator);
} else {
curr.addEvaluator(evaluator);
}
}
private String getNonWildcardPrefix(String str) {
int minIndex = str.length();
for (int i = 0; i < wildcardChars.length(); i++) {
int index = str.indexOf(wildcardChars.charAt(i));
if (index != -1 && index < minIndex) {
minIndex = index;
}
}
return str.substring(0, minIndex);
}
private Set<T> getEvaluatorsForResource(String resource) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerResourceTrie.getEvaluatorsForResource(" + resource + ")");
}
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_OP_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_TRIE_OP_LOG, "RangerResourceTrie.getEvaluatorsForResource(resource=" + resource + ")");
}
TrieNode<T> curr = root;
TrieNode<T> parent = null;
final int len = resource.length();
int i = 0;
while (i < len) {
if (!isOptimizedForRetrieval) {
curr.setupIfNeeded(parent);
}
final TrieNode<T> child = curr.getChild(getLookupChar(resource, i));
if (child == null) {
break;
}
final String childStr = child.getStr();
if (!resource.regionMatches(optIgnoreCase, i, childStr, 0, childStr.length())) {
break;
}
parent = curr;
curr = child;
i += childStr.length();
}
if (!isOptimizedForRetrieval) {
curr.setupIfNeeded(parent);
}
Set<T> ret = i == len ? curr.getEvaluators() : curr.getWildcardEvaluators();
RangerPerfTracer.logAlways(perf);
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerResourceTrie.getEvaluatorsForResource(" + resource + "): evaluatorCount=" + (ret == null ? 0 : ret.size()));
}
return ret;
}
private TrieNode<T> getNodeForResource(String resource) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerResourceTrie.getNodeForResource(" + resource + ")");
}
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_OP_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_TRIE_OP_LOG, "RangerResourceTrie.getNodeForResource(resource=" + resource + ")");
}
TrieNode<T> curr = root;
final int len = resource.length();
int i = 0;
while (i < len) {
final TrieNode<T> child = curr.getChild(getLookupChar(resource, i));
if (child == null) {
break;
}
final String childStr = child.getStr();
if (!resource.regionMatches(optIgnoreCase, i, childStr, 0, childStr.length())) {
break;
}
curr = child;
i += childStr.length();
}
RangerPerfTracer.logAlways(perf);
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerResourceTrie.getNodeForResource(" + resource + ")");
}
return curr;
}
private Set<T> getEvaluatorsForResources(Collection<String> resources) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerResourceTrie.getEvaluatorsForResources(" + resources + ")");
}
Set<T> ret = null;
Map<Long, T> evaluatorsMap = null;
for (String resource : resources) {
Set<T> resourceEvaluators = getEvaluatorsForResource(resource);
if (CollectionUtils.isEmpty(resourceEvaluators)) {
continue;
}
if (evaluatorsMap == null) {
if (ret == null) { // first resource: don't create map yet
ret = resourceEvaluators;
} else if (ret != resourceEvaluators) { // if evaluator list is same as earlier resources, retain the list, else create a map
evaluatorsMap = new HashMap<>();
for (T evaluator : ret) {
evaluatorsMap.put(evaluator.getId(), evaluator);
}
ret = null;
}
}
if (evaluatorsMap != null) {
for (T evaluator : resourceEvaluators) {
evaluatorsMap.put(evaluator.getId(), evaluator);
}
}
}
if (ret == null && evaluatorsMap != null) {
ret = new HashSet<>(evaluatorsMap.values());
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerResourceTrie.getEvaluatorsForResources(" + resources + "): evaluatorCount=" + (ret == null ? 0 : ret.size()));
}
return ret;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
TrieData trieData = getTrieData();
sb.append("resourceName=").append(resourceDef.getName());
sb.append("; optIgnoreCase=").append(optIgnoreCase);
sb.append("; optWildcard=").append(optWildcard);
sb.append("; wildcardChars=").append(wildcardChars);
sb.append("; nodeCount=").append(trieData.nodeCount);
sb.append("; leafNodeCount=").append(trieData.leafNodeCount);
sb.append("; singleChildNodeCount=").append(trieData.singleChildNodeCount);
sb.append("; maxDepth=").append(trieData.maxDepth);
sb.append("; evaluatorListCount=").append(trieData.evaluatorListCount);
sb.append("; wildcardEvaluatorListCount=").append(trieData.wildcardEvaluatorListCount);
sb.append("; evaluatorListRefCount=").append(trieData.evaluatorListRefCount);
sb.append("; wildcardEvaluatorListRefCount=").append(trieData.wildcardEvaluatorListRefCount);
return sb.toString();
}
class ResourceTrieBuilderThread extends Thread {
class WorkItem {
final String resourceName;
final boolean isRecursive;
final T evaluator;
WorkItem(String resourceName, boolean isRecursive, T evaluator) {
this.resourceName = resourceName;
this.isRecursive = isRecursive;
this.evaluator = evaluator;
}
@Override
public String toString() {
return
"resourceName=" + resourceName +
"isRecursive=" + isRecursive +
"evaluator=" + (evaluator != null? evaluator.getId() : null);
}
}
private final TrieNode<T> thisRoot = new TrieNode<>(null);
private final BlockingQueue<WorkItem> workQueue = new LinkedBlockingQueue<>();
ResourceTrieBuilderThread() {
}
void add(String resourceName, boolean isRecursive, T evaluator) throws InterruptedException {
workQueue.put(new WorkItem(resourceName, isRecursive, evaluator));
}
Map<Character, TrieNode<T>> getSubtrees() { return thisRoot.getChildren(); }
@Override
public void run() {
if (LOG.isDebugEnabled()) {
LOG.debug("Running " + this);
}
while (true) {
final WorkItem workItem;
try {
workItem = workQueue.take();
} catch (InterruptedException exception) {
LOG.error("Thread=" + this + " is interrupted", exception);
break;
}
if (workItem.evaluator != null) {
insert(thisRoot, workItem.resourceName, workItem.isRecursive, workItem.evaluator);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Received termination signal. " + workItem);
}
break;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Exiting " + this);
}
}
}
static class TrieData {
int nodeCount;
int leafNodeCount;
int singleChildNodeCount;
int maxDepth;
int evaluatorListCount;
int wildcardEvaluatorListCount;
int evaluatorListRefCount;
int wildcardEvaluatorListRefCount;
}
class TrieNode<U extends T> {
private String str;
private TrieNode<U> parent;
private final Map<Character, TrieNode<U>> children = new HashMap<>();
private Set<U> evaluators;
private Set<U> wildcardEvaluators;
private boolean isSharingParentWildcardEvaluators;
private volatile boolean isSetup = false;
TrieNode(String str) {
this.str = str;
}
String getStr() {
return str;
}
void setStr(String str) {
this.str = str;
}
TrieNode<U> getParent() {
return parent;
}
void setParent(TrieNode<U> parent) {
this.parent = parent;
}
Map<Character, TrieNode<U>> getChildren() {
return children;
}
Set<U> getEvaluators() {
return evaluators;
}
Set<U> getWildcardEvaluators() {
return wildcardEvaluators;
}
TrieNode<U> getChild(Character ch) {
return children.get(ch);
}
void populateTrieData(RangerResourceTrie.TrieData trieData) {
trieData.nodeCount++;
if (wildcardEvaluators != null) {
if (isSharingParentWildcardEvaluators) {
trieData.wildcardEvaluatorListRefCount++;
} else {
trieData.wildcardEvaluatorListCount++;
}
}
if (evaluators != null) {
if (evaluators == wildcardEvaluators) {
trieData.evaluatorListRefCount++;
} else {
trieData.evaluatorListCount++;
}
}
if (!children.isEmpty()) {
if (children.size() == 1) {
trieData.singleChildNodeCount++;
}
for (Map.Entry<Character, TrieNode<U>> entry : children.entrySet()) {
TrieNode child = entry.getValue();
child.populateTrieData(trieData);
}
} else {
trieData.leafNodeCount++;
}
}
int getMaxDepth() {
int ret = 0;
for (Map.Entry<Character, TrieNode<U>> entry : children.entrySet()) {
TrieNode<U> child = entry.getValue();
int maxChildDepth = child.getMaxDepth();
if (maxChildDepth > ret) {
ret = maxChildDepth;
}
}
return ret + 1;
}
TrieNode<U> getOrCreateChild(String str) {
int len = str.length();
TrieNode<U> child = children.get(getLookupChar(str, 0));
if (child == null) {
child = new TrieNode<>(str);
addChild(child);
} else {
final String childStr = child.getStr();
final int childStrLen = childStr.length();
final boolean isExactMatch = optIgnoreCase ? StringUtils.equalsIgnoreCase(childStr, str) : StringUtils.equals(childStr, str);
if (!isExactMatch) {
final int numOfCharactersToMatch = Math.min(childStrLen, len);
int index = 1;
for (; index < numOfCharactersToMatch; index++) {
if (getLookupChar(childStr, index) != getLookupChar(str, index)) {
break;
}
}
if (index == numOfCharactersToMatch) {
// Matched all
if (childStrLen > len) {
// Existing node has longer string, need to break up this node
TrieNode<U> newChild = new TrieNode<>(str);
this.addChild(newChild);
child.setStr(childStr.substring(index));
newChild.addChild(child);
child = newChild;
} else {
// This is a longer string, build a child with leftover string
child = child.getOrCreateChild(str.substring(index));
}
} else {
// Partial match for both; both have leftovers
String matchedPart = str.substring(0, index);
TrieNode<U> newChild = new TrieNode<>(matchedPart);
this.addChild(newChild);
child.setStr(childStr.substring(index));
newChild.addChild(child);
child = newChild.getOrCreateChild(str.substring(index));
}
}
}
return child;
}
private void addChild(TrieNode<U> child) {
children.put(getLookupChar(child.getStr(), 0), child);
child.setParent(this);
}
void addEvaluator(U evaluator) {
if (evaluators == null) {
evaluators = new HashSet<>();
}
evaluators.add(evaluator);
}
void addWildcardEvaluator(U evaluator) {
if (wildcardEvaluators == null) {
wildcardEvaluators = new HashSet<>();
}
if (!wildcardEvaluators.contains(evaluator)) {
wildcardEvaluators.add(evaluator);
undoSetup();
}
}
void removeEvaluator(U evaluator) {
if (CollectionUtils.isNotEmpty(evaluators) && evaluators.contains(evaluator)) {
evaluators.remove(evaluator);
if (CollectionUtils.isEmpty(evaluators)) {
evaluators = null;
}
}
}
boolean removeWildcardEvaluator(U evaluator) {
if (CollectionUtils.isNotEmpty(wildcardEvaluators) && wildcardEvaluators.contains(evaluator)) {
wildcardEvaluators.remove(evaluator);
if (CollectionUtils.isEmpty(wildcardEvaluators)) {
wildcardEvaluators = null;
}
undoSetup();
return true;
} else {
return false;
}
}
void undoSetup() {
if (isSetup) {
if (evaluators != null) {
for (TrieNode<U> child : children.values()) {
child.undoSetup();
}
if (evaluators == wildcardEvaluators) {
evaluators = null;
} else {
if (wildcardEvaluators != null) {
evaluators.removeAll(wildcardEvaluators);
if (CollectionUtils.isEmpty(evaluators)) {
evaluators = null;
}
if (isSharingParentWildcardEvaluators) {
wildcardEvaluators = null;
} else {
Set<U> parentWildcardEvaluators = getParent() == null ? null : getParent().getWildcardEvaluators();
if (parentWildcardEvaluators != null) {
wildcardEvaluators.removeAll(parentWildcardEvaluators);
if (CollectionUtils.isEmpty(wildcardEvaluators)) {
wildcardEvaluators = null;
}
}
}
}
}
}
isSharingParentWildcardEvaluators = false;
isSetup = false;
}
}
void wrapUpUpdate() {
if (isOptimizedForRetrieval) {
RangerPerfTracer postSetupPerf = null;
if (RangerPerfTracer.isPerfTraceEnabled(PERF_TRIE_INIT_LOG)) {
postSetupPerf = RangerPerfTracer.getPerfTracer(PERF_TRIE_INIT_LOG, "RangerResourceTrie.init(name=" + resourceDef.getName() + "-postSetup)");
}
postSetup(null);
RangerPerfTracer.logAlways(postSetupPerf);
}
}
void postSetup(Set<U> parentWildcardEvaluators) {
setup(parentWildcardEvaluators);
for (Map.Entry<Character, TrieNode<U>> entry : children.entrySet()) {
TrieNode<U> child = entry.getValue();
child.postSetup(wildcardEvaluators);
}
}
void setupIfNeeded(TrieNode<U> parent) {
boolean setupNeeded = !isSetup;
if (setupNeeded) {
synchronized (this.children) {
setupNeeded = !isSetup;
if (setupNeeded) {
setup(parent == null ? null : parent.getWildcardEvaluators());
if (TRACE_LOG.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
this.toString(sb);
TRACE_LOG.trace("Set up is completed for this TriNode as a part of access evaluation : [" + sb + "]");
}
}
}
}
}
void setup(Set<U> parentWildcardEvaluators) {
if (!isSetup) {
// finalize wildcard-evaluators list by including parent's wildcard evaluators
if (parentWildcardEvaluators != null) {
if (CollectionUtils.isEmpty(this.wildcardEvaluators)) {
this.wildcardEvaluators = parentWildcardEvaluators;
} else {
for (U evaluator : parentWildcardEvaluators) {
addWildcardEvaluator(evaluator);
}
}
}
this.isSharingParentWildcardEvaluators = wildcardEvaluators == parentWildcardEvaluators;
// finalize evaluators list by including wildcard evaluators
if (wildcardEvaluators != null) {
if (CollectionUtils.isEmpty(this.evaluators)) {
this.evaluators = wildcardEvaluators;
} else {
for (U evaluator : wildcardEvaluators) {
addEvaluator(evaluator);
}
}
}
isSetup = true;
}
}
private void removeEvaluatorFromSubtree(U evaluator) {
if (removeWildcardEvaluator(evaluator)) {
for (Map.Entry<Character, TrieNode<U>> entry : children.entrySet()) {
entry.getValue().removeEvaluatorFromSubtree(evaluator);
}
}
removeEvaluator(evaluator);
}
void toString(StringBuilder sb) {
String nodeValue = this.str;
sb.append("nodeValue=").append(nodeValue);
sb.append("; isSetup=").append(isSetup);
sb.append("; isSharingParentWildcardEvaluators=").append(isSharingParentWildcardEvaluators);
sb.append("; childCount=").append(children.size());
sb.append("; evaluators=[ ");
if (evaluators != null) {
for (U evaluator : evaluators) {
sb.append(evaluator.getId()).append(" ");
}
}
sb.append("]");
sb.append("; wildcardEvaluators=[ ");
if (wildcardEvaluators != null) {
for (U evaluator : wildcardEvaluators) {
sb.append(evaluator.getId()).append(" ");
}
}
}
void toString(String prefix, StringBuilder sb) {
String nodeValue = prefix + (str != null ? str : "");
sb.append(prefix);
toString(sb);
sb.append("]\n");
for (Map.Entry<Character, TrieNode<U>> entry : children.entrySet()) {
TrieNode<U> child = entry.getValue();
child.toString(nodeValue, sb);
}
}
}
}