| /* |
| * 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.hadoop.conf.Configuration; |
| import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource; |
| import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; |
| import org.apache.ranger.plugin.policyengine.RangerAccessRequest.ResourceElementMatchingScope; |
| import org.apache.ranger.plugin.policyevaluator.RangerPolicyEvaluator; |
| import org.apache.ranger.plugin.policyresourcematcher.RangerResourceEvaluator; |
| import org.apache.ranger.plugin.resourcematcher.RangerAbstractResourceMatcher; |
| import org.apache.ranger.plugin.resourcematcher.RangerResourceMatcher; |
| import org.apache.ranger.plugin.util.RangerPerfTracer; |
| import org.apache.ranger.plugin.util.RangerRequestExprResolver; |
| import org.apache.ranger.plugin.util.ServiceDefUtil; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.LinkedBlockingQueue; |
| |
| import static org.apache.ranger.plugin.resourcematcher.RangerPathResourceMatcher.DEFAULT_PATH_SEPARATOR_CHAR; |
| import static org.apache.ranger.plugin.resourcematcher.RangerPathResourceMatcher.OPTION_PATH_SEPARATOR; |
| |
| public class RangerResourceTrie<T extends RangerResourceEvaluator> { |
| private static final Logger LOG = LoggerFactory.getLogger(RangerResourceTrie.class); |
| private static final Logger TRACE_LOG = RangerPerfTracer.getPerfLogger("resourcetrie.trace"); |
| private static final Logger PERF_TRIE_INIT_LOG = RangerPerfTracer.getPerfLogger("resourcetrie.init"); |
| private static final Logger 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 RangerResourceDef resourceDef; |
| private final boolean optIgnoreCase; |
| private final boolean optWildcard; |
| private final String wildcardChars; |
| private final boolean isOptimizedForRetrieval; |
| private final boolean isOptimizedForSpace; |
| private final Character separatorChar; |
| private Set<T> inheritedEvaluators; |
| private final TrieNode<T> root; |
| |
| public RangerResourceTrie(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.isOptimizedForSpace = other.isOptimizedForSpace; |
| this.isOptimizedForRetrieval = other.isOptimizedForRetrieval; |
| this.separatorChar = other.separatorChar; |
| this.inheritedEvaluators = other.inheritedEvaluators != null ? new HashSet<>(other.inheritedEvaluators) : null; |
| this.root = copyTrieSubtree(other.root, null); |
| |
| wrapUpUpdate(); |
| |
| if (!isOptimizedForRetrieval) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Trie for " + this.resourceDef.getName() + " is not optimized for retrieval. Resetting isSetup flag by calling undoSetup() on the root"); |
| } |
| root.undoSetup(); |
| } |
| |
| RangerPerfTracer.logAlways(perf); |
| |
| if (PERF_TRIE_INIT_LOG.isDebugEnabled()) { |
| PERF_TRIE_INIT_LOG.debug(toString()); |
| } |
| |
| if (TRACE_LOG.isTraceEnabled()) { |
| TRACE_LOG.trace("Trie Dump from RangerResourceTrie.copyTrie(name=" + other.resourceDef.getName() + "):\n[" + dumpTrie() + "]"); |
| } |
| } |
| |
| public RangerResourceTrie(RangerResourceDef resourceDef, List<T> evaluators, boolean isOptimizedForRetrieval, RangerPluginContext pluginContext) { |
| this(resourceDef, evaluators, isOptimizedForRetrieval, false, pluginContext); |
| } |
| |
| public <E> RangerResourceTrie(RangerResourceDef resourceDef, List<E> evaluators, boolean isOptimizedForRetrieval, boolean isOptimizedForSpace, RangerPluginContext pluginContext) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerResourceTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + ", isOptimizedForRetrieval=" + isOptimizedForRetrieval + ", isOptimizedForSpace=" + isOptimizedForSpace + ")"); |
| } |
| |
| 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); |
| boolean optReplaceReqExpressions = RangerAbstractResourceMatcher.getOptionReplaceReqExpressions(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; |
| } |
| |
| if (optReplaceReqExpressions) { |
| tokenReplaceSpecialChars += RangerRequestExprResolver.EXPRESSION_START.charAt(0); |
| } |
| |
| this.resourceDef = resourceDef; |
| this.optIgnoreCase = RangerAbstractResourceMatcher.getOptionIgnoreCase(matcherOptions); |
| this.optWildcard = RangerAbstractResourceMatcher.getOptionWildCard(matcherOptions); |
| this.wildcardChars = optWildcard ? DEFAULT_WILDCARD_CHARS + tokenReplaceSpecialChars : "" + tokenReplaceSpecialChars; |
| this.isOptimizedForSpace = isOptimizedForSpace; |
| this.isOptimizedForRetrieval = !isOptimizedForSpace && isOptimizedForRetrieval; // isOptimizedForSpace takes precedence |
| this.separatorChar = ServiceDefUtil.getCharOption(matcherOptions, OPTION_PATH_SEPARATOR, DEFAULT_PATH_SEPARATOR_CHAR); |
| |
| final TrieNode<T> tmpRoot = buildTrie(resourceDef, evaluators, builderThreadCount); |
| |
| if (builderThreadCount > 1 && tmpRoot == null) { // if multithreaded 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()) { |
| TRACE_LOG.trace("Trie Dump from RangerResourceTrie.init(name=" + resourceDef.getName() + "):\n[" + dumpTrie() + "]"); |
| } |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerResourceTrie(" + resourceDef.getName() + ", evaluatorCount=" + evaluators.size() + ", isOptimizedForRetrieval=" + this.isOptimizedForRetrieval + ", isOptimizedForSpace=" + this.isOptimizedForSpace + "): " + this); |
| } |
| } |
| |
| public Set<T> getInheritedEvaluators() { |
| return inheritedEvaluators; |
| } |
| |
| public Set<T> getEvaluatorsForResource(Object resource) { |
| return getEvaluatorsForResource(resource, ResourceElementMatchingScope.SELF); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public Set<T> getEvaluatorsForResource(Object resource, ResourceElementMatchingScope scope) { |
| if (resource instanceof String) { |
| return getEvaluatorsForResource((String) resource, scope); |
| } else if (resource instanceof Collection) { |
| Collection<String> resources = (Collection<String>) resource; |
| |
| if (CollectionUtils.isEmpty(resources)) { // treat empty collection same as empty-string |
| return getEvaluatorsForResource("", scope); |
| } else { |
| return getEvaluatorsForResources(resources, scope); |
| } |
| } |
| |
| 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)) { |
| addInheritedEvaluator(evaluator); |
| } |
| } else { |
| if (resource.getIsExcludes()) { |
| addInheritedEvaluator(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()) { |
| TRACE_LOG.trace("Trie Dump from RangerResourceTrie.add(name=" + resource + "):\n[" + dumpTrie() + "]"); |
| } |
| } |
| |
| 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 + ")"); |
| } |
| |
| if (resource == null) { |
| if (evaluator.isAncestorOf(resourceDef)) { |
| removeInheritedEvaluator(evaluator); |
| } |
| } else if (resource.getIsExcludes()) { |
| removeInheritedEvaluator(evaluator); |
| } else { |
| for (String value : resource.getValues()) { |
| TrieNode<T> node = getNodeForResource(value); |
| if (node != null) { |
| node.removeEvaluatorFromSubtree(evaluator); |
| } |
| } |
| } |
| |
| RangerPerfTracer.logAlways(perf); |
| |
| if (TRACE_LOG.isTraceEnabled()) { |
| TRACE_LOG.trace("Trie Dump from RangerResourceTrie.delete(name=" + resource + "):\n[" + dumpTrie()+ "]"); |
| } |
| } |
| |
| public void wrapUpUpdate() { |
| if (root != null) { |
| root.wrapUpUpdate(); |
| if (TRACE_LOG.isTraceEnabled()) { |
| TRACE_LOG.trace("Trie Dump from RangerResourceTrie.wrapUpUpdate(name=" + resourceDef.getName() + "):\n[" + dumpTrie() + "]"); |
| } |
| } |
| } |
| |
| public StringBuilder dumpTrie() { |
| StringBuilder sb = new StringBuilder(); |
| if (root != null) { |
| root.toString("", sb); |
| } |
| return sb; |
| } |
| |
| TrieNode<T> getRoot() { |
| return root; |
| } |
| |
| private void addInheritedEvaluator(T evaluator) { |
| if (inheritedEvaluators == null) { |
| inheritedEvaluators = new HashSet<>(); |
| } |
| |
| inheritedEvaluators.add(evaluator); |
| } |
| |
| private void removeInheritedEvaluator(T evaluator) { |
| if (CollectionUtils.isNotEmpty(inheritedEvaluators) && inheritedEvaluators.contains(evaluator)) { |
| inheritedEvaluators.remove(evaluator); |
| if (CollectionUtils.isEmpty(inheritedEvaluators)) { |
| inheritedEvaluators = null; |
| } |
| } |
| } |
| |
| 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); |
| |
| if (parent != null) { |
| parent.addChild(dest); |
| } |
| |
| 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 <E> TrieNode<T> buildTrie(RangerResourceDef resourceDef, List<E> 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 (E evaluator : evaluators) { |
| final List<T> resourceEvaluators; |
| |
| if (evaluator instanceof RangerPolicyEvaluator) { |
| resourceEvaluators = (List<T>) ((RangerPolicyEvaluator) evaluator).getResourceEvaluators(); |
| } else if (evaluator instanceof RangerResourceEvaluator) { |
| resourceEvaluators = Collections.singletonList((T) evaluator); |
| } else { |
| LOG.error("buildTrie(): unexpected evaluator class " + evaluator.getClass().getCanonicalName()); |
| |
| resourceEvaluators = Collections.emptyList(); |
| } |
| |
| for (T resourceEvaluator : resourceEvaluators) { |
| Map<String, RangerPolicyResource> policyResources = resourceEvaluator.getPolicyResource(); |
| RangerPolicyResource policyResource = policyResources != null ? policyResources.get(resourceName) : null; |
| |
| if (policyResource == null) { |
| if (resourceEvaluator.isAncestorOf(resourceDef)) { |
| addInheritedEvaluator(resourceEvaluator); |
| } |
| |
| continue; |
| } |
| |
| if (policyResource.getIsExcludes()) { |
| addInheritedEvaluator(resourceEvaluator); |
| } else { |
| RangerResourceMatcher resourceMatcher = resourceEvaluator.getResourceMatcher(resourceName); |
| |
| if (resourceMatcher != null && (resourceMatcher.isMatchAny())) { |
| ret.addWildcardEvaluator(resourceEvaluator); |
| } else { |
| if (CollectionUtils.isNotEmpty(policyResource.getValues())) { |
| for (String resource : policyResource.getValues()) { |
| if (!isMultiThreaded) { |
| insert(ret, resource, policyResource.getIsRecursive(), resourceEvaluator); |
| } else { |
| try { |
| lastUsedThreadIndex = insert(ret, resource, policyResource.getIsRecursive(), resourceEvaluator, 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) { |
| 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 int getNonWildcardPrefixLength(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 minIndex; |
| } |
| |
| private String getNonWildcardPrefix(String str) { |
| int prefixLen = getNonWildcardPrefixLength(str); |
| |
| return (prefixLen < str.length()) ? str.substring(0, prefixLen) : str; |
| } |
| |
| |
| |
| private Set<T> getEvaluatorsForResource(String resource, ResourceElementMatchingScope scope) { |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("==> RangerResourceTrie.getEvaluatorsForResource(" + resource + ", " + scope + ")"); |
| } |
| |
| 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; |
| TrieNode<T> child = null; |
| final int len = resource.length(); |
| int i = 0; |
| |
| Set<T> accumulatedEvaluators = new HashSet<>(); |
| |
| while (i < len) { |
| if (!isOptimizedForSpace) { |
| curr.setupIfNeeded(parent); |
| } else { |
| if (curr.getWildcardEvaluators() != null) { |
| accumulatedEvaluators.addAll(curr.getWildcardEvaluators()); |
| } |
| } |
| |
| 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 (!isOptimizedForSpace) { |
| curr.setupIfNeeded(parent); |
| } else { |
| if (curr.getWildcardEvaluators() != null) { |
| accumulatedEvaluators.addAll(curr.getWildcardEvaluators()); |
| } |
| } |
| |
| boolean isSelfMatch = (i == len); |
| Set<T> ret; |
| |
| if (!isOptimizedForSpace) { |
| ret = isSelfMatch ? curr.getEvaluators() : curr.getWildcardEvaluators(); |
| } else { |
| if (isSelfMatch) { |
| if (curr.getEvaluators() != null) { |
| accumulatedEvaluators.addAll(curr.getEvaluators()); |
| } |
| } |
| ret = accumulatedEvaluators; |
| } |
| |
| final boolean includeChildEvaluators = scope == ResourceElementMatchingScope.SELF_OR_CHILD || scope == ResourceElementMatchingScope.SELF_OR_PREFIX; |
| final Set<T> childEvaluators = includeChildEvaluators ? new HashSet<>() : null; |
| |
| if (scope == ResourceElementMatchingScope.SELF_OR_CHILD) { |
| final boolean resourceEndsWithSep = resource.charAt(resource.length() - 1) == separatorChar; |
| |
| if (isSelfMatch) { // resource == path(curr) |
| if (resourceEndsWithSep) { // ex: resource=/tmp/ |
| curr.getChildren().values().stream().forEach(c -> c.collectChildEvaluators(separatorChar, 0, childEvaluators)); |
| } else { // ex: resource=/tmp |
| curr = curr.getChild(separatorChar); |
| |
| if (curr != null) { |
| curr.collectChildEvaluators(separatorChar, 1, childEvaluators); |
| } |
| } |
| } else if (child != null) { // resource != path(child) ex: (resource=/tmp, path(child)=/tmp/test.txt or path(child)=/tmpdir) |
| int remainingLen = len - i; |
| boolean isPrefixMatch = child.getStr().regionMatches(optIgnoreCase, 0, resource, i, remainingLen); |
| |
| if (isPrefixMatch) { |
| if (resourceEndsWithSep) { // ex: resource=/tmp/ |
| child.collectChildEvaluators(separatorChar, remainingLen, childEvaluators); |
| } else if (child.getStr().charAt(remainingLen) == separatorChar) { // ex: resource=/tmp |
| child.collectChildEvaluators(separatorChar, remainingLen + 1, childEvaluators); |
| } |
| } |
| } |
| } else if (scope == ResourceElementMatchingScope.SELF_OR_PREFIX) { |
| curr.collectChildEvaluators(resource, i, childEvaluators); |
| } |
| |
| if (CollectionUtils.isNotEmpty(childEvaluators)) { |
| if (CollectionUtils.isNotEmpty(ret)) { |
| childEvaluators.addAll(ret); |
| } |
| |
| ret = childEvaluators; |
| } |
| |
| RangerPerfTracer.logAlways(perf); |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerResourceTrie.getEvaluatorsForResource(" + resource + ", " + scope + "): evaluators=" + (ret == null ? null : Arrays.deepToString(ret.toArray()))); |
| } |
| |
| 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 = getNonWildcardPrefixLength(resource); |
| 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(); |
| } |
| |
| curr = (i == len) ? curr : null; |
| |
| RangerPerfTracer.logAlways(perf); |
| |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("<== RangerResourceTrie.getNodeForResource(" + resource + ")"); |
| } |
| |
| return curr; |
| } |
| |
| private Set<T> getEvaluatorsForResources(Collection<String> resources, ResourceElementMatchingScope scope) { |
| 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, scope); |
| |
| 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 volatile Set<U> evaluators; |
| private volatile 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<U> 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<>(); |
| } |
| |
| wildcardEvaluators.add(evaluator); |
| } |
| |
| void removeEvaluator(U evaluator) { |
| if (CollectionUtils.isNotEmpty(evaluators)) { |
| evaluators.remove(evaluator); |
| |
| if (CollectionUtils.isEmpty(evaluators)) { |
| evaluators = null; |
| } |
| } |
| } |
| |
| void removeWildcardEvaluator(U evaluator) { |
| if (CollectionUtils.isNotEmpty(wildcardEvaluators)) { |
| wildcardEvaluators.remove(evaluator); |
| |
| if (CollectionUtils.isEmpty(wildcardEvaluators)) { |
| wildcardEvaluators = null; |
| } |
| } |
| } |
| |
| void undoSetup() { |
| for (TrieNode<U> child : children.values()) { |
| child.undoSetup(); |
| } |
| |
| if (isSetup) { |
| if (evaluators != null) { |
| if (evaluators == wildcardEvaluators) { |
| evaluators = null; |
| } else { |
| if (wildcardEvaluators != null) { |
| evaluators.removeAll(wildcardEvaluators); |
| |
| if (CollectionUtils.isEmpty(evaluators)) { |
| evaluators = null; |
| } |
| } |
| } |
| } |
| |
| if (wildcardEvaluators != 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 removeSelfFromTrie() { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("==> removeSelfFromTrie(" + this + ")"); |
| } |
| if (evaluators == null && children.size() == 0) { |
| TrieNode<U> parent = getParent(); |
| if (parent != null) { |
| parent.children.remove(str.charAt(0)); |
| } |
| } else { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("removeSelfFromTrie(" + this + ") could not remove self from Trie"); |
| } |
| } |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("<== removeSelfFromTrie(" + this + ")"); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| void collectChildEvaluators(Character sep, int startIdx, Set<U> childEvaluators) { |
| if (!isOptimizedForSpace) { |
| setupIfNeeded(getParent()); |
| } |
| |
| final int sepPos = startIdx < str.length() ? str.indexOf(sep, startIdx) : -1; |
| |
| if (sepPos == -1) { // ex: startIdx=5, path(str)=/tmp/test, path(a child) could be: /tmp/test.txt, /tmp/test/, /tmp/test/a, /tmp/test/a/b |
| if (isOptimizedForSpace) { |
| if (this.wildcardEvaluators != null) { |
| childEvaluators.addAll(this.wildcardEvaluators); |
| } |
| } |
| if (this.evaluators != null) { |
| childEvaluators.addAll(this.evaluators); |
| } |
| |
| children.values().stream().forEach(c -> c.collectChildEvaluators(sep, 0, childEvaluators)); |
| } else if (sepPos == (str.length() - 1)) { // ex: str=/tmp/test/, startIdx=5 |
| if (isOptimizedForSpace) { |
| if (this.wildcardEvaluators != null) { |
| childEvaluators.addAll(this.wildcardEvaluators); |
| } |
| } |
| if (this.evaluators != null) { |
| childEvaluators.addAll(this.evaluators); |
| } |
| } |
| } |
| |
| void collectChildEvaluators(String resource, int startIndex, Set<U> childEvaluators) { |
| if (startIndex == resource.length()) { |
| collectChildEvaluators(childEvaluators); |
| } else if (startIndex < resource.length()) { |
| Character startChar = getLookupChar(resource, startIndex); |
| TrieNode<U> childNode = children.get(startChar); |
| |
| if (childNode != null) { |
| if (!isOptimizedForSpace) { |
| childNode.setupIfNeeded(childNode.getParent()); |
| } |
| |
| String childStr = childNode.getStr(); |
| int lenToMatch = Math.min(resource.length() - startIndex, childStr.length()); |
| |
| if (resource.regionMatches(optIgnoreCase, startIndex, childStr, 0, lenToMatch)) { |
| if (childNode.wildcardEvaluators != null) { |
| childEvaluators.addAll(childNode.wildcardEvaluators); |
| } |
| |
| if (childNode.evaluators != null) { |
| childEvaluators.addAll(childNode.evaluators); |
| } |
| |
| if (resource.length() == (startIndex + lenToMatch)) { |
| childNode.collectChildEvaluators(childEvaluators); |
| } else { |
| childNode.children.values().stream().forEach(c -> c.collectChildEvaluators(resource, startIndex + childStr.length(), childEvaluators)); |
| } |
| } |
| } |
| } |
| } |
| |
| private void collectChildEvaluators(Set<U> childEvaluators) { |
| Stack<TrieNode<U>> nodes = new Stack<>(); |
| |
| nodes.addAll(children.values()); |
| |
| while (!nodes.isEmpty()) { |
| TrieNode<U> childNode = nodes.pop(); |
| |
| if (!isOptimizedForSpace) { |
| childNode.setupIfNeeded(childNode.getParent()); |
| } |
| |
| if (childNode.wildcardEvaluators != null) { |
| childEvaluators.addAll(childNode.wildcardEvaluators); |
| } |
| |
| if (childNode.evaluators != null) { |
| childEvaluators.addAll(childNode.evaluators); |
| } |
| |
| nodes.addAll(childNode.children.values()); |
| } |
| } |
| |
| private void removeEvaluatorFromSubtree(U evaluator) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("==> removeEvaluatorFromSubtree(" + evaluator.getId() + ")"); |
| } |
| if (CollectionUtils.isNotEmpty(wildcardEvaluators) && wildcardEvaluators.contains(evaluator)) { |
| removeWildcardEvaluator(evaluator); |
| } else { |
| removeEvaluator(evaluator); |
| } |
| removeSelfFromTrie(); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("<== removeEvaluatorFromSubtree(" + evaluator.getId() + ")"); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| toString(sb); |
| return sb.toString(); |
| } |
| |
| void toString(StringBuilder sb) { |
| String nodeValue = this.str; |
| |
| sb.append("nodeValue=").append(nodeValue == null ? "ROOT" : 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("|,|"); |
| } |
| } |
| sb.append("]"); |
| } |
| |
| void toString(String prefix, StringBuilder sb) { |
| String nodeValue = prefix + (str != null ? str : ""); |
| |
| if (!nodeValue.equals(prefix)) { |
| prefix = prefix + "|"; |
| } |
| |
| 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); |
| } |
| } |
| } |
| } |